티스토리 뷰

API 구현

회원 생성 API 구현

  • 회원 객체 생성 api
  • POST ("api/v2/members")
{
    "name": "최똥꼬",
    "email": "dnstlr2933@naver.com7",
    "address": {
        "zipcode" : "139xx",
        "streetAdr": "안양시 동안구 동편로 xxx",
        "detailAdr": "xxx-xxx"
        }
}

 

Member 객체

@Entity
@Getter @Setter
public class Member {

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

	private String name;

	private String email;

	@Embedded
	private Address address;

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

 

DTO를 사용하지 않고 Entity 그대로 api를 통해 구현하는 경우

@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
	Long id = memberService.join(member);
	return new CreateMemberResponse(id);
}

등록

  • 요청 값으로 Member 엔티티를 직접 받는다.

문제점

  • 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
    1. 널값을 방어하기 위해 @NotNull 등을 달아줘야 하는 등 Entity에 대해 추가 제약이 붙는다
    2. API마다 필수 요소들이 다를수 있는데도 일률적으로 제한해야 한다.
    3. 엔티티에 API 검증을 위한 로직이 들어간다. (@NotEmpty, @NotNull)
  • 엔티티가 변경되면 API 스펙이 같이 변한다.
    • 즉 api가 Entity에 의존적이게 된다.

결론

  • API 요청 스펙에 맞추어 별도의 DTO를 통해서 받는다.

DTO를 사용하여 api와 Entity를 구분하는 경우

@PostMapping("api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {

	Member member = new Member();
	member.setName(request.getName());
	member.setEmail(request.getEmail());
	member.setAddress(request.getAddress());

	Long id = memberService.join(member);
	return new CreateMemberResponse(id);
}

등록

  • 요청 값으로 Member 엔티티 대신에 별도의 DTO를 받는다

회원 생성을 위한 DTO

@Data
static class CreateMemberRequest {
	private String name;
	private String email;
	private Address address;
}

응답

@Data
@AllArgsConstructor
static class CreateMemberResponse {
	private Long id;
}

회원 수정 API 구현

  • 회원 이름 수정 api
  • PATCH ("api/v2/members/{id}")
{
	"name": "운식이짱"
}

 

회원 이름을 수정하기 위한 DTO를 통해서 api를 받아서 처리

@PatchMapping("api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2 (
			@PathVariable("id") Long id,
			@RequestBody @Valid UpdateMemberRequest request)
{
		memberService.update(id, request.getName());
		Member findMember = memberService.findMember(id);
		return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}

회원 이름 변경용 DTO (이름만 받아서 변경)

@Data
static class UpdateMemberRequest{ 
	private String name; 
}

응답

@Data
@AllArgsConstructor
static class UpdateMemberResponse {
	private Long id; 
	private String name; 
}

회원 조회 API 구현

  • GET ("api/v2/members")

회원 목록을 바로 반환시 문제점

  • 기본적으로 Entity의 모든값이 노출되므로 매우 위험하다.
  • 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
    • 값을 내보내지 않기 위한 @JsonIgnore 등..
  • 하나의 엔티티에 연결된 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 매우 어렵다.
  • 엔티티가 변경되면 API 스펙이 변한다.
    • 회원의 이름을 name: "이름"으로 가져왔는데, entity의 name이 username으로 변경되어서 username으로 가져오게 되면 전체적인 api구조가 꼬이게 된다.
  • 컬렉션을 직접 반환하면 항후 API 스펙을 변경하기 어렵다.
    • 따라서 별도의 반환을 위한 Result 클래스를 생성해 해당 클래스를 값으로 갖도록해서 api의 유연한 확장성을 유지하자
    • 예를 들어 회원 목록의 개수를 추가한다는 등 수정에 용이해진다.

결론

  • API 응답 양식에 맞추어 별도의 DTO를 생성해서 반환하자

회원 조회 용 DTO를 통해서 api 처리

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

	return new Result<>(collect);
}

 

회원 리스트를 배열로 바로 보내지 않고 data로 포함해서 보내기 위한 응답 객체

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

 

회원 조회용 DTO (이름과 이메일만 반환)

@Data
@AllArgsConstructor
static class MemberDto {
	private String name;
	private String email;
}
반응형
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday