From 0c3e80893f8860f905a77f8c93b48d7b457cf77c Mon Sep 17 00:00:00 2001 From: mychum1 Date: Fri, 7 Jan 2022 20:59:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20=EB=AA=A9=EC=B0=A8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/index.md diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..34e950f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,2 @@ +# 목차 +## 1. 계층형 아키텍쳐의 문제는 무엇일까? \ No newline at end of file From 48ca9ea0ae3fc94c20b3ff8a00e68cc74e24c273 Mon Sep 17 00:00:00 2001 From: mychum1 Date: Sun, 9 Jan 2022 23:42:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=204=EC=9E=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ch04.md | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/ch04.md diff --git a/docs/ch04.md b/docs/ch04.md new file mode 100644 index 0000000..f513958 --- /dev/null +++ b/docs/ch04.md @@ -0,0 +1,143 @@ +다른 계좌로 송금하는 유스케이스 구현해보기 + +## 도메인 모델 구현하기 + +1. ActivityWindow에 지난 활동들을 포착한다. +2. 출금 전에 잔고를 초과하는지 체크하는 비즈니스 규칙을 검사한다. + +## 유스케이스 둘러보기 + +### 단계 + +1. 입력을 받는다. +2. 비즈니스 규칙을 검증한다. +3. 모델 상태를 조작한다. +4. 아웃고잉 어댑터에서 온 출력을 유스케이스를 호출한 어댑터로 반환한다. + +입력 유효성 검증은 다른 곳에서 처리한다. + +유스케이스는 비즈니스 규칙을 검증하고, 도메인 엔티티와 이 책임을 공유한다.(잔고 초과 체크) + +## 입력 유효성 검증 + +유스케이스는 하나 이상의 어댑터에서 호출되므로 유효성 검증을 각 어댑터에서 전부 구현하면서 휴먼 에러가 발생할 수 있다. 따라서 **입력 모델** 의 생성자 내에서 입력 유효성을 검증한다. + +```java +package io.reflectoring.buckpal.account.application.port.in; + +import io.reflectoring.buckpal.account.domain.Account.AccountId; +import io.reflectoring.buckpal.account.domain.Money; +import io.reflectoring.buckpal.common.SelfValidating; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.validation.constraints.NotNull; + +@Value +@EqualsAndHashCode(callSuper = false) +public +class SendMoneyCommand extends SelfValidating { + + @NotNull + private final AccountId sourceAccountId; + + @NotNull + private final AccountId targetAccountId; + + @NotNull + private final Money money; + + public SendMoneyCommand( + AccountId sourceAccountId, + AccountId targetAccountId, + Money money) { + this.sourceAccountId = sourceAccountId; + this.targetAccountId = targetAccountId; + this.money = money; + this.validateSelf(); + } +} +``` + +1. final 로 불변 필드로 지정해서 생성 이후 변경할 수 없다는 것을 보장한다. +2. 유스케이스 API 일부이기 때문에 incoming 포트 패키지에 위치한다. +3. @NotNull 과 같이 Bean Validation API 를 사용하면 유효성을 쉽게 체크할 수 있다. +4. SelfValidating 추상 클래스를 확장해서 어노테이션 외에 유효성 검증을 체크한다. 이는 계층형의 계층이 아니라 유스케이스의 보호막 계층이다. + +## 생성자의 힘 + +생성자를 private으로 한 후 build() 메소드에서 구현해서 많은 파라미터를 받을 수도 있다. + +다만 필드가 많아지면 컴파일러가 잡아내지 못하기 때문에 오히려 휴먼에러가 발생하기 쉽다. + +```java +new SentMoneyCommandBuilder() + .sourceAccountId(new AccountId(~)) + .targetAccountId(new AccountId(~)) + .build(); +``` + +## 유스케이스마다 다른 입력 모델 + +각기 다른 유스케이스에 동일한 입력 모델을 사용해야할 때가 있다. 그럴 때 각기 다른 필드에 대한 유효성 검사가 필요해지게 된다. 이는 8장에서.. + +## 비즈니스 규칙 검증하기 + +구문상의 유효성을 검증하는 입력 유효성 검증과는 다르게 비즈니스 규칙은 의미적인 유효성을 검증한다. ‘출금 계좌에서 초과출금되면 안된다’ 라는 규칙과 비슷하다. + +가장 좋은 방법은 이 규칙을 도메인 엔티티 안에 넣는 것이고 여의치 않다면 유스케이스 코드에서 도메인 엔티티를 사용하기 전에 해도 된다. 데이터베이스에서 가져와야 검증이 가능한 경우처럼 좀 복잡한 경우에는 도메인 엔티티 내에서 구현해야 한다. + +```java +public boolean withdraw(Money money, AccountId targetAccountId) { + + if (!mayWithdraw(money)) { + return false; + } +} +``` + +실패할 경우 검증 전용 예외를 던지거나 에러메시지로 사용자에게 보여준다. + +## 풍부한 도메인 모델 vs 빈약한 도메인 모델 + +**풍부한 도메인 모델** 에서는 코어에 있는 엔티티에 가능한 많은 도메인 로직이 구현된다. 유스케이스는 도메인 모델의 진입점으로 동작하고, 많은 비즈니스 규칙이 유스케이스 구현체가 아니라 엔티티에 위치한다. + +**빈약한 도메인 모델**에서는 getter, setter 만 있고 어떤 도메인 로직도 가지고 있지 않다. + +## 유스케이스마다 다른 출력 모델 + +유스케이스가 반환하는 값은 필요로 하는지, 필요하다면 다른 호출자도 사용할 수 있는지 확인해야 한다. 의심스러운 경우에는 가능한 적게 반환한다. 출력 모델을 공유하면 유스케이스들이 강하게 결합된다. + +## 읽기 전용 유스케이스 + +간단한 데이터 쿼리라서 프로젝트 맥락에서 유스케이스로 간주되지 않는다면 유스케이스와 구분하기 위해 쿼리로 구현할 수 있고 이를 쿼리 서비스에 구현한다. + +```java +package io.reflectoring.buckpal.account.application.service; + +import java.time.LocalDateTime; + +import io.reflectoring.buckpal.account.application.port.in.GetAccountBalanceQuery; +import io.reflectoring.buckpal.account.application.port.out.LoadAccountPort; +import io.reflectoring.buckpal.account.domain.Account.AccountId; +import io.reflectoring.buckpal.account.domain.Money; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class GetAccountBalanceService implements GetAccountBalanceQuery { + + private final LoadAccountPort loadAccountPort; + + @Override + public Money getAccountBalance(AccountId accountId) { + return loadAccountPort.loadAccount(accountId, LocalDateTime.now()) + .calculateBalance(); + } +} +``` + +이는 CQS(Command-Query Separation) 나 CORS(Command-Query Responsibility Segragation)과 개념이 유사하다. + +## 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까? + +입출력 모델을 독립적으로 모델링해서 원치 않는 부수효과를 피할 수 있다. \ No newline at end of file