회원 수정 API - 컨트롤러

@RestController
@RequiredArgsConstructor
public class MemberApiController {

    private final MemberService memberService;

    **@PutMapping("/api/v2/members/{id}") //...1
    public UpdateMemberResponse updateMemberV2(
            @PathVariable("id") Long id, //...2
            @RequestBody @Valid UpdateMemberRequest request) {

        memberService.update(id, request.getName()); //...5
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());
    }

    @Data
    static class UpdateMemberRequest {
        private String name;
    }

    @Data
    @AllArgsConstructor //...4
    static class UpdateMemberResponse {
        private Long id;
        private String name;
    }**

}
  1. Restful하게 만들기 위해서 PUT 메서드를 사용합니다.

    참고

    <aside> ❗ 하지만 정확하게는 PUT 보다는 POST 가 맞습니다.


    회원 수정 API updateMemberV2 은 회원 정보를 부분 업데이트 합니다.

    여기서 PUT 방식을 사용했는데, PUT은 전체 업데이트를 할 때 사용하는 것이 맞습니다.

    부분 업데이트를 하려면 PATCH를 사용하거나 POST를 사용하는 것이 REST 스타일에 맞습니다.

    </aside>

  2. @PathVariable

  3. update용 DTO를 별도로 만들었습니다.

  4. @AllArgsConstructor

    <aside> ✏️ [참고]

    코딩 성향인데, 엔티티에는 롬복을 Getter 정도만 사용하고 DTO에는 롬복을 막 사용합니다

    왜냐면 DTO는 데이터만 왔다갔다 하는 것이기 때문에 실용적인 관점에서 롬복을 막 써도 괜찮습니다.

    </aside>

회원 수정 API - 서비스

이제 수정하는 로직을 만들어봅시다.

수정할 때는 가급적 변경 감지를 사용하는게 좋습니다.

memberService 에서 변경 감지를 사용하도록 update 메소드를 만듭니다.

변경 감지 참고

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    **@Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
    }**
}

최초에는 영속성 컨텍스트에 Memeber 정보가 없으니까 데이터베이스에서 member를 찾아오면서 영속성 상태가 됩니다.

영속 상태의 Member의 이름을 바꿔주면 Transactional 애노테이션에 의해서 Transactional 관련된 AOP가 끝나는 시점에 영속성 컨텍스트를 플러시 하고 데이터베이스에 트랜잭션 커밋이 됩니다.

<aside> ❗ 커맨드와 쿼리를 분리?


참고1, 참고2

사실 그냥 member를 리턴해서 update에서 반환해도 됩니다.

@Transactional
public Member update(Long id, String name) {
    Member member = memberRepository.findOne(id);
    member.setName(name);
		return member;
}

하지만 커맨드와 쿼리를 철저하게 분리하는 정책이 있습니다. 이렇게 하면 update를 하면서 member를 쿼리하는 꼴이 되어버립니다.

update는 엔티티를 쫙 바꾸는 변경성 메서드입니다. 그런데 여기서 id를 가지고 member를 또 조회하는 꼴이 되어버린다는 것이죠.

즉, 커맨드와 쿼리가 같이 있는 꼴이 되어버리는 것입니다.

그래서 update를 하면 그대로 끝내버리거나 id 정도만 반환을 합니다.

그리고 조회 쿼리를 별도로 처리합니다.

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

보통 PK 하나 찍어서 조회하는 것은 트래픽이 많은 api가 아니면 이슈가 안되기 때문에 이런 스타일로 코드를 짜는게 좋습니다.

웹 애플리케이션에서 성능에 영향을 주는 부분은 대부분 복잡한 리스트를 조회할 때 발생하고, 하나의 데이터를 PK 기반으로 조회할 때는 전체를 보면 성능에 주는 영향이 미미합니다.

이렇게 하면 유지보수성이 많이 증대됩니다.

물론 개발하는 코드 분량이 작을 때는 이런 부분들이 크게 부각되지 않는데, 개발하는 코드 분량이 많고, 복잡할 수 록, 이런식으로 나누어 설계하면 관심사가 분리되어서 코드를 유지보수하기 쉽습니다.

하지만 이것이 딱 정답은 아니고, 생각하신 것 처럼 조회를 한번 더 해야 하기 때문에 상황에 따라 트레이드 오프가 있습니다.

</aside>