데브코스 7주차 과제로 JPA를 사용한 간단한 CRUD 게시판을 만들었다.
🎃 목표
- REST API DOCS를 사용하여 문서화하기 ✅
- 작은 단위로 테스트 코드 작성하기 ✅
- 페어프로그래밍 하기 ✅
🔮 프로젝트를 진행하며 했던 고민
JPA의 엔티티 클래스의 필드는 Primitive Type? Wrapper Class?
- PK에서는 WrapperClass를 사용한다.
- PK가 아직 할당되지 않았음을 명시적으로 표현할 수 있다.
- Hibernate 공식 문서에서 WrapperClass를 사용하는 것을 권장하고 있다.
- Null의 허용 유무에 따라 결정될 수 있다.
- ex) Item의 재고 수량 필드 → Null이 필요하지 않고 값을 입력하지 않으면 0으로 지정해도 문제가 없기 때문에 primitive로 할 수 있다.
- Null이 주는 의미에 따라 결정될 수 있다.
- primitive 타입으로 지정한다면 수(Number)가 주는 의미밖에 전달할 수 없다.
- WrapperClass로 지정한다면 수가 주는 의미 + Null이 주는 의미(값이 할당되지 않음)를 전달할 수 있다.
- 일관성을 주고자 모든 필드를 WrapperClass로 설정할 수 있다.
💡 → 나는 WrapperClass 타입으로 통일하기로 했다.
예를 들어, User 엔티티의 age를 int라는 primitive 타입으로 했다가 만약 User의 age가 @Nullable 하도록 변경된다면 int → Integer로 변경해줘야 할 것이다. WrapperClass 타입으로 지정한다면 이런 변경으로 인한 파급효과를 조금이라도 더 줄일 수 있을 것이다.
static으로 사용할 것인지 bean으로 등록할 것인지
프로젝트에서 DTO → Entity, Entity → DTO로 변환시켜주는 로직을 담고있는 Converter클래스를 만들었다.
public class PostConverter {
public static PostDTO.FindResponse postToFindResponse(Post post) {
return PostDTO.FindResponse.builder()
.id(post.getId())
.title(post.getTitle())
.content(post.getContent())
.userId(post.getUser().getId())
.userName(post.getUser().getName())
.build();
}
public static Post saveRequestToPost(PostDTO.SaveRequest postSaveRequest, User user) {
return Post.builder()
.title(postSaveRequest.getTitle())
.content(postSaveRequest.getContent())
.user(user)
.build();
}
}
이 클래스를 스프링 빈으로 등록하여 주입받아 사용할지, 아니면 static 메소드로 사용할지 고민을 하였다.
검색해본 결과 다음과 같은 결론을 내렸다.
- 외부 자원에 의존하여 외부 자원이 변경된다면 실행 결과에 영향을 끼치는 객체를 스프링 빈으로 등록한다. → 의존성 주입을 통해 의존성을 스프링 컨테이너에서 관리하도록 하기 위해
- 외부 자원에 의존하지 않거나, 의존하더라도 의존하는 외부 자원에 의해 함수의 실행 결과가 바뀌지 않으면서 공용으로 사용되는 객체를 static으로 사용한다.
🧨 궁금한 점 & 알아볼 점
프로젝트를 진행하며 궁금했던 점이 있다.
- PostDTO에서만 쓰는 User의 정보를 Post 패키지에서 관리하는 것이 좋은지 User 패키지에서 관리하는 것이 좋은지?
🎉 배운 것
- JPA Auditing
- @RequestBody
- 페어프로그래밍
- Record
- JPA 페이징
🧪 느낀점
페어프로그래밍
이번 프로젝트는 이전처럼 단순히 각자 프로젝트를 진행하는 것이 아니라 팀원들끼리 팀을 나눠 페어프로그래밍을 진행하였다. 처음 해보는 작업이라 여러 느낀 점이 많았다.
- 나는 개발하면서 정말 적은 고민을 했다.
- 내가 한 고민은 대부분 다른 사람들도 했다. -> 모르겠으면 검색을 통해 고민을 해결해보자.
- 페어프로그래밍을 통해 내가 아는 지식을 재점검하고 팀원이 가지고 있는 새로운 지식을 알 수 있어서 좋았다.
- JPA Auditing에 대해 알게되었다.
- 팀원들과 많은 대화를 하며 더 친해질 수 있어서 좋았다.
- 함께 같은 문제에 대해 고민하면서 시야를 넓힐 수 있었다.
- 많은 대화를 하고 여러 사람의 고민을 함께 생각하다보니 생산성이 떨어졌다.
인터넷 문제와 여러 사람이 함께 코딩을 하다보니 개발 속도가 느려 약간의 답답함은 있었지만 장점이 더 많은 경험이었던 것 같다. 앞으로 팀원들과 자주 페어프로그래밍을 해야겠다고 생각했다.
REST API Docs 사용
처음으로 REST API Docs를 사용해보았다. 테스트를 통과해야만 API 정의서가 작성되는 점이 더 정확한 API 정의서를 작성할 수 있다고 생각하여 좋았다. 하지만 설정하는게 어려웠다.
작은 단위로 테스트하기
@SpringBootTest를 사용하여 통합테스트를 하면 전체적인 flow대로 테스트를 할 수 있고 의존성을 주입받아 쉽게 테스트가 가능하지만 테스트코드 실행 시 모든 빈이 컨테이너에 올라가고 테스트 실패 시 디버깅이 힘들다는 단점이 있어서 최대한 유닛테스트로 테스트 코드를 작성하고자 하였다. 이 과정에서 Mock에 대해 학습할 수 있었다. 다만 아직은 테스트 코드를 어느정도까지 세세하게 작성해야 하는지 고민이 된다. 너무 세세하게 코드를 작성하자니 들어가는 시간 대비 효율성이 떨어질 것 같고, 그냥 넘어가자니 찜찜한.. ㅠㅠ 더 깊이 생각해봐야겠다.
프로젝트 GitRepository
https://github.com/rhdtn311/springboot-board-jpa
피드백
멘토님, 팀원에게 받은 피드백을 정리했다.
- URL에 공통적으로 “/posts”가 포함되어 있다. RequestMapping("/posts")로 라우터 지정해도 좋을 것 같다.
- setter 없이 changeTitle(), chageContent() 메소드를 따로 둔 것은 setter와 다를 바가 없다. 대부분 title, content는 같이 변경될 것 같으므로 하나의 메소드로 관리하는게 좋을 것 같다.
- Pageable과 PageRequestDTO 중 어떤 것을 사용하는 것이 좋을까 → 일단 PageRequestDTO
- Response의 HttpStatus를 담아서 보낼 때 하드코딩보다 HttpStatus 상수를 쓰는 것이 좋다.
- PageDTO.Response의 startPageNumber를 구하는 계산식에서 항상 10으로 나누도록 했다. page의 값은 바뀔 수도 있기 때문에 유동적으로 관리하도록 코드를 바꿔보자
- 에러 핸들링을 할 때 RunTimeException을 핸들링 해두는 것이 디버깅 하기 편할 것이다.
- 엔티티 생성 시에는 HttpStatus를 201로 하는 것이 좋다.
- update 하는 API의 경우 PUT이나 PATCH를 사용하는게 좋다.
- DTO를 inner class로 관리했는데, 이는 복잡성을 많이 늘리게 된다.
피드백은 대부분 기본적인 것들에 대한 내용이 많았다. 아직 내가 기본적인 것도 제대로 고민하지 못하는 것 같아 아쉬웠다. 다음 프로젝트에는 이런 세세한 부분까지 신경써서 코드를 작성할 수 있도록 노력해야겠다.
📜 References
JPA Entity Class 에서 Primitive Type 을 써야할까 Wrapper Class 를 사용해야할까
언제 static 함수 모음 Class를 만들어야 할까?