개발자는 기록이 답이다

DDD 입문서를 읽고 나서 느낀 DDD란 무엇인가? 본문

카테고리 없음

DDD 입문서를 읽고 나서 느낀 DDD란 무엇인가?

slow-walker 2024. 9. 18. 22:24

최근 2달간 면접에서 DDD에 대한 질문을 받고나서, DDD에 대해 알아봐야겠다고 생각했습니다.

카카오뱅크, 힐링페이퍼, 뉴플로이 회사에서 총 3번 정도 DDD 질문을 받고 나니 그 필요성을 느끼게 되었습니다.

유튜브로 찾아보기도 했는데, 사실 "전략적 설계"와 "전술적 설계"가 무엇인지 와닿지 않았습니다.

 

그래서 구체적으로 와닿을 만한게 어떤 게 있을까? 고민하면서 주변 개발자분들과 얘기를 나누다 스터디를 진행하기로 했습니다. 제가 DDD 입문서로 추천받은 책은 바로 최범균님의 'DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기' 입니다.

추석 연휴 동안 다 읽는 것을 목표로 했는데, 아직 10장, 11장은 읽지 못했지만 바운디드 컨텍스트까지 읽은 내용을 정리해봤습니다.

 

 

해당 책은 DDD의 철학을 다 이해했다는 가정하에, 그 철학을 구체화해서 구현부를 다루는 내용이라고 합니다.

그래서 책에서 철학에 대해 자세히 다루진 않았지만, 제가 이해한 내용 기반으로 오호라! 싶었던 것들을 적어보겠습니다.

 

📌 DDD 의 철학

DDD(Domain Driven Design, 도메인 주도 설계)란 는 비즈니스 도메인에 깊이 집중하여 이를 기반으로 소프트웨어를 설계하는 방법론입니다. 즉, 비즈니스 요구사항을 도메인 모델로 표현하고, 이를 통해 도메인의 책임과 역할을 명확히 반영하는 것을 목표로 합니다.

 

예를 들어, 온라인 서점 이라는 상위 도메인을 기반으로 애플리케이션을 개발할 때, 각 세부적인 기능을 '회원', '혜택', '주문', '결제', '리뷰', '배송'과 같은 하위 도메인으로 나누어 역할을 정의할 수 있습니다. 이러한 과정을 통해 각 도메인이 무엇을 책임지고, 어떤 역할을 수행해야 하는지를 명확히 할 수 있습니다.

 

이렇게 도메인 모델을 설계하고 나면, 각 도메인이 맡는 역할과 책임이 코드에 자연스럽게 반영됩니다. 이를 통해 "코드가 곧 문서화"라는 철학과 연결되는데, DDD에서는 코드가 비즈니스 로직과 도메인을 직접 표현하기 때문에, 별도의 문서 없이도 코드만으로 시스템의 설계와 동작을 이해할 수 있게 됩니다.

DDD Start의 p.17

 

결론적으로 DDD는 비즈니스 도메인을 잘 이해하고 이를 소프트웨어에 녹여내는 데 중점을 두며, 설계 단계에서부터 코드가 도메인의 본질을 명확히 반영하도록 함으로써, 비즈니스와 기술 간의 간격을 줄이고 코드 자체가 도메인의 문서 역할을 하도록 만드는 것이 그 핵심 철학입니다.

 

📌  DB 엔티티와 도메인 엔티티는 다르다.

책을 읽으면서 가장 크게 느낀 점은, DDD객체지향 프로그래밍(OOP)이 비슷하다는 생각입니다. 결국 DDD도 객체의 역할과 책임에 집중하고, 각 객체 간의 협력을 중시하는 내용이라는 점에서 OOP와 크게 다르지 않다고 느꼈습니다.

 

그러다가 "DB 엔티티와 도메인 엔티티는 다르다" 라는 문장을 보고, 조영호님의 '오브젝트' 서적을 읽을 때가 떠올랐습니다.

객체지향 설계를 잘하기 위해서는 상속, 추상화, 다형성과 같은 개념들이 중요한데, 문제는 데이터베이스와 상호작용하게 될 때 이러한 객체지향적 설계 원칙을 100% 활용할 수 없다고 느꼈기 때문입니다. 

 

예를 들어, 할인 기능을 설계한다고 할 때, 정률 할인과 정액 할인을 각각 데이터베이스에 저장하고 그 정보를 기반으로 할인 방식을 처리한다면, 결국 데이터 중심의 설계로 흘러가게 됩니다. 순수한 Java 코드만으로 설계했을 때는 완전한 OOP로 설계할 수 있지만, 외부 저장소(예: 데이터베이스)를 사용하는 순간에는 완벽한 객체지향 설계가 어렵다는 생각을 하게 되었습니다. 그래서 OOP를 객체 '지향' 프로그래밍이라고 부르는 것인가 싶기도 했습니다.

 

이러한 생각을 바탕으로 기존에 프로젝트를 진행했기 때문에, 도메인 엔티티라는 것도 결국 DB의 레코드와 유사한 기능을 하는 것이라고 생각했습니다. 하지만, 이 책에서 설명하는 DDD의 핵심은, DB 엔티티는 데이터를 영속화하기 위한 것이고, 도메인 엔티티비즈니스 본질을 다룬다는 점입니다.

 

DDD에서 도메인 엔티티는 단순히 데이터를 저장하는 DB 엔티티와는 달리, 비즈니스 로직행위를 포함한 객체입니다. 도메인 모델은 비즈니스의 핵심 개념을 표현하며, 이를 통해 비즈니스 요구사항을 정확하게 반영하는 것을 목표로 합니다.

 

그래서 도메인 영역에는 Entity, Value 객체, Aggregate, Repository, Domain Service와 같은 개념들이 존재합니다. 이들은 모두 비즈니스 로직을 중심으로 시스템을 구성하기 위한 핵심 요소들입니다.

 

📌  애그리거트는 복잡한 도메인 모델을 관리하는 단위이다.

 

각 도메인별로 필요한 Entity와 Value 객체를 구현하다보면 점점 개별 구성 요소들이 많아지고, 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기가 어려워집니다. 마치 100개 이상의 테이블을 한 장의 ERD로 표현했을때, 그 복잡함으로 인해 전체적인 구조를 파악하기 어려운 것 처럼 말이죠.

 

이처럼 복잡한 도메인을 보다 쉽게 이해하고, 상위 수준에서 모델을 조망할 수 있게 하는 것이 바로 애그리거트(Aggregate)의 역할입니다.

DDD Start의 p.72 ~ p.73

 

한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖습니다. 이들은 함께 생성되고 함께 소멸하며, 논리적으로 관련된 상태와 행위를 관리합니다. 애그리거트는 [그림 3.3]에서 보는것 처럼 경계를 갖습니다. 애그리거트 자체는 독립된 객체 군이며, 경계 안에서 일관성이 유지되도록, 외부에서는 직접 수정할 수 없어야 합니다. 즉, 자기 자신만 관리할 뿐 다른 애그리거트는 관리하지 않아야 합니다.

 

애그리거트 루트 엔티티(Aggregate Root)가 중심이 되어 애그리게잇 내의 일관성을 유지하는 것이 중요합니다.

  1. 루트 엔티티가 아닌 객체가 직접 변경되지 않도록
    • 애그리거트 내에 있는 객체들은 루트 엔티티를 통해서만 접근하고 수정되어야 합니다. 루트 엔티티가 아닌 다른 객체가 애그리거트 내 객체를 직접 변경하게 되면 일관성이 깨질 수 있습니다.
  2. public set 메소드 사용 지양
    • public한 setter 메소드를 무분별하게 사용하지 않도록 주의해야 합니다. 대신, 도메인 모델의 의미를 표현하는 메소드를 통해 상태 변경을 유도하여, 불필요한 데이터 변경을 방지하고 비즈니스 로직을 보호해야 합니다.
  3. 불변 Value 객체 사용
    • 불변(Immutable) Value 객체를 사용하여 애그리거트 외부에서 값 객체를 변경하지 못하도록 해야 합니다.

 

📌  도메인 서비스와 응용 서비스의 역할이 다르다.

 

도메인 서비스란 domain 영역에 속하며, 도메인 영역에서 비즈니스 로직을 처리 할때 하나의 애그리거트로 기능을 구현할 수 없을때 사용합니다. 대표적으로 결제 금액 계산 로직을 예시로 들 수 있습니다, 실제 결제 금액을 계산할 때 '상품 애그리거트', '주문 애그리거트', '할인 쿠폰 애그리거트', '회원 애그리거트' 를 사용해야 하는데, 실제 계산 금액을 계산해야 하는 주체가 어떤 애그리거트일지 정하기가 애매합니다. 이처럼 하나의 애그리거트로 밀어넣기 어려운 도메인 개념을 구현하기 위해서는 도메인 서비스를 이용해야 합니다.

 

저 역시 DDD에 대해 알기 전에 주문 금액과 실제 결제 금액을 구별해서 구현해야 할때 이 기능을 어떤 도메인의 역할로 잡아야할지 애매했던 적이 있습니다. 당시 저는 여러 VO에 관련 모든 데이터를 담아두고 각 VO에 있는 값들을 기반으로 메소드를 구현해서 응용 서비스에서 사용했던 적이 있는데 이걸 도메인 서비스로 구현하면 된다는 것을 깨달았습니다.

 

반면 응용 서비스는 application 영역에 존재하며, 맡은 역할도 다릅니다. 주로 애플리케이션의 흐름을 조율하며 비즈니스 로직을 포함하지 않고, domain 영역과 infrastructure 영역을 호출해서 처리하는데 집중합니다.

DDD Start의 p.171 ~ p.172

  1. 도메인 객체 간의 실행 흐름 제어
  2. 표현 영역와 도메인 영역을 연결해주는 Facade 역할
  3. 트랜잭션 처리
  4. 접근 제어
  5. 이벤트 처리
구분 도메인 서비스 응용 서비스
위치 domain application
역할 비즈니스 로직 처리 애플리케이션 흐름 관리
책임 도메인 상태 변경 및 상태 계산 도메인 객체와 외부 시스템 조율
비즈니스 로직 포함 여부 O X
예시 결제 금액 계산, 계좌 이체 트랜잭션 관리, 사용자 요청 처리

📌  BOUNDED CONTEXT는 애그리거트와 무엇이 다를까?

 

애그리거트는 복잡한 도메인 모델을 일관성 있게 관리하기 위한 단위입니다. 애그리거트는 비즈니스 로직을 처리하는 작은 단위로, 도메인 내에서 객체들이 하나의 트랜잭션 안에서 처리되고, 그 내부의 객체들 간의 일관성을 유지하도록 설계됩니다. 각 애그리거트는 경계를 가지고 있지만, 이 경계는 비즈니스 로직을 처리하는 단위 내에서 작동합니다.

 

반면 BOUNDED CONTEXT전체 도메인 모델의 경계를 정의하고 관리하는 더 큰 단위입니다. 바운디드 컨텍스트는 도메인에서 사용되는 비즈니스 용어, 규칙, 의미 등을 관리하며, 동일한 용어라도 하위 도메인에 따라 다른 의미를 가질 수 있습니다. 바운디드 컨텍스트는 이러한 차이를 관리하고, 각 문맥(context)에 맞게 도메인 모델을 나누어 경계를 정의합니다.

 

대표적인 예가 "상품" 입니다.

 

애그리거트에서는 "상품"은 비즈니스 로직을 처리하는 작은 단위로, 해당 도메인에서 상품에 대한 정보나 규칙을 관리하는 객체들이 하나의 트랜잭션으로 묶여 관리됩니다. 예를 들어, 상품 명, 상품 가격, 상세 설명 등을 업데이트하거나 관리하는 로직이 이 안에 포함될 수 있습니다.

 

BOUNDED CONTEXT에서는 "상품"이라는 개념이 하위 도메인마다 다른 의미를 가질 수 있습니다.

  • 카탈로그 도메인에서는 "상품"이 상품 이미지, 상품 설명, 가격 등 마케팅과 관련된 정보로 정의될 수 있습니다.
  • 재고 관리 도메인에서는 "상품"이 재고를 추적하기 위한 개별 객체로, 물리적으로 존재하는 상품을 관리하는 용어로 쓰일 수 있습니다.

이처럼, 동일한 용어라도 서로 다른 하위 도메인에서 의미가 달라지기 때문에, 바운디드 컨텍스트는 각 도메인이 독립적으로 동작할 수 있도록 도메인 간의 경계를 설정하고 관리하는 역할을 합니다.

구분 애그리거트 바운디드 컨텍스트
역할 도메인 로직을 처리하는 작은 단위 도메인 모델의 큰 경계를 정의
적용 범위 각 도메인 별 개별 트랜잭션 및 일관성 처리 여러 도메인 모델 간의 경계 설정 및 충돌 방지
비즈니스 로직 특정 비즈니스 로직을 묶어서 처리 여러 하위 도메인을 통합하여 관리

개발을 진행하면서 무의식적으로 느꼈던 비효율적인 부분들을 DDD(Domain-Driven Design)를 통해 해결할 수 있다는 강한 인상을 받았습니다. 특히, DDD에서 강조하는 경계를 명확히 설정하는 것이 시스템 설계의 핵심이라는 점이 크게 와닿았습니다.

 

개발 중에 특정 기능을 더 잘 개발할 수 있는 방법을 고민하면서 어떻게 해결할지에 대해 이론이나 개발론적으로는 명확하게 모르겠지만, 머리를 쥐어짜내면서 어찌저찌 해결했었는데, 그 내용들이 이 책에 담겨있다는게 재미있었습니다. 경계 설정이 불명확해 생긴 문제였고, 해결 방법에 대해 알 수 있는 아하! 모먼트를 갖게 되었습니다.

 

그러나 동시에, 애그리거트가 지나치게 커지거나, 바운디드 컨텍스트를 명확히 설정하지 못하면 결국 처음과 같은 비효율적인 상황에 다시 빠질 수 있다는 점도 경계해야 한다고 생각합니다. 이부분은 실제 현업에서 같이 일하는 동료 개발자들과 함께 하면서 논의하는 시간을 가지면 더욱 좋을 것 같은데 아쉽네요..

 

또한, 본 포스팅에서 다루진 않았지만 리포지터리를 domain 영역에서 추상화하고, 구현체는 infrastructure 영역에 두는 방식을 통해 각 영역 별로 결합도를 낮추면 사용하는 데이터베이스나 외부 서비스가 변경되더라도 시스템의 안정성을 유지할 수 있음을 알게 되었습니다.

 

사실 이 부분에서는 클린 아키텍처와 헥사고날 아키텍처가 생각났는데, 이 아키텍처들은 의존성의 방향을 도메인 쪽으로 향하게 함으로써 도메인 로직이 외부 기술에 영향을 받지 않도록 하는 설계 철학을 공유하고 있다고 알고 있습니다. 결국엔 모두 비슷하게 유연하고 확장 가능한 시스템을 만들 수 있다는 점에서 공통점을 갖는것 같은데, 이걸 명확하게 면접에서 구별해서 설명해야 한다는 것도 어려운 일이라고 느껴지네요😅