Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

도메인 주도 설계란 무엇인가? 1주차 - 김영명 #504

Merged
merged 1 commit into from
Apr 2, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions 2025/Domain-Driven Design Quickly/ymkim97/chapter1_2_3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# 도메인 주도 설계란 무엇인가?
## 1 ~ 3장
---

지금까지는 DDD에 대해 아주 어렴풋이 알고 있어서, 이번 기회를 통해 좀 더 개념을 잡아갈 수 있을 것 같아 읽기 전부터 기대가 되었고, 정말 재밌게 읽었습니다. 에릭 에반스의 책 “Domain-Driven Design”을 정리하고 요약한 버전인 비교적 가벼운 책이기 때문에, 에반스의 책을 읽기 전에 읽기 딱 좋은 것 같습니다. 따라서 이 책을 다 읽고 에반스의 책을 읽어 보기로 마음을 가지게 되었습니다.

책에서 등장하는 개념들은 그 자체로 70% ~ 80%는 이해가 되는 것 같지만, 실제 코드로 구현하기 위해서는 어떻게 해야하는지 잘 그려지지 않았습니다. 호기심을 해결하고 나머지 20% ~ 30%의 이해를 채우기 위해서 “도메인 주도 개발 시작하기” 책도 구매하였고, 이 또한 읽을 예정입니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

완벽하게 정독하려고 했지만 생각보다 개념이해가 어렵더라구요 저는 40%정도 이해한 듯 합니다.
다시한번 봐야겠습니다.


이번 스프린트에서 가장 크게 깨달았던 부분은 “값 객체” 와 “집합”인 것 같습니다. 정의에 대해서도 제대로 배울 수 있었고, 특히 왜 사용하고 어떻게 사용되는지를 책을 통해 알게 되었습니다.

책의 초반은 유비쿼터스 언어를 이용한 다양한 직군들 간의 협업을 강조하는 느낌이 들었고, 3장에서부터는 도메인 주도 설계의 핵심인 모델 주도 설계를 설명하면서 특히 캡슐화가 중요하고 강조되는 느낌이 들었습니다.

## 1장 - 도메인 주도 설계란 무엇인가?
* 자동화된 비즈니스 프로세스나 현실 세계의 문제가 소프트웨어의 도메인이다.
* 소프트웨어란 이 도메인으로부터 시작되며 떼려야 뗄 수 없는 관계를 가지고 있다.

위 책에서 나온 말을 보면, 소프트웨어로 현실에서 발생하는 복잡한 문제를 해결할 수 있도록 개발하는 것이 개발자의 역할이다.
그러나 복잡한 문제는 복잡한 소프트웨어가 만들어질 수 밖에 없고, 여기서 좋은 소프트웨어를 만들기 위해서는 그 소프트웨어가 무엇에 관련된 것인지를 알아야 한다.
즉, 도메인에 대한 깊은 지식이 있어야 한다는 것이고 도메인에 집중해서 소프트웨어가 도메인과 조화를 잘 이루도록 해야한다.
우리는 도메인을 모델링하여 소프트웨어를 설계해야한다.
머릿속에서 형성된 모델을 바깥으로 끄집어내고 모델을 통해 도메인 전문가, 설계자, 개발자 들과 의사소통해야 한다.

지식과 정보가 모두 표현된 모델을 가지고 코드 설계를 시작할 수 있다.
* 소프트웨어 설계: 집의 구조를 만드는 것처럼 큰 그림을 다루는 작업, ex) Waterfall, Agile
* 코드 설계: 어떤 벽에 그림을 걸지 정하는 것처럼 세부 사항에 관한 작업, ex) 디자인 패턴

책에서는 도메인 지식을 쌓는 과정을 비행 항로 제어 시스템 구축 프로젝트를 예로 들어 설명한다.
개발자는 도메인 지식을 혼자 완전히 쌓을 수는 없으며, 도메인 전문가와 지속적인 논의를 통해 정보들을 얻고, 가공하여 점차적으로 도메인의 필수 개념을 알아내도록 노력해야 한다.
이 과정에서 소프트웨어 전문가와 도메인 전문가들은 도메인 모델을 함께 만들어 내고 이 모델은 두 전문 영역이 만나는 장소가 된다.
“결국 소프트웨어의 목적이란 현실 세계의 도메인 안에 있는 비즈니스 문제들을 해결하기 위한 것”.

## 2장 - 유비쿼터스 언어
소프트웨어 전문가와 도메인 전문가가 함께 도메인 모델을 만들어 나갈때 의사소통 장벽으로 어려움이 있다.
개발자는 클래스, 메서드 등에 집중하고 현실 세계를 프로그램으로 매핑하려는 경향이 있고, 도메인 전문가는 특화된 분야에 대해서만 전문가일 뿐이며 서로의 관점이 다르기 때문이다.
모델을 만들때 서로 똑같이 이해하는 아이디어나 요소 등 정확한 정보를 교환해야하고 이는 프로젝트의 성공에 가장 중요하다.
또한 프로젝트에서 팀 멤버끼리 도메인에 관해 토의할 수 있는 공통 언어를 갖지 못한다면 심각하게 위험하다.
따라서 모델을 이야기하고 정의할 때 같은 언어로 말할 필요가 있는데 이를 어떻게 정해야할까?

> 도메인 주도 설계의 핵심 원칙은 모델 기반의 언어를 사용하는 것이다. 모델 소프트웨어와 도메인이 서로 교차하는 지점이기 때문에 모델 기반 언어를 사용하는 것이 가장 적절하다. 이때 팀이 사용하는 모든 의사소통의 형식에 항상 이 언어가 사용되도록 확인하라. 이러한 관점에서 이 언어를 **유비쿼터스 언어**라 부른다.
> . . .
> 소프트웨어 아키텍트, 개발자, 도메인 전문가로 구성된 설계팀은 자신들의 행동을 통합하고, 모델 작성과 작성된 모델의 코드화를 도와줄 언어가 필요하다.

## 3장 - 모델 주도 설계
유비쿼터스 언어를 이용해서 충분히 모델링 프로세스를 진행하고나면, 모델을 코드로 구현해야하며 이 단계 역시 엄청 중요하다.
이때 우리가 품게 되는 질문은 ‘모델을 코드로 어떻게 변환할 것인가?’이다.
모델링이 완성되어도, 개발자가 코드로 변환하는 과정에서 고려하지 못했던 문제를 발견하고 설계를 변경해야하는 경우가 생긴다.
분석가, 설계자, 개발자가 너무 따로 움직이면 설계의 의미가 의도한대로 전달되지 못할 가능성이 높다.
따라서 개발자들이 분석가들의 회의에 함께 참여하고 도메인과 모델을 명확하고 정확하게 이해해야 한다.
반대로, 분석가들이 구현 프로세스에서 분리되지 말아야 한다.

모델 주도 설계에서 사용되는 가장 중요한 패턴들:

**[계층형 아키텍처]**
복잡한 프로그램을 ‘레이어’로 분할하여 각 레이어 내부에서 설계를 수행하여 응집도 높고 자기 하위 레이어만 의존 하도록 만들어야 한다.
하나의 레이어에 도메인과 관련된 모든 코드를 집중시켜서, 사용자 인터페이스, 애플리케이션, 인프라스트럭처 코드로부터 독립적으로 만들어야 한다.
즉 도메인 모델 자체를 표현하는 것에만 집중할 수 있도록 한다.
이렇게 설계할 때 모델은 풍부하고 명확해져 핵심적 업무 지식을 충분히 포착하여 동작할 수 있게 된다.

**[엔티티]**
엔티티란 소프트웨어가 여러 상태를 거치는 동안에도 동일한 값을 유지하는 식별자를 지닌 객체이다.
특히 서로 다른 두 식별자를 가진 두 객체를 시스템이 쉽게 구분할 수 있어야 한다.
객체가 일반 속성이 아닌 식별자에 의해 구별된다면, 모델의 해당 객체 정의에는 주로 이 식별자가 반영되어야 한다.
또한, 속성 값을 이용해 두 객체의 동일 여부를 판단하는 요구사항을 경계해야 한다.

**[값 객체]**
엔티티는 추적 될 수 있어야 하고, 그러기 위해 식별자를 만들고 추적하는데에는 그만한 비용이 든다.
하나의 객체가 도메인의 어떠한 측면을 표현하는데 사용되지만 식별자가 없는 경우를 값 객체라고 부른다.
모든 객체를 엔티티로 만드는 것은 도움이 되지 않다.
식별자가 없는 값 객체는 쉽게 생성되고 폐기할 수 있다.
또한 객체는 수정할 수 없게 만들어야 한다. 다른 값을 지닌 값 객체가 필요하면 하나 더 만들기만 하면 된다.
값 객체를 공유할 수 있다면 변경 불가능하게 만들어야 하는 것이다. 그래야 데이터의 무결성이 만족된다.

아래는 책의 예시를 Java(Spring, JPA)로 작성해 보았다.
```java
@Entity
@Getter
@NoArgsConstructor
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long customerId;

private String name;

@Embedded
private Address address;

public Customer(String name, Address address) {
this.name = name;
this.address = address;
}
}
```

```java
@Embeddable
@Getter
@NoArgsConstructor
public class Address {
private String street;
private String city;
private String state;

public Address(String street, String city, String state) {
this.street = street;
this.city = city;
this.state = state;
}
}
```

**[서비스]**
도메인을 분석하여 모델을 구성하는 주요 객체를 정의하려고 할 때 도메인의 어떤 부분들은 객체로 쉽게 매핑될 수 없다는 사실을 발견하게 된다.
객체는 내부 상태 정보와 행위를 가지고 있다.
유비쿼터스 언어를 정의할 때 도메인의 핵심 개념이 나타나면 명사는 쉽게 객체로 매핑할 수 있다.
명사와 연관되어서 해당 객체의 행위를 나타내는 동사는 보통 객체의 행위 부분이 된다.
그러나 도메인의 행위 가운데 어떤 행동이나 일부 동사는 어느 객체에도 속하지 않는다.
이러한 유형의 행위가 도메인에서 식별되었을 때, 가장 좋은 해결책은 서비스로 정의하는 것이다.
서비스 객체는 내부적인 상태는 가지지 않으면서, 단순히 도메인에 기능을 제공하는 목적을 지닌다.

**[모듈]**
모델은 애플리케이션의 규모가 커지고 복잡해지기 때문에 모듈로 나누어 구조화할 필요가 있다.
> 모듈화란, 관련된 개념과 작업을 조직화하여 복잡도를 감소기키는 기법이다.
모듈을 사용하는 또 하나의 이유는 코드의 품질 때문이다.
응집도를 가능한 한 최대화하기 위해서 밀접한 관계를 지닌 클래스들을 하나의 모듈로 정의하는 방법이 권장된다.
**통신 응집도**는 모듈의 일부가 같은 데이터를 다룰 때 얻을 수 있다.
**기능 응집도**는 모듈의 모든 부분이 잘 정의된 임무를 함께 수행하고 있을 때 얻어지며, 이것이 최고의 응집도라 할 수 있다.
모듈에는 유비쿼터스 언어로 이름이 부여되어야 한다.
모듈은 한번에 모두 설계하는 것이 아니라 프로젝트를 진행하면서 리팩토링을 거쳐서 진화시켜 나가는 것이 권장된다.
결과적으로 응집도를 높이고, 결합도를 낮춰야 한다.

**[집합, Aggregate]**
도메인 객체의 생명주기를 관리하는 것은 그 자체로 매우 도전적인 작업이다.
도메인 객체의 생명주기를 잘 관리하기 위한 패턴 중 하나인 집합은 객체의 소유권과 경계를 정의하는 데 사용되는 패턴이고, 팩토리와 레포지토리는 객체의 생성과 저장을 도와주기 위한 설계 패턴이다.

> 집합은 데이터를 변경할 때 하나의 단위로 간주되는 관련된 객체들의 집합이다. 집합은 하나의 객체의 외부와 내부를 가르는 경계를 정해 구분한다. 각 집합은 하나의 root를 지니며, root는 엔티티이고 외부에서 접근할 수 있는 창구다. root는 집합된 다른 객체들에 대한 참조를 담고 있으며, 다른 객체들은 서로 관계를 맺고 있다. 그러나 객체의 외부에서는 root 객체를 통해서만 참조할 수 있다.

불변식이란 데이터가 변경될 때마다 검증해야하는 규칙이며, 집합이 데이터의 무결성과 불변식을 강제할 수 있다.
이는 다른 객체들은 root에 대한 참조만을 지니기 때문이다. 즉 다른 객체들은 집합에 속한 객체들을 변경할 수 없다는 말이다.

**[팩토리]**
엔티티와 집합은 종종 root 엔티티의 생성자를 통해 생성하기에는 너무 크고 복잡하다.
하나의 객체를 생성하는 것은 그 자체로 주요 오퍼레이션에 해당하지만, 복잡하게 조합된 오퍼레이션을 이미 생성된 객체가 부담하게 하는 것은 적절하지 않다.
따라서 복잡한 객체 생성의 절차를 캡슐화할 수 있는 팩토리가 필요하다.
팩토리는 객체 생성에 필요한 지식을 캡슐화하는 데 사용되며 집합을 생성하는데 특히 유용하다.

**[리파지토리]**
하나의 객체를 사용하기 위해서 반드시 다른 객체가 해당 객체의 참조 주소를 가지고 있어야 한다.
대부분의 객체들은 데이터베이스를 직접 조회하여 바로 얻을 수 있고, 이것은 객체의 참조를 얻어야 한다는 문제를 해결해 준다.
클라이언트가 객체를 사용하기를 원할 때, 직접 데이터베이스에 접근하여 조회해 오면 되지만, 이것은 설계에 상당히 좋지 않은 영향을 준다.
내부적인 세부 사항 이상의 정보를 노출하게 되고, 이런 코드들이 도메인 전체에 산재하게 되는데, 데이터베이스를 변경하게 된다면 이렇게 산재된 모든 코드를 수정해야한다.
또한 집합에 대한 캡슐화까지 깨진다.
객체의 참조를 얻는 로직을 캡슐화하기 위해 리파지토리를 사용해야한다.
직접 접근할 필요가 있는 집합 root에 대해서만 리파지토리를 제공한다.
클라이언트는 모델에만 집중하도록 하고 객체의 저장이나 접근과 관련된 내용은 리파지토리에 위임한다.

## [논의 내용]
* **3장 - 56 페이지**에서 레이어드 아키텍처의 애플리케이션 레이어에 대한 설명에서 “업무 로직을 포함하지 않는다.” 라고 적혀있습니다. 그런데 다음 페이지에서는 “애플리케이션 작업 전반을 조율하고 관리 비즈니스 로직이 그 안에 존재한다” 라고 적혀있습니다. 제가 이해하기로는 업무 로직이 곧 비즈니스 로직인데, 이는 해당 레이어에 있는게 자연스럽다고 생각해 왔어서 혼란스러웠습니다. 제가 잘못 이해하고 있었던 것인지, 내용이 잘못 적힌 것인지 논의해보고 싶습니다.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

레이어드 아키텍처의 애플리케이션 레이어에 대한 설명에서 “업무 로직을 포함하지 않는다.”

이부분에서 설명하는 Application Layer는 퍼사드 역할을 하는 Application Service 에 대한 얘기를 하는 것으로 보입니다. 이 경우에 Application layer는 비즈니스 로직 구현의 책임을 가지기 보단, 도메인 객체, 도메인 서비스 객체, 리파지토리 객체 등 객체간에 상호작용을 조정하는 역할로 볼 수 있고 이 경우에 비즈니스 로직은 도메인 객체에 있어야하는게 맞습니다

그런데 다음 페이지에서는 “애플리케이션 작업 전반을 조율하고 관리 비즈니스 로직이 그 안에 존재한다” 라고 적혀있습니다.

이 문장 역시 위와 같은 맥락에서 이해할 수 있습니다 여기서 말하는 애플리케이션 레이어는 위 설명과 같이 여러 객체들 간의 작업을 조정하는 역할만 하고, 비즈니스 로직은 애플리케이션 레이어가 아닌, 도메인 객체가 가지고 있는 것이 맞습니다

제가 이해하기로는 업무 로직이 곧 비즈니스 로직인데, 이는 해당 레이어에 있는게 자연스럽다고 생각해 왔어서 혼란스러웠습니다. 제가 잘못 이해하고 있었던 것인지, 내용이 잘못 적힌 것인지 논의해보고 싶습니다.

흔히 자바 스프링 개발자들 기준으로 서비스 객체의 책임 무엇이냐고 물어볼 때, 많은 경우에 비즈니스 로직을 작성하는 것을 말하시는 분들을 많이 보았습니다 제가 생각했을 때, 이렇게 답하는 이유는 대개의 경우, 비즈니스로직 작성을 트랜잭션 스크립트 패턴 방식으로 작성하시는 분들이 서비스 객체의 메소드 하나에 비즈니스 로직을 모두 담아서 작성하는 코드를 주로 작성하기 떄문입니다

그러나, 이 책에서 말하는 애플리케이션 레이어 즉, 서비스 객체에 대한 설명은 트랜잭션 스크립트 패턴을 기준으로 설명하지 않고, 도메인 모델링으로 설계된 도메인 객체를 이용해서, 비즈니스로직을 작성한다는 전제하에 설명하고 있기 때문에, 여기서의 애플리케이션 레이어는 비즈니스 로직을 가지지 않고, 그 비즈니스 로직은 도메인 객체들이 가지고 있는 것 입니다

즉, 책이 틀린 것은 아니라고 생각하고, 애플리케이션 레이어(서비스 객체)에 대한 정의가 각 사람마다 이해하는 수준이다 범위가 다르기 때문에, 책을 읽으면서 혼란이 발생했다? 정도로 이해해 볼 수 있을 것 같습니다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

책에서는 애플리케이션 서비스와 도메인 서비스를 혼용한 것 같네요..

* 서비스에 관한 부분에서, 서비스는 애플리케이션 레이어나 도메인 레이어에 속할 수도 있다고 합니다. 그런데 저는 도메인 서비스와 애플리케이션 서비스의 차이를 어떻게 구분지어야 할지 고민입니다. 혹시 이에 대해 경험 혹은 해석이 있으신 분이 있으시다면 의견을 들어보고 싶습니다.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스를 크게 애플리케이션 서비스와 도메인 서비스로 나눈다고 할 때,

  • 애플리케이션 서비스는 위에서 설명한 것처럼 여러 객체들을 조정 하는 퍼사드 역할
  • 도메인 서비스는 각 도메인 객체의 책임으로 넣기 애매한 것들을 처리하기 위한 역할

로 볼 수 있을 것 같습니다

위에 대한 내용은 자바/스프링 개발자를 위한 실용주의 프로그래밍 책에 나온 내용을 첨부합니다

스크린샷 2025-03-20 12 38 50

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 완벽하게 이해한 것은 아니지만 서로 다른 모듈로 분리했을 때, "동일 모듈 + 외부 도움(DB 조회 등) 없음"이라면 도메인 서비스일 확률이 높다고 이해했습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스의 본질은 변하지 않고 어느 레이어에 속해 있느냐에 차이만 있을 거라고 생각합니다.

* 엔티티의 특정 속성 값들을 “값 객체”로 빼내는 기준을 어떻게 잡는 것이 좋을지 궁금합니다. 물론 초기 설계부터 바로 값 객체로 빼내지는 않을 것 같은데 좀 더 구체적으로, 실제로 어떤 상황에서 값 객체로 빼내게 되는지 여러분의 경험과 기준을 들어보고 싶습니다.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 제 개인적으로는 값 객체 식별에 대해서는 상대적으로 덜 신경을 쓰는 편 입니다

그래서, 값객체를 어떻게 활용하고 식별해서 사용해야한다, 어느 시기에 활용해야한다 등의 기준은 없는거 같습니다

흔한 조언으로는 초기부터 값객체를 식별하기보다, 설계 -> 코드 작성 -> 유지보수 하는 과정에서 드러나는 맥락을 기준으로 빼내라가 있는 것 같습니다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 부분에 대해서 많이 고민해봤는데, 의식적으로 계속 생각하는 것 밖에 답이 없는 것 같습니다.
처음부터 값 객체로 빼면 오히려 의미 없는 값 객체때문에 유지보수성을 해칠수도 있어서, 주의해야할 것 같습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 값을 복사한 객체가 필요한데 불변이어야 하는 때가 있으면 추가로 설계해서 쓰곤 합니다.

C# 은 과거에 구조체를 주로 썼는데 record 라는 게 생겨서 record struct 로 선언하면 딱 도메인 주도 설계에서 얘기하는 값 객체에 부합하는 객체를 만들 수 있습니다.