Domain Driven Design을 길라잡이로써

Domain Driven Desgin의 러닝 커브는 높다.
지난 2~3년을 DDD, OOP, MSA에 대해 고민하고, 적용하고, 레퍼런스들을 열심히 읽었지만,
누군가 “그래서 DDD/OOP가 뭔데?” 라고 물어보면 선뜻 대답하기 힘들다.
많이도 혼란스러웠고, 지금도 혼란스럽고, 앞으로도 혼란스러울것이다.
공부를 거듭하면서 앞으로의 생각은 계속 바뀌겠지만, 지금 가진 생각을 정리했다.

  1. 응집성
  2. 상속과 믹스인 사용 멈춰!
  3. 인터페이스가 핵심이다
  4. 계층형 아키텍처가 아니야!
  5. 도메인 계층에서 도메인 로직을 작성하는데 어려움을 느꼈다

응집성과 결합도

응집력 있는 도메인 설계가 시스템의 다른 부분과 느슨하게 결합될 수 있다면
아마 그러한 아키텍처는 도메인 주도 설계를 지원할 수 있을 것이다
- 도메인 주도 설계 4장, 에릭 에반스

높은 응집성과 낮은 결합도는 어떠한 방법론, 디자인 패턴, 아키텍처를 평가하는 도구가 될 수 있다.
TDD의 “한 번에 하나만 테스트”,
Unix 철학 중 하나인 “각자 일을 잘 수행하는 작은 조각”,
등은 높은 응집성과 낮은 결합도를 충족한다.

응집도와 결합도가 중요한 이유는

따라서 프로젝트가 커질 수록 높은 응집성과 낮은 결합도는 매우 중요하다.

상속과 믹스인 사용 멈춰!

Favor object composition over class inheritance. - GoF 디자인 패턴

객체 합성은 높은 응집성을 가진 객체를 낮은 결합도를 가지고 상호작용하기 위한 방법이다.

Spring, Django 같은 서버 프레임워크를 현란하게 사용해 본적이 있는가?
제공되는 클래스와 믹스인을 현란하게 상속 받고,
매우 긴 이름의 repo 메소드를 여러개 만들고 나면, 당시엔 굉장히 뿌듯하다.
내부 구조를 더 잘 이해해서 최대한 적은 양의 코드로 기능을 구현하는 것이 목표가 된다.
하지만 시간이 지나 요구 사항이 복잡해져, 프레임워크가 설계한 상속의 범주를 벗어나면 코드 수정이 매우 힘들어진다.
또한 나중에 프레임워크의 내부 구조를 까먹으면 다시 프레임워크의 방대한 코드를 확인해야한다.

상속을 이용해 코드를 재사용하는 것을 화이트박스 재사용이라고 한다.
블랙박스와는 반대로 내부 구조를 알고 있어야 한다는 뜻이다.
많은 코드를 봐야 한다는 것은 곧 낮은 응집도를 뜻한다.

“상속과 믹스인으로 제공하려던 기능들을, 별도의 클래스들로 분리하라”
분리한 클래스는 인자로 받아 활용하자.
이때 구현 클래스가 아닌 인터페이스에 의존한다면, 객체의 세부적인 구현을 몰라도 객체를 재사용할 수 있다.
따라서 객체 합성을 통한 재사용을 블랙박스 재사용이라고 한다.

blog_creator = BlogCreator(KafkaEvnetPublisher())
blog_creator.create(req)

blog_creator = BlogCreator(RabbmitEvnetPublisher())
blog_creator.create(req)

BlogCreator 관점에선 BlogCreated 이벤트가 어디에 Publishe 되는 것은 중요하지 않다.
EventPublisher 인터페이스를 구현한 객체들 받아 적절히 호출하는 것만 신경쓰면 된다.

인터페이스가 핵심이다

인터페이스가 곧 객체 관계이므로 매우 중요하다고 할 수 있다.
또한 인터페이스가 일단 정의되어 여러 곳으로 퍼지고 나면 수정이 힘들기 때문에 신중히 결정해야한다.

인터페이스는 항상 고민하는 영역이다.
도움이 되었던 생각들은 다음과 같다.

계층형 아키텍처가 아니야!

계층 아키텍처를 사용하고 있다고 말하는 많은 팀은 실제론 핵사고날을 사용하고 있다 - 도메인 주도 설계 구현 4장, 반 버논

계층형 아키텍처에선 아래 계층이 상위 계층을 참조하면 안 된다.
그러한 관점에서 Repository는 이상하고 볼 수 있다.
상위 계층인 도메인 모델을 반환하기 때문이다.
이를 정당화하기 위해 주장하는 것이 DIP이다.
이게 잘 와닿지 않아서 이런저런 생각이 참 많았다.

핵사고날 아키텍처 관점에서 이를 설명 할 수 있다.
계층형이 “UI -> application -> domain -> Infra” 모델을 제안했다면,
핵사고날은 이를 대칭적으로 접어서, UI와 Infra를 외부(OUTSIDE)로 정의한다.
따라서 Outside → (Application → Domain) 꼴이 된다.
애초에 Repository가 Domain 보다 위에 위치하게 되므로 Domain Model을 참조하는 것이 문제 되지 않는다.

그러나 사실 계층이 어쩌고는 크게 중요한 것은 아니라고 생각한다.
앞서 말했듯 인터페이스가 중요하다.
그런데 DDD에서 더 중요한 것은 도메인 계층의 인터페이스다.
따라서 실제 구현이 어떤 계층에 있든 기술 중심이 아닌 비즈니스 중심으로 기술하는 것이 중요하다.
도메인 계층을 기준으로 인터페이스가 구성되었다면, 그 구현이 아래 계층에서 발생하는 것은 문제가 되지 않는다. 심지어는 위의 계층에 구현체를 만들어도 무방하다.

도메인 모델에서 도메인 로직을 작성하는 데 어려움을 느꼈다.

이것은 도메인 로직 구현이 Pure 해야 한다는 오해 아닌 오해에서 비롯되었다.
확실히 도메인 모델은 Isolation된 환경을 가지고 있어야 한다.
도메인 서비스도 가능하면 외부에 의존하지 않는 것이 좋지만, 필요에 따라서 네트워크 호출을 할 수 있다.
앞서 반복적으로 이야기 한 것처럼 중요한 것은 인터페이스기 때문이다.
오히려 네트워크 호출을 하지 않으려고 메소드 인자에 이것저것 받아서 인터페이스가 더러워지는 경우를 피해야 한다.

그러나 도메인 모델에서 직접 네트워크 호출을 하는 것은 좋지 않다.
보통 도메인 모델에 다형성을 포함하지 않기 때문이다.
이러면 대표적으론 모델 테스트가 외부에 의존하기 때문에 곤란해진다.
그러므로 도메인 모델에서 네트워크 호출이 필요하다면, 우선 나는 필요한 부분을 도메인 서비스로 분리한다.
그리고 도메인 모델 메소드의 인자로 도메인 서비스를 주입 받아 처리한다.