Intro

The Create (C) of CRUD is done in the previous post at 2022-03-31 How to create effective APIs with DTO? Let’s do the rest.

Update (with DTOs)

@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;
}

Then for your Service layer,

public class MemberService {
   private final MemberRepository memberRepository;
   /**
   * 회원 수정
   */
   @Transactional
   public void update(Long id, String name) {
   Member member = memberRepository.findOne(id);
   member.setName(name);
   }
}

Remeber CQS. Since update is a command, it should not return anything (void). But I can let return of id value by closing one eye.

Also, PUT is for 전체 update. If you want 부분 업데이트, use PATCH or POST according to your REST style.

Read/Search

Let’s discuss 2 versions again.

Version 1 with Entity

@RestController
@RequiredArgsConstructor
public class MemberApiController {
     private final MemberService memberService;
     /**
     * 조회 V1: 응답 값으로 엔티티를 직접 외부에 노출한다.
     * 문제점
     * - 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
     * - 기본적으로 엔티티의 모든 값이 노출된다.
     * - 응답 스펙을 맞추기 위해 로직이 추가된다. (@JsonIgnore, 별도의 뷰 로직 등등)
     * - 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의
    API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
     * - 엔티티가 변경되면 API 스펙이 변한다.
     * - 추가로 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.(별도의 Result 클래스
    생성으로 해결)
     * 결론
     * - API 응답 스펙에 맞추어 별도의 DTO를 반환한다.
     */
     //조회 V1: 안 좋은 버전, 모든 엔티티가 노출, @JsonIgnore -> 이건 정말 최악, api가 이거
    하나인가! 화면에 종속적이지 마라!
     @GetMapping("/api/v1/members")
     public List<Member> membersV1() {
        return memberService.findMembers();
     }
}

The last point in that advantage list is important. What if you want to add new data field like count? If you return collection like V1, it is hard to change API 스펙 because like in this example, it returns an array. The JSON data is enclosed in square brackets of the array so additional features cannot be added within the square brackets.

[
        //cannot add new feature
  {
      json data
  }
  {
      json data
  }
        
        
        ]

Version 2 wtih DTO and Generic T

@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);
        return new Result(collect.size(),collect);
 }
 
 @Data
 @AllArgsConstructor
 static class Result<T> {
    private T data;
    private int count;
 }
 @Data
 @AllArgsConstructor
 static class MemberDto {
    private String name;
 }

Here we used Generic T in Result DTO. So since we are not returning a collection but a generic type of data, JSON data is enclosed in curly braces, not square braces like V1. So we can add new data features like count, inside it.