티스토리 뷰

반응형

Query DSL logo
QueryDSL

 

회사에서는 도메인 주도 설계인 DDD(Domain Driven Design) 패턴을 사용하여 개발을 진행하고 있습니다. 이유는 도메인 패키지의 경우 메인 패키지이면서 다른 모듈 패키지에서도 사용하기 때문이기도 하고, RDBMS를 사용하는 회사의 시스템 상 다른 같은 Database Schema 를 사용하기 때문입니다. 

그래서 DDD 사용하기 위해서는 주의해야 하는 부분이 있습니다. 

 

1. 도메인 패키지 내에 엔티티 클래스와 리포지토리를 선언한다.

Cash 라고 하는 엔티티가 있다고 가정해봅니다. Cash 폴더 내에 Cash와 관련된 엔티티 패키지는 도메인 패키지 내에 있다고 하지만 DTO, 서비스, 리포지토리는 어디에 위치하여 있는것이 효율적일까요? 

 

@Entity
@Getter
@NoArgsConstructor
public class Cash {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "cash_id")
    private Long id;
    
    ...
    
}

정해진 것은 없지만 기본적으로 데이터베이스에 직접 접근이 가능한 Cash, CashRepository 등은 도메인 주도 설계를 진행할 때 공통 패키지에 있는 것을 선호하고, CashService의 서비스 레벨단부터는 각 모듈에 배치된 것을 사용하는 것을 선호하는 바입니다. 왜 그렇게 선언을 할까요? 

 

바로 컴파일을 진행하면서 하위 모듈들에서 각각 서비스 레이어는 사용을 할 수도, 또는 그렇지 않을 수도 있기 때문입니다. 공통 모듈은 모든 모듈에서 공통적으로 사용할 것이지만 서비스는 공통적으로 사용하는 것이 아닌 각 모듈별 특징별로 사용하는 것에 초점을 맞추기 때문입니다. 관리자 모듈에서는 관리자 전용 서비스만을 사용하고, API 모듈에서는 API 전용 서비스만을 사용하기 위함입니다.

 

2. QClass 를 만드는 방법과 위치 

QClass는 Repository의 확장 레벨인 QueryDSL을 사용할 때 만들어지는 특수한 결합도를 가진 엔티티 클래스입니다. DTO 레벨로 여겨지는 QClass는 `@QueryProjection`이라는 어노테이션을 기반으로 생성이 됩니다. 그러면 이 녀석의 경우 어디에 위치한 것이 더 효율적이고 성능이 우수해지는 것일까요?

결론부터 말씀드리면 QClass를 생성하는 `@QueryProjection` 어노테이션은 공통 모듈 내에 위치해야 합니다. 여러 번 실험을 해보았지만 공통 모듈 외부에서 QClass를 생성하기 위해 Gradle -> build -> querydslClasses 을 실행해보았지만 빈번히 에러가 나는 것을 확인할 수 있었습니다. 

 

3. QClass 를 잘 사용하는 방법

그럼 @QueryProjection을 사용하였다면 이는 그저 단순히 DTO모델로도 사용할 수 있지만, 공통된 쿼리에서 필요한 엔티티 데이터를 사용하는 경우 VO 모델로도 사용 가능합니다. 

Java DTO Verse VO
DTO vs VO

  • DTO : 데이터를 가져와 변형하여 사용 가능
  • VO : 엔티티 클래스가 가지고 있는 데이터 자체 

저의 경우 위와 같은 Cash 엔티티 클래스가 존재한다면, CashDTO 로 사용하기 이전 단계에서 사용할 수 있는 CashProjection 클래스를 하나 더 만들어서 사용하곤 합니다.

public class CashProjection {

    private Cash cash;
    private CashLog cashLog;
    
    @QueryProjection
    public CashProjection(Cash cash, CashLog cashLog) {
        this.cash = cash;
        this.cashLog = cashLog;
    }
}

 

그리고 이 클래스를 DTO 클래스에서 한 번 더 꼬아 사용합니다. 

public class CashLogDTO {

    private Long id;
    private Long price;
    private String comment;
    private int historyCount;
    
    public static CashLogDTO of(CashProjection projection) {
         Cash cash = projection.getCash();
         CashLog cashLog = projection.getCashLog();
         return CashLogDTO.builder()
                          .id(cash.getId())
                          .price(cash.getPrice())
                          .comment(cashLog.getComment())
                          .historyCount(cashLog.getCount())
                          .build();
    }
    
}

 

이렇게 되면 CashProjection 클래스를 CashLogDTO 뿐 아니라 다른 여러 클래스에서 Cash, CashLog 엔티티 데이터가 필요한 경우 여러 번 사용할 수 있는 재사용성을 높게 띤 클래스로 사용 가능합니다. 

반응형
댓글
공지사항