SpringBoot + JPA 환경에서 RestAPI 생성시 고려해야할 것들
회사에서 API 를 직접 생성해서 제공하고, 외부 API 를 가져와서 데이터를 파싱하는건 아주 흔한일이다.
처음에 지식이 없는 상태로 구축을 진행하게 되면, 유지보수 때 아주 힘들 수 있으므로 처음 개발을 진행할 때 항상 염두에 두어야 하는것들을 정리했다.
1. DTO 를 따로 만들자
Entity 는 여러군데에서 쓰기 때문에 변화가 많다. 따라서 외부에서 쓸 때 변화가 없는 request / response dto 를 따로 만들어서 작업하도록 하자. 실제로 유지보수할 때, 응답에 포함되는 responseBody 항목이 바뀌면 관련된 가이드 문서부터 시작해서 내가 제공한 사이트에서 나는 오류발생시 대응등.... 불편한점이 한두가지가 아니다. 그러므로 반드시 규격화된 DTO 를 생성하여 Entity 가 변경되더라도 응답규격은 일정하게 유지해야한다.
2. @JsonIgnore 을 사용하면 사용되는 필드는 노출 되지 않는다.
@JsonIgnore 을 사용하면 내가 노출을 원하지 않는 필드를 설정할 수 있다.
3. 컬렉션 리턴시 규격화된 Result 를 생성해서 사용하자
단순한 엔티티 반환시에는 크게 문제가 없지만, 반환값에 컬렉션이 포함될 경우 기본 json 형식이 깨지는 문제가 있을 수 있다. 리턴시 리턴할 규격을 정한 DTO 를 선언하고 감싸서 보내게 되면 이런 문제를 해결 할 수 있다.
예를 들면 다음과 같이 구현할 수 있다.
@GetMapping("/api/v2/members")
public Result memberV2() {
List<Member> findMembers = memberService.findMembers();
Collection<Object> collect = findMembers.stream().map( m -> new MemberDTO(m.getName()))
.collect(Collectors.toList());
return new Result(collect.size(), collect);
}
@Data
@AllArgsConstructor
static class Result<T> {
private int count;
private T data;
}
4. 양방향 연관관계가 있을 경우, 한쪽에 대해서는 @JsonIgnore 를 추가해주도록 하자
양방향 연관관계에서 주의해야할 점이 바로 무한루프의 발생가능성이다. ( 연관관계에 무한루프에 대해서는 다음 포스팅을 참고 ) 따라서 양방향 연관관계가 있을 경우 따라서 한쪽과의 연관관계를 끊어줘야하기 때문에 @JsonIgnore 을 사용하여 노출되지 않도록 하는것이 필요하다.
5. 지연 로딩 문제를 해결 할 때는 Hibernate5Module 를 사용하자
지연로딩을 사용할 경우, JSON 형식으로 리턴시 문제가 생길 수 있다. 지연로딩설정을 해두면, 연관관계가 있는 객체에 대해 진짜 엔티티 객체가아닌 프록시 객체 상태로 호출이 되어 InvalidDefinitionException 을 발생 시킨다.
이럴 때 Hibernate5Module 를 사용하면 문제를 해결할 수 있다.
사용방법은 간단한데, build.gradle 파일에 아래 내용을 추가해주면 된다. 그러면 프록시객체는 제외하고 리턴하게 되어 InvalidDefinitionException 이 발생하지 않는다.
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta'
6. repository 에서 DTO 로 할지 Entity 로 할지에 대한 문제 결정
레파지토리에서 바로 DTO 로 받아올지, 우선 Entity 로 받아올지는 사실 개발자의 선택이다. 성능차이는 크지 않기 때문에, 어떻게 받아오던지보다 서비스단에서 DTO 로 변환하여 결과적으로 리턴되는 객체에 대해서 DTO 로 구성하는것에 초점을 맞추는게 좋다.
~ 결과적으로는 사실 레파지토리에선 엔티티 받아온다음에 서비스단에서 디티오로 바꿔주는게 일순위임
7. 컬렉션을 DTO 로 반환할 경우 컬렉션에 연관된 엔티티의 의존도를 제거하자
만약 A 라는 엔티티 안에 B 엔티티가 리스트형태로 들어가 있다면, 리턴시 B 객체 자체도 DTO 로 변경해줘야 한다.
즉 리턴되는 DTO 의 형태는 'A 엔티티에 대한 response DTO' 안에 ' B 엔티티 DTO 에대한 리스트' 가 포함되어야 한다.
class ADto {
List<BDto> blist;
...
}
참고 및 출처 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화