@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member)
{
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data
static class CreateMemberRequest {
private String name;
}
@Data
static class CreateMemberResponse {
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
}
문제점
결론
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
CreateMemberRequest 를 Member 엔티티 대신에 RequestBody와 매핑한다.
엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있다.
엔티티와 API 스펙을 명확하게 분리할 수 있다.
엔티티가 변해도 API 스펙이 변하지 않는다.
참고 : 실무에서는 엔티티를 API 스펙에 노출하면 안된다!
※ 평소 API 만들 때 엔티티를 노출하지 말자
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long id,
@RequestBody @Valid UpdateMemberRequest request) {
memberService.update(id, request.getName());
Member findMember = memberService.findOne(id);
return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}
@Data
static class UpdateMemberRequest {
private String name;
}
@Data
@AllArgsConstructor
static class UpdateMemberResponse {
private Long id;
private String name;
}
public class MemberService {
private final MemberRepository memberRepository;
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name);
}
}
회원 수정 API updateMemberV2 은 회원 정보를 부분 업데이트 한다.
위의 코드는 PUT 방식을 사용했는데, PUT은 전체 업데이트를 할 때 사용하는 것이 맞다.
부분 업데이트를 하려면 PATCH를 사용하거나 POST를 사용하는 것이 REST 스타일에 맞다.
실무에서는 member 엔티티의 데이터가 필요한 API가 계속 증가하게 된다.
어떤 API는 name 필드가 필요하지만, 어떤 API는 name 필드가 필요없을 수 있다.
결론적으로 엔티티 대신에 API 스펙에 맞는 별도의 DTO를 노출해야 한다.
엔티티를 DTO로 변환해서 반환한다.
엔티티가 변해도 API 스펙이 변경되지 않는다.
추가로 Result 클래스로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있다.
@GetMapping("/api/v2/members")
public Result membersV2() {
List<Member> findMembers = memberService.findMembers();
//엔티티 -> DTO 변환
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName()))
.collect(Collectors.toList());
return new Result(collect);
}
@Data
@AllArgsConstructor
static class Result<T> {
private T data;
}
@Data
@AllArgsConstructor
static class MemberDto {
private String name;
}