등록, 생성, 추가 관련 API
보통 POST Method 를 이용하여 요청을 처리한다.
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMember(@RequestBody @Valid Member member){
...
}
문제: 엔티티를 RequestBody 에 직접 매핑
이유:
- 프레젠테이션을 위한 로직에 엔티티가 들어간 부분은 유지 보수에 좋지 않다.
- RequestBody 로 매핑되는 파라미터는 검증을 할 수 있다. 근데 엔티티를 RequestBody로 직접 매핑시켜버리면 엔티티에 검증을 위한 로직이 추가된다. 예를 들어
@NotNull이 엔티티 필드에 추가된다는 것이다. - API 는 수십~수백개가 존재할 텐데 그 때마다 API 의 요구사항을 엔티티에 반영하는 것은 무리이다.
- 엔티티가 변경되면 API 스펙이 변경될 수 있는 문제가 존재한다.
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMember(@RequestBody @Valid CreateMemberRequest reqeust){
...
}
해결: 엔티티 대신 DTO 클래스를 RequestBody에 매핑
수정 관련 API
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResposne updateMemberV2(@PathVariable("id") Long id, @RequestBody @Valid UpdateMemberRequest request){
memberService.update(id, request.getName());
Member findMember = membeService.findOne(i);
return new UpdateMemberResponse(...);
}
수정 관련 API 에 대해서 어떤 Http Method 를 사용하는 지가 문제일 수 있다.
1. PUT vs POST : 멱득성
POST는 같은 요청을 여러 번 보냈을 때 다른 결과를 반환한다.PUT은 같은 요청에 대해서 항상 같은 결과를 반환한다.
2. PUT vs PATCH : 전체와 부분
PUT은 전체를 업데이트할 때 사용한다.PATCH는 부분을 업데이트할 때 사용한다.- 하지만 이 둘을 명확하게 구분한 것은 어려운 일이다. 예시를 들어 보자.
User엔티티에id와name만 있었다고 가정을 해보자. 이름을 변경하기 위해서는 API는PUT으로 만들어야 한다. 근데 시간이 흘러age라는 필드가user에 추가되었다. 원래name만 변경하는 API 는PATCH로 변경되어야 한다. 이렇게 되면 API 의 method 를 변경해야 하는데 이런 작업이 쉬운 것은 아니다.
서비스 계층의 수정 메소드에서 반환 값 설정없이 void 로 하는 이유
- 목적에 맞지 않다. 수정하는 서비스에 대해서는 해당 객체를 반환하는 것은 조회도 포함하게 되는데 수정과 조회를 함게 하는 형태가 된다. 이런 형태는 유지 보수를 어렵게 한다.
memberService.update(id, request.getName());
Member findMember = memberService.findOne(id);
- 수정 관련 API 에서 응답 값을 보내줘야 하므로 서비스 계층의 조회 메소드를 호출하여 값을 반환해 준다.
조회 관련 API
Get Method를 사용하여 요청을 매핑한다.
@GetMappiing("/api/v1/members")
public List<Member> membersV1(){
return MemberService.findMembers();
}
문제: 응답 값으로 엔티티를 직접 외부에 노출한다.
이유:
- RequsetBody 로 엔티티를 사용할 때와 같은 문제가 발생한다.
- 엔티티의 모든 값이 노출된다. -> 필요한 값만 노출되어야 한다.
- 추가로 컬렉션을 직접 반환할 시 향후 API 스펙을 변경하기가 어렵다.
@GetMappiing("/api/v2/members")
public Result membersV2(){
List<Member> findMembers = MemberService.findMembers();
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName()))
.collect(Collectors.toList());
return new Result(collect);
}
해결: 별도의 응답 DTO 클래스를 생성하여 Result 객체에 담아서 반환한다.
- 엔티티 변경 시 API 스펙 변경이 없다.
- 엔티티가 노출되지 않는다.
Result클래스를 감싸서 향 후 필요한 필드를 추가할 수 있다.
참고자료
인프런 이영한 - 스프링 부트와 JPA 활용 2
'스프링' 카테고리의 다른 글
| [Test] 테스트 컨테이너 설정 (0) | 2025.04.02 |
|---|---|
| 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock) (1) | 2025.03.19 |