리팩터링하기 쉬운 코드를 만들 수 있는 가장 쉬운 방법은 잘 알려진, 디자인 패턴(팩터리 패턴, 파사드 패턴 등) 혹은 설계를 따르는 것이다. 테스트도 그렇다. 근데, 생각없이 짜면 안된다. 잘 알려진 레이어드 패턴을 떠올려보자.
Presentation → Application → Domain → Infra
내가 좋아하는 마틴 파울러 블로그, 단 요즘에는 ORM쓰기 때문에 Data Mapper는 빼도 됨.
bliki: PresentationDomainDataLayering
우리가 일반적으로 생각하는 레이어드 아키텍처이다. 하지만, 이 코드의 문제점이 뭘까??
DI를 안하면 코드가 어떻게 될까? Infra혹은 Domain에 구현체가 들어가고 난리 부르스를 출거다.
이것과 Testable이 무슨 상관? 고수준 모듈이 저수준 모듈을 의존하고 있다? 이거 안좋음.
저수준 모듈의 구현이 바뀌면 고수준 모듈에 전파가 된다. 즉, 저수준 모듈의 구현을 완료해야 고수준 모듈을 만들 수가 있다.
Application 레이어의 메서드 하나를 테스트로 짠다고 해보자. Repository 패턴에 ORM쓴다고 하면,
가장 쉽게 구현하는 방법은 JPARepository<T, ID>
인터페이스를 도메인 레이어에 집어넣겠지.
JPARepository 반환값이 Optional<Entity>
인데 Entity설계 없이 Application 레이어 개발 가능? 불가능.
DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함이다.
인터페이스와 구현 클래스는 그 결과일 뿐 인과관계를 착각해서는 안된다!!!
나는 JPA안쓰고 TypeOrm쓸거니까 TypeOrm repository가 되겠지. 주의점은 Nest.js가 DI를 interface로 못해주기 때문에 interface로 하고 싶으면 직접 module에다가 설정을 해줘야함(반자동까지는 가능).
애그리거트에 속한 객체는 동일한 라이프 사이클을 갖는다. 같이 생성되고 같이 제거된다.
같은 애그리거트 안에서만 transaction이 이루어져야 함
아니면 Service로 빼라.
상품과 리뷰를 예시로 들 때, 상품에 리뷰가 포함되니 같은 애그리거트로 착각할 수 있다.
하지만, 상품과 리뷰의 라이프 사이클이 다르다. 라이프 사이클이 비슷한지를 기준으로 경계선을 나누어야 한다.
루트가 기능에 대한 메서드를 가지고 있고 세부로직은 애그리거트에 속한 객체에게 위임해야 한다.
중요한건
이론적으로 Order과 OrderLine은 같은 애그리거트이고 리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회해야 한다.
근데 JoinTable마다 하나씩 리포지터리 만들잖아?
OrderLine은 Entity가 아니라 이론적으로 VO이다. 하지만 @Entity() 에노테이션 붙여서 쓰잖아. 현실적으로
Nest Custom Repository 생성부터 적용까지 __ (feat. 회원가입 인증)
앞서 말했지만 하나의 트랜잭션은 하나의 애그리거트만 수정해야 한다.
만약 하나의 트랜잭션이 여러 애그리거트를 수정해야 한다면 Service레이어에서 해야 한다.
ORM덕분에 애그리거트 루트를 쉽게 참조할 수가 있다. 다만, 다른 애그리거트를 수정해도 된다는 뜻은 아니다.
특히, 즉시 로딩 사용한다면 한번 불러올 때마다 시간이 걸린다.
따라서, ID를 이용해서 애그리거트를 참조해야 한다. 이러면 애그리거트 간의 의존을 제거하고 물리적 연결을 제거하므로 복잡도도 낮아진다.
걱정마 CQRS쓸거니까.
예를 들어, Store와 Product 애그리거트가 있다고 치고 Service레이어에서 두 애그리거트를 사용한다고 했을 때, Store의 상태에 따라 Product의 생성이 달라진다고 하자.
이때, Store를 Product의 팩토리로 사용하면 도메인 로직을 숨길 수가 있다.
애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 할 때 팩토리 메서드를 구현하는 것을 고려하라.