스프링

SpringBoot로 API 개발 시 최적화에 대해서 - 1

khw7385 2024. 9. 6. 00:32

등록, 생성, 추가 관련 API 

보통 POST Method 를 이용하여 요청을 처리한다.

@PostMapping("/api/v1/members")
public CreateMemberResponse saveMember(@RequestBody @Valid Member member){
	...
}

문제:  엔티티를 RequestBody 에 직접 매핑

이유:

  • 프레젠테이션을 위한 로직에 엔티티가 들어간 부분은 유지 보수에 좋지 않다.
  • RequestBody 로 매핑되는 파라미터는 검증을 할 수 있다. 근데  엔티티를 RequestBody로 직접 매핑시켜버리면 엔티티에 검증을 위한 로직이 추가된다. 예를 들어 @NotNull 이 엔티티 필드에 추가된다는 것이다. 
  • API 는 수십~수백개가 존재할 텐데 그 때마다 API 의 요구사항을 엔티티에 반영하는 것은 무리이다.
  • 엔티티가 변경되면 API 스펙이 변경될 수 있는 문제가 존재한다.

 

@PostMapping("/api/v2/members")
public CreateMemberResponse saveMember(@RequestBody @Valid CreateMemberRequest reqeust){
	...
}

해결: 엔티티 대신 DTO 클래스를 RequestBody에 매핑

 

수정 관련 API

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

수정 관련 API 에 대해서 어떤 Http Method 를 사용하는 지가 문제일 수 있다.

1. PUT vs POST : 멱득성

  • POST 는 같은 요청을 여러 번 보냈을 때 다른 결과를 반환한다.
  • PUT 은 같은 요청에 대해서 항상 같은 결과를 반환한다.

2. PUT vs PATCH : 전체와 부분

  • PUT 은 전체를 업데이트할 때 사용한다.
  • PATCH 는 부분을 업데이트할 때 사용한다.
  • 하지만 이 둘을 명확하게 구분한 것은 어려운 일이다. 예시를 들어 보자. User 엔티티에 idname 만 있었다고 가정을 해보자. 이름을 변경하기 위해서는 API는 PUT 으로 만들어야 한다. 근데 시간이 흘러 age 라는 필드가 user 에 추가되었다. 원래 name 만 변경하는 API 는 PATCH 로 변경되어야 한다. 이렇게 되면 API 의 method 를 변경해야 하는데 이런 작업이 쉬운 것은 아니다.

서비스 계층의 수정 메소드에서 반환 값 설정없이 void 로 하는 이유

  • 목적에 맞지 않다. 수정하는 서비스에 대해서는 해당 객체를 반환하는 것은 조회도 포함하게 되는데 수정과 조회를 함게 하는 형태가 된다. 이런 형태는 유지 보수를 어렵게 한다.
memberService.update(id, request.getName());
Member findMember = memberService.findOne(id);
  • 수정 관련 API 에서 응답 값을 보내줘야 하므로 서비스 계층의 조회 메소드를 호출하여 값을 반환해 준다.

조회 관련 API

Get Method를 사용하여 요청을 매핑한다.

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

문제: 응답 값으로 엔티티를 직접 외부에 노출한다.

이유:

  • RequsetBody 로 엔티티를 사용할 때와 같은 문제가 발생한다.
  • 엔티티의 모든 값이 노출된다. -> 필요한 값만 노출되어야 한다.
  • 추가로 컬렉션을 직접 반환할 시 향후 API 스펙을 변경하기가 어렵다.
@GetMappiing("/api/v2/members")
public Result membersV2(){
	List<Member> findMembers = MemberService.findMembers();
	List<MemberDto> collect = findMembers.stream()
			.map(m -> new MemberDto(m.getName()))
			.collect(Collectors.toList());
			
	return new Result(collect);
}

해결: 별도의 응답 DTO 클래스를 생성하여 Result 객체에 담아서 반환한다.

  • 엔티티 변경 시 API 스펙 변경이 없다.
  • 엔티티가 노출되지 않는다.
  • Result 클래스를 감싸서 향 후 필요한 필드를 추가할 수 있다.

참고자료

인프런 이영한 - 스프링 부트와 JPA 활용 2