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<>();
}
@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스팩은 변하지 않는다.
@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 스타일에 맞다.
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로 변환해서 반환한다.
기본적으로 실무에서는 Entity를 API스펙이 노출하면 안된다!
Entity <-> DTO <-> View(Client) 의 형태로 통신할 수 있도록 하자.
추가로 API를 위해 만든 컬랙션을 직접 반환하면 향후 API스팩을 변경하기 어렵다.
이런 경우 별도의 Result 클래스를 만들어 해당 클래스를 반환하도록 해주면 좋다.
@Data
@AllArgsConstructor
static class Result<T> {
private int count; // API에서 추가적으로 원하는 정보
private T data; //반환 할 데이터
}
이렇게 별도의 Result 클래스를 만들면 응답을 확장 할 수 있는 유연성이 증가한다.
추가 정보가 필요하면 멤버 변수만 추가하면 끝.
Result 클래스를 사용한 반환 결과 예시.