Spring BootとJPAを使用した2-TIL(1)


[参考講座]実戦!Spring BootとJPAによる2-APIの開発とパフォーマンスの最適化

💡 API開発基盤


会員登録API

@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 {
        @NotEmpty
        private String name;
    }

    @Data
    static class CreateMemberResponse {
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }
}

V 1エンティティをRequest Bodyに直接マッピング

  • 問題
  • エンティティは、プレゼンテーションレイヤの論理を追加します.
  • エンティティは、API検証のための論理(@NotEmptyなど)
  • を含む
  • 実務では、会員エンティティのために作成されるAPIは多様であり、1つのエンティティに各APIのすべての要求要件を含めることは困難である.
  • エンティティが変更されると、API仕様は
  • に変更されます.
  • 結論
  • API要求の仕様に従って、個別のDTOパラメータが受け入れられる.
  • DTOをV 2エンティティではなくRequest Bodyにマッピング


    要求値として、メンバーエンティティではなく個別のDTOが受信されます.
        @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をメンバーエンティティではなくRequest Bodyにマッピングします.
  • エンティティとプレゼンテーションレイヤの論理を分離できます.
  • エンティティは、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;
        }
    MemberService
        @Transactional
        public void update(Long id, String name) {
            Member member = memberRepository.findOne(id);
            member.setName(name);
        }

    ▼▼▼会員照会API


    会員照会V 1:エンティティを応答値の外に直接暴露する
    public class MemberApiController {
    
        private final MemberService memberService;
    
        @GetMapping("/api/v1/members")
        public List<Member> membersV1() {
            return memberService.findMembers();
        }
    }
  • 問題
  • エンティティは、プレゼンテーションレイヤの論理を追加します.
  • デフォルトでは、エンティティのすべての値が露出します.
  • 応答specに合わせるために論理を追加した.(@JosonIgnore等)
  • の実践では、同じエンティティのAPIは、用途によって異なり、1つのエンティティに各APIに対するプレゼンテーション応答ロジックを含めることは困難である.
  • エンティティが変更されると、API仕様が変更されます.
  • さらに
  • ローカル集合を直接返却すると,今後のAPI規格の変更は困難である.(個別のResultクラスを作成して解決)
  • 結論
  • API応答specに従って個別のDTOが返される.
  • 会員照会V 2:エンティティではなく独立したDTOを応答値として使用
        @GetMapping("/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.size(), collect);
        }
    
        @Data
        @AllArgsConstructor
        static class Result<T> {
            private int count;
            private T data;
        }
    
        @Data
        @AllArgsConstructor
        static class MemberDto {
            private String name;
        }
  • エンティティをDTOに戻します.
  • エンティティが変更されても、API仕様は変更されません.
  • Resultクラスのローカルセットを追加して、将来必要なフィールドを追加します.