API 개발 기본
회원 등록 API
•
Entity를 API request/response에 바로사용하는 것은 위험하다. (외부 노출 지양)
◦
Entity의 스펙이 변경 → API의 스펙 변경과 직결되는 문제 발생
◦
Entity는 어플리케이션 전체에 많은 영향도를 가지고 있기 때문에, API 스펙에 맞는 DTO를 별도로 선언하여 사용해야한다!
◦
Entity를 Parameter/return값으로 사용하지마라!
•
DTO를 만들어서 Entity를 Mapping, DTO를 Controller에서 사용 → 안정적인 시스템 운용 가능
◦
API 스펙에 맞는 Validation 가능 → 유지보수 편리
◦
DTO를 통해 API 스펙 파악이 바로 가능
◦
Entity <> Presentation Layer(validation 등) Logic을 분리
◦
Entity 스펙이 변경되어도 API에 Side Effect가 없음
// Entity
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
// 별도의 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);
}
Java
복사
회원 수정 API
•
수정 - PUT
◦
멱등성
▪
같은 것을 여러 번 호출해도 결과가 동일함
•
command <> query logic 분리
◦
유지보수 용이
// Controller
@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); // command <> query의 분리
return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}
// Serivce
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name); // 변경감지 (dirty Checking)
}
Java
복사
회원 조회 API
•
V1
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
Java
복사
→ 응답에 Orders에 대한 정보도 포함되어서 옴. 하지만 단순 회원 정보만 받고 싶다면?
→ Membe Entity에 @JsonIgnore 어노테이션을 추가하여 해당 정보는 무시하도록 한다.
◦
API스펙에 따라 다양한 변수(컬럼)를 포함하거나, 무시하는 경우 발생. 이럴 때마다 Entity관리를 어떻게 진행할 것인가!?
▪
응답 스펙을 맞추기 위해 로직이 추가된다. (@JsonIgnore, 별도의 뷰 로직 등등)
◦
실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
◦
기본적으로 엔티티의 모든 값이 노출된다.
◦
Entity에 Presentation Layer의 기능도 포함되어 좋지 않은 설계가 됨
▪
Application을 수정하기 어려운 형태가 됨
◦
마찬가지로 Entity를 변경하면, API스펙이 변경되는 문제 (장애 유발 요인)
▪
Entity를 직접 반환하지 말자!
•
V2
@GetMapping("/api/v2/members")
public Result memberV2() {
List<Member> findMembers = memberService.findMembers();
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName()))
.collect(Collectors.toList());
return new Result(collect);
}
@Data
@AllArgsConstructor
static class MemberDto {
private String name;
}
@Data
@AllArgsConstructor
static class Result<T> {
//private int count; //field 추가에도 유연한 변경 가능
private T data;
}
Java
복사
→ Entity를 Dto로 변환하여, 변경에 유연하게 대비할 수 있도록 Result 객체로 감싼 후 응답하도록 한다.
◦
field가 추가된 경우에도 API스펙, DTO를 바꾸지 않아도 감싸는 Result 객체에 field만 추가하면 되기 떄문!
▪
API 스펙 : DTO = 1: 1
▪
변경에 유연한 설계를 가져갈 수 있다.
API request/response에는 반드시 DTO를 사용해라. Entity를 외부에 노출하지 마라!