Testable한 설계

김경호·2023년 10월 29일
0
post-thumbnail

Testable한 코드와 Application, Domain, Infra의 관계

리팩터링하기 쉬운 코드를 만들 수 있는 가장 쉬운 방법은 잘 알려진, 디자인 패턴(팩터리 패턴, 파사드 패턴 등) 혹은 설계를 따르는 것이다. 테스트도 그렇다. 근데, 생각없이 짜면 안된다. 잘 알려진 레이어드 패턴을 떠올려보자.

Presentation → Application → Domain → Infra

마틴파울러-레이어드

내가 좋아하는 마틴 파울러 블로그, 단 요즘에는 ORM쓰기 때문에 Data Mapper는 빼도 됨.

bliki: PresentationDomainDataLayering

P of EAA: Data Mapper

우리가 일반적으로 생각하는 레이어드 아키텍처이다. 하지만, 이 코드의 문제점이 뭘까??

Infra 혹은 경우에 따라서 Domain이 의존성의 끝에 위치하고 있다.

DI를 안하면 코드가 어떻게 될까? Infra혹은 Domain에 구현체가 들어가고 난리 부르스를 출거다.

이것과 Testable이 무슨 상관? 고수준 모듈이 저수준 모듈을 의존하고 있다? 이거 안좋음.

저수준 모듈의 구현이 바뀌면 고수준 모듈에 전파가 된다. 즉, 저수준 모듈의 구현을 완료해야 고수준 모듈을 만들 수가 있다.

DB설계 혹은 Entity 설계를 안하면 기능개발을 못한다, 내지는 테스트가 안된다.

Application 레이어의 메서드 하나를 테스트로 짠다고 해보자. Repository 패턴에 ORM쓴다고 하면,
가장 쉽게 구현하는 방법은 JPARepository<T, ID> 인터페이스를 도메인 레이어에 집어넣겠지.
JPARepository 반환값이 Optional<Entity>인데 Entity설계 없이 Application 레이어 개발 가능? 불가능.

DIP의 핵심은 인터페이스와 구현 클래스를 분리하는게 아니야.

DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함이다.

인터페이스와 구현 클래스는 그 결과일 뿐 인과관계를 착각해서는 안된다!!!

저수준 모듈이 고수준 모듈을 의존하는 모습

의존성주입

나는 JPA안쓰고 TypeOrm쓸거니까 TypeOrm repository가 되겠지. 주의점은 Nest.js가 DI를 interface로 못해주기 때문에 interface로 하고 싶으면 직접 module에다가 설정을 해줘야함(반자동까지는 가능).

저수준고수준모듈

애그리거트가 뭐야?

bliki: DDD_Aggregate

애그리거트에 속한 객체는 동일한 라이프 사이클을 갖는다. 같이 생성되고 같이 제거된다.

같은 애그리거트 안에서만 transaction이 이루어져야 함

아니면 Service로 빼라.

포함관계와 착각하지마!!

상품과 리뷰를 예시로 들 때, 상품에 리뷰가 포함되니 같은 애그리거트로 착각할 수 있다.

하지만, 상품과 리뷰의 라이프 사이클이 다르다. 라이프 사이클이 비슷한지를 기준으로 경계선을 나누어야 한다.

애그리거트 루트의 핵심은 일관성에 있다!!

루트가 기능에 대한 메서드를 가지고 있고 세부로직은 애그리거트에 속한 객체에게 위임해야 한다.

VO가 뭐야?

bliki: ValueObject

중요한건

  • Property로 비교하냐 id로 비교하냐
  • 로직이 없냐 있냐
  • Immutable하냐 안하냐
  • 논리적으로 속성에 해당하냐 안하냐

리포지터리, 이상과 현실 속에서

이론적으로 Order과 OrderLine은 같은 애그리거트이고 리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회해야 한다.

근데 JoinTable마다 하나씩 리포지터리 만들잖아?

OrderLine은 Entity가 아니라 이론적으로 VO이다. 하지만 @Entity() 에노테이션 붙여서 쓰잖아. 현실적으로

커스텀 레포 생성하는 법

Nest Custom Repository 생성부터 적용까지 __ (feat. 회원가입 인증)

그럼 모든 Transaction은 도메인레이어 에서만 이루어질까?

앞서 말했지만 하나의 트랜잭션은 하나의 애그리거트만 수정해야 한다.

만약 하나의 트랜잭션이 여러 애그리거트를 수정해야 한다면 Service레이어에서 해야 한다.

애그리거트가 애그리거트를 참조한다면??

ORM덕분에 애그리거트 루트를 쉽게 참조할 수가 있다. 다만, 다른 애그리거트를 수정해도 된다는 뜻은 아니다.

특히, 즉시 로딩 사용한다면 한번 불러올 때마다 시간이 걸린다.

따라서, ID를 이용해서 애그리거트를 참조해야 한다. 이러면 애그리거트 간의 의존을 제거하고 물리적 연결을 제거하므로 복잡도도 낮아진다.

N+1문제는 어떻게 할건데요

걱정마 CQRS쓸거니까.

애그리거트, 그런데 팩토리 패턴을 곁들인.

예를 들어, Store와 Product 애그리거트가 있다고 치고 Service레이어에서 두 애그리거트를 사용한다고 했을 때, Store의 상태에 따라 Product의 생성이 달라진다고 하자.

이때, Store를 Product의 팩토리로 사용하면 도메인 로직을 숨길 수가 있다.

애그리거트가 갖고 있는 데이터를 이용해서 다른 애그리거트를 생성해야 할 때 팩토리 메서드를 구현하는 것을 고려하라.

profile
프로그래밍은 문제를 해결하기 위한 수단이 되어야 한다.

0개의 댓글