Yebali

Spring을 이용한 API개발 - 기본 본문

Spring

Spring을 이용한 API개발 - 기본

예발이 2021. 10. 11. 19:29

Spring을 이용해 API를 개발할 때 참고하기 적절 한 글.

예시에서 아래 Member라는 Entity를 사용한다.

@Data
@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @JsonIgnore 
    @OneToMany(mappedBy = "member")
    private List<Order> order = new ArrayList<>();
}

등록(Post) API

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

@Data
static class CreateMemberRequest {
    @NotEmpty
    private String name;
}

요청 파라미터를 'Member' Entity에 직접 매핑하지 않고, 임의의 DTO(CreateMemberRequest)에 매핑한다.
Entity와 API스펙을 명확히 분리할 수 있으며 Entity가 변해도 API스펙은 변하지 않는다.

수정(Post/Put) 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
class UpdateMemberResponse {
    private Long id;
    private String name;
}

등록 API와 마찬가지로 임의의 DTO(UpdateMemberResponse)에 요청 파라미터를 매핑하는 것이 좋다.

전체를 업데이트 할 때에는 PUT을 사용하고,
부분 업데이트를 할 때에는 PATCH나 POST를 사용하는 것이 REST 스타일에 더 맞다.

조회(Get) API

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
class Result<T> {
	private int count;
	private T data;
}
 
@Data
@AllArgsConstructor
class MemberDto {
	private String name;
}

조회 시 Entity를 직접 반환하는 것이 아닌 DTO로 변환해서 반환한다.

API 개발에서 Entity를 직접 사용했을 때 문제

  1. Entity에 API 검증 로직이 추가되어야 한다. (ex. @NotEmpty)
  2. 실무에서는 Entity를 위한 다양한 API가 만들어지는데, 하나의 Entity에 모든 API의 요구사항들을 반영하기는 어렵다.
  3. Entity가 변경되면 API 스펙이 변한다.
기본적으로 실무에서는 Entity를 API스펙이 노출하면 안 된다.
Entity <-> DTO <-> View(Client)의 형태로 통신할 수 있도록 해야 한다.

조회에서 굳이 Result 클래스를 만든 이유

추가로 API를 위해 만든 컬랙션을 직접 반환하면 향후 API스펙을 변경하기 어렵다.
이런 경우 별도의 Result 클래스를 만들어 해당 클래스를 반환하도록 해주면 좋다.

@Data
@AllArgsConstructor
static class Result<T> {
    private int count; // API에서 추가적으로 원하는 정보
    private T data;  //반환 할 데이터
}

이렇게 별도의 Result 클래스를 만들면 응답을 확장할 수 있는 유연성이 증가한다.
추가 정보가 필요하면 멤버 변수만 추가하면 된다.

Result 클래스를 사용한 반환 결과 예시.