TL;DR
- ユースケースは、入力を受け取る、ビジネスルールを検証する、エンティティの状態を変える、結果を返す、の順に処理を進める。
- 入力値の妥当性チェックはユースケースではなくアプリケーション層で行い、ビジネスルールの検証はユースケースが担う。
内容
ユースケースの処理の流れ
ユースケースは次の流れで処理を進める。
- 受信アダプタから入力を受け取る
- ビジネスルールに関する妥当性チェックを行う
- ドメインオブジェクト(エンティティ)の状態を変える
- 処理結果を返す
ユースケース(ドメイン層)はビジネスロジックに集中すべきであり、入力値のチェックはビジネスロジックに含まれないので、著者は、1 の段階で入力値の妥当性チェックを行わないほうがよいと考えている。代わりに、アプリケーション層で行う(入力値の妥当性チェック、Domain Modeling Made Functional 第11章 シリアライズのための設計)。
一方、ビジネスルールに関する妥当性チェックはユースケースの責務であり、ドメインモデルとともに担う。
エンティティの状態を変えたら、一般にその状態を永続化する。そのため、エンティティを永続化アダプタが実装する送信ポートに渡す。永続化以外の副作用を持つ処理があれば、ほかの適切な送信ポートも呼び出す。最後に、送信ポートから受け取った値を別のオブジェクトに変換し、呼び出し元の受信アダプタに返す。
送金のユースケース
以下は、本書のサンプルを Go で書き直したものである。SendMoneyService は、受信ポートである SendMoneyUseCase インタフェースを満たすものとする。
// buckpal/application/domain/service/send_money_service.go
type SendMoneyService struct {
loadAccountPort LoadAccountPort
updateAccountStatePort UpdateAccountStatePort
}
// ...
func (s *SendMoneyService) SendMoney(command SendMoneyCommand) bool {
// 詳細は次の章以降
}SendMoney が呼び出されると、次の順に処理する。
- 送信ポートである
LoadAccountPortを呼び出し、口座の状態を取得する - 取得した口座の状態を持つエンティティを使って送金し、状態を変える
- 新しい口座の状態をデータベースに保存するため、送信ポートである
UpdateAccountStatePortを呼び出す
LoadAccountPort と UpdateAccountStatePort を一緒に使う頻度が高ければ、1 つの大きなインタフェースにまとめるのも手である。
type AccountRepositoryPort interface {
LoadAccountPort
UpdateAccountStatePort
}