REST API 구현, 회원 API

dropKick·2020년 8월 25일
0

회원 가입 API

사용 어노테이션과 기술

@Data
@RequestBody
@ResponseBody

RequestBody의 엔티티 바인딩 방식과 DTO

엔티티 바인딩 방식
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
  • Request 데이터를 엔티티에 그대로 바인딩
    API 스펙이 그대로 필드에 바인딩 되기 때문에 엔티티 필드 변경 시 API도 변경되어 버리는 문제점이 있다
@Data
    static class CreateMemberRequest { // DTO
        private String name;
        private Address address;
    }
    
@PostMapping("/api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
        Member member = new Member();
        member.setName(request.getName());
        member.setAddress(request.getAddress());

        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
  • 따라서 별도의 DTO를 통해 분리
    엔티티가 수정 되더라도 DTO를 통해 바인딩되기 때문에 API 스펙이 변경 되거나 엔티티가 노출될 필요가 없다.

NULL 방지

 "defaultMessage": "반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.",
            "objectName": "member",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotEmpty"
  • Vaildation 기능을 이용하여 어노테이션 매핑
    그럼 에러는 아니지만 지정된 에러코드와 데이터를 반환 해줌

임베딩 데이터 추가

public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
        Member member = new Member();
        member.setName(request.getName());
        member.setAddress(request.getAddress());

임베딩 데이터인 Address도 받아올 수 있게 구성

{
    "name": "userA",
    "address": {
    "street":"seoul",
    "city":"seoul",
    "zipcode":"123-45"
    }
}

JSON 계층을 구성하고 전송하면 잘 들어간다.

insert into member (city, street, zipcode, name, member_id) values (?, ?, ?, ?, ?)
insert into member (city, street, zipcode, name, member_id) values ('seoul', 'seoul', '123-45', 'userA', 1);

회원 수정

ddl-auto : none 시 테이블을 못찾는 문제
creater가 아니라 기본적으로 테이블을 만들어주지 않아서 발생하는 것 같은데
따로 스크립트로 만들어줘야 하는듯?

엔티티 바인딩 방식의 문제점

@GetMapping("/api/v1/members")
    public List<Member> membersV1() {
        return memberService.findMembers();
    }

이렇게 엔티티 자체를 List로 반환 시 아주 쉽게 회원 정보를 보낼 수 있지만

[
    {
        "id": 1,
        "name": "userA",
        "address": {
            "city": "seoul",
            "street": "11111",
            "zipcode": "123-45"
        },
        "orders": []
    },
    {
        "id": 2,
        "name": "userB",
        "address": {
            "city": "seoul",
            "street": "11111",
            "zipcode": "123-45"
        },
        "orders": []
    }
]

불필요한 Order라는 엔티티 정보까지 노출이 되고, @JsonIgnore를 통해서 숨기더라도
엔티티에 뷰 계층의 로직이 추가되는 양방향 연관 관계가 생겨버리는 문제점이 있다.

DTO Wrapping

DTO
    @Data
    @AllArgsConstructor
    private static class Result<T> {
        private T data;
    }

    @Data
    @AllArgsConstructor
    private static class MemberDto {
        private String name;
        private Address address;
    }

DTO와 DTO가 받을 데이터 감싸는 클래스를 만든다.

@GetMapping("api/v2/members")
    public Result<List<MemberDto>> memberV2() {
        List<Member> findMembers = memberService.findMembers();
        List<MemberDto> collect = findMembers.stream()
                .map(m -> new MemberDto(m.getName(), m.getAddress()))
                .collect(Collectors.toList());

        return new Result<>(collect);
    }

이렇게 되면 엔티티에서 데이터는 데이터대로 가져온 뒤 그 데이터를 DTO를 통해 랩핑하기 때문에 엔티티도 노출되지 않고 Data라는 타입으로 계층 구조를 생성할 수 있다.

{
    "새로운 데이터" : "새 데이터",
    "data": [ // Wrapped 
        {
            "name": "userA",
            "address": {
                "city": "seoul",
                "street": "11111",
                "zipcode": "123-45"
            }
        }
    ]
}

기존과 다르게 data라는 이름으로 계층이 분리되었기 때문에 엔티티가 수정되어 새 데이터가 생기더라도 API 스펙 자체는 변하지 않는다.
그리고 엔티티가 수정 되더라도 컴파일 시점에서 모든 에러가 잡히기때문에 수정이 용이하고
엔티티와 DTO가 1:1 연관 관계를 가진다.

궁금증

Inner Class와 Static

    @Data
    static class CreateMemberRequest { // 반드시 있어야 함 
        private String name;
    }

    @Data
    static class CreateMemberResponse { // 없어도 됨
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }
  • Response의 경우 클래스 내부에서 객체의 선언과 할당이 이루어진다.
  • Request의 경우 외부에서 객체가 들어오기 때문에 클래스 로딩 시점에 접근할 수 있어야 한다.
  • 외부 클래스로 뽑아도 되나 한정된 상황의 한정된 클래스라면 응집도를 강화시키는 방법이다.

0개의 댓글