한이음 프로젝트

Entity <-> DTO 빌더 로직은 어디에 넣어야할까??

딤섬뮨 2022. 11. 18. 14:26
728x90

객체지향을 신경 쓰며 개발하다 보니, get을 쓰게 되면 이게 맞나.. 고민하고 이 메서드가 이 객체에게 책임이 있는가.. 쓰이는 게 맞나.. 고민하게 된다. 지금 닥친 문제는 Builder에서 나타난다.

고민사항

우리는 서비스와 controller에 Entity를 주고 받지 않고 , DTO를 사용한다. 그렇다면 보통 Entity를 DTO로 변환하는 과정이 추가되는데 이 과정에서 보통 Builder를 사용한다.
다음과 같은 세 가지 버전이 있을 것 같다.

  • 서비스에 Builder가 나타난 ver

서비스 안에 builder가 보이는 로직인데 , 이는 중복 코드도 많아지고, service에 트랜잭션 순서만 드러나야지 이렇게 메서드가 들어가는 건 지양해야 할 듯싶어서 객체에게 메서드로 빼고 싶었다.

@Transactional
public void createCareer(CareerCreateRequest request) {
    Career requestCareer = Career.builder()
        .memberId(request.getMemberId())
        .name(request.getName())
        .period(request.getPeriod())
        .selfDescription(request.getSelfDescription())
        .build();
    careerRepository.save(requestCareer);
}

그러면 Entity에게 builder를 넣어줘야하나,..DTO에게 넣어줘야 나?

  • Entity안에서 들어온 DTO를 Entitiy로 변환
public static Career toCareer(CareerCreateRequest request){
    return Career.builder()
        .memberId(request.getMemberId())
        .name(request.getName())
        .period(request.getPeriod())
        .selfDescription(request.getSelfDescription())
        .build();
}
  • DTO안에서 Entity로 변환
public Career toCareer() {
    return Career.builder()
        .memberId(memberId)
        .name(name)
        .email(email)
        .period(period).
        selfDescription(selfDescription)
        .build();
}


다음과 같은 두가지 방식이 고민되는 상황이 아닐까 싶다.

일단 내 마음은 DTO안에서 Entity로 변화하는 방식이다. getter를 지양하고 싶은 이 철저한 마음이랄까..

구글링

[1] 엔티티는 DTO의 영향을 받으면 안된다.

주니어 개발자의 클린 아키텍처 맛보기 | 우아한 형제들 기술 블로그 (woowahan.com)

간단히 알아보는 클린 아키텍처
세부적인 차이는 있어도 공통적인 목표는 계층을 분리하여 관심사를 분리하는 것
이런 아키텍처가 동작하기 위해서는 의존성 규칙을 지켜야 한다.
의존성 규칙은 모든 소스코드 의존성은 반드시 외부에서 내부로, 고수준 정책을 향해야 한다고 말한다.
즉 업무의 업무 로직을 담당하는 코드들이 Web 또는 DB와 같은 구체적인 세부사항에 의존하면 안 된다.
이를 통해 고수준 정책(DB나 웹 같은 업무 로직)은 세부사항들(저수준 정책)의 변경에 영향을 받지 않도록 해야 한다.

Entity가 DTO를 직접 참조하는 문제
가끔씩 코드를 보면 Entity를 생성할 때 Controller로부터 생성된 Request(DTO) 객체를 이용하는 경우를 보았습니다.

public class Entity {
    ...
    public static Entity of(Request request) {
        ...
    }
}

이러한 코드를 보고 Web으로 오는 데이터를 직접 참조하지 않고
DTO를 통해 다른 경계로 전달하기 때문에 규칙을 지켰다고 생각할 수 있습니다.
하지만 제가 생각하기에 의존성 규칙의 목적은 저수준의 개념이 고수준의 개념에 영향을 주지 않게 하기 위함이라고 생각합니다.

위의 코드와 같이 Entity가 직접 DTO를 참조하게 된다면 DTO의 변경에 의해 Entity도 변경될 수 있다는 것을 알려줍니다. 따라서 Entity를 생성하는 UseCase 계층에서 DTO의 데이터를 꺼내서 Entity를 생성하는 것이 DTO의 변경에 의해 Entity가 변경할 가능성을 낮출 수 있다고 생각합니다.

  1. Entity, DTO를 전달하는 방법
    • 저는 일반적으로 업무와 관련된 인터페이스를 사용하면 Entity 자체를 넘기거나 DTO를 파라미터로 사용합니다. 이러한 이유는 업무 로직의 세부사항은 언제든지 변경될 수 있기 때문입니다. Entity나 DTO를 사용하면 이러한 변경이 클라이언트에 영향을 미치는 것을 줄일 수 있습니다.
      //DTO를 사용할 때 팩토리 메서드를 사용하여 Entity를 받게 구현한다면,
      //필요한 데이터가 추가되더라도 클라이언트의 변경을 줄일 수 있습니다.
      public class DTO {
      public static ofEntity(Entity entity) {
      ...
      }
      }
    • 보통 Enitity보다 DTO를 사용하는 경우는 인터페이스가 특정 도메인에 종속되는 것이 아니라 범용적일 때 사용하고 있습니다.
  2. 필요한 데이터를 각각 넘기는 방법
    • 모든 인터페이스에 Entity나 DTO를 넘기는 것은 과할 수 있습니다. 제가 생각하기에 불변에 가까운 유틸 성 인터페이스는 필요한 인자를 파라미터로 받으면 오히려 명확할 수 있다고 생각합니다.
      public interface DateUtil {
      long between(Date from, Date to); 
      }

[2] Entity에서 변경해야 한다.

OKKY에서 글을 봤는데, 캡처만 해놓고 주소를 잃어버렸다

여기서는 DTO는 pure 한 데이터를 소유한 객체라 entity 변환 로직이 들어가는 순간 의미가 변질되지 않나, DTO의 의미만 보아도(Data Transfer Oject) 레이어 간 데이터 전달을 위한 오브젝트에 엔티티 변환 역할이 있다고 보기에는 어렵다.
객체 지향의 의미를 살린다면 transfer object 또는 mapper클래스에서 변환 로직을 위임하는 게 나을듯하다.

이 글 들 말고도 여러 글에서 다 의견이 갈린다. 각자 프로젝트에 맞게 유연하게 설계하는 수밖에 없을 듯하다.
나는 getter사용의 지양과, Entity의 영향을 주고 싶지 않은 관점에서 , DTO안에서 Entity를 만드는 방향을 택하고 싶다. 물론 다양한 의견을 환영한다!!!!

 

728x90