DDDに基づいた実装例として、Scala(PlayFramework)を使って簡易版モンハンの世界を設計/実装した(DDDは設計思想のため具体的な実装方式は複数存在するが、そのうちの一つとしての位置づけ)。
DDDを実践するにあたってのアーキテクチャや考え方の基礎は KOSKA社 で実践しているものを参考とした上で、自分なりに手を加えている。
Golang版はこちら: ddd_on_golang_sample
Scala版とGolang版を実装してみての比較まとめはこちら: ScalaとGolangでDDDを実装比較してみた
アプリケーション全体としては以下の構成となっており、いわゆるオニオンアーキテクチャの形式である。
読み取りアクセス(GET)と書き込みアクセス(POST/PUT/DELETE)では処理フローを以下のように分けている(CQRS)。
├── Dockerfile
├── DockerfileDb
├── README.md
├── app
│ ├── Module.scala // DI
│ ├── adapter // Adapter layer(e.g. controllers)
│ ├── domain // Domain layer
│ ├── infrastructure // infra layer(e.g. DTO, repositoryImpl)
│ ├── query // query processor
│ └── usecase // useCase(application) layer
├── bin // 動作確認用のツールなど
├── build.sbt
├── codegen // db migration
├── conf
├── docker-compose.yml
...
各ユースケースは対応するShellScriptを用意しているので、サーバーを起動してSeedデータを投入した後に実行することで動作確認することが出来る。
- ハンターがモンスターを攻撃する
- 確認コマンド:
bin/attackMonster.sh
- 確認コマンド:
- ハンターが倒したモンスターから素材を剥ぎ取る
- 確認コマンド:
bin/getMaterialFromMonster.sh
- 確認コマンド:
- モンスターがハンターを攻撃する
- 確認コマンド:
bin/attackHunter.sh
- 確認コマンド:
- Scala v2.12.8
- PlayFramework v2.8
- cats
- Eff
- Domain-Driven Design
- CQRS
- Docker: 19.03.12
- docker-compose: 1.26.2
- Value Objectを生成する際に
必ず成功or失敗のどちらかとなる
ファクトリメソッドを用意することで、オブジェクトの生成が不完全なものとならないようにした(完全コンストラクタの実現)- Value Objectが何らかの存在条件を持っている場合(例えば
5文字以上20文字以下のStringであること
など)には、生成時にこの仕組を取り入れることで条件を満たないValue Objectの生成を防げるので有効である - このレポジトリでは試験的にhunterId/monsterIdにその機能を取り入れた(それら以外のValue Objectは特別な条件を有していないため省略)
- Value Objectが何らかの存在条件を持っている場合(例えば
- cats を用いることでAdapter層で発生したエラー全てを積み上げ、レスポンスに全件返すようにしている
- Eff を用いることでUseCase層での型ネストを解消してコードが仕様を反映している(UseCase層のコードを読むことでそのまま仕様として意味が通る)状態を実現
- implicit classを用いることで表現力を上げる
- UseCase層で
toUCErrorIfNotExists
やraiseIfFutureFailed
などを用意することで、英語としてある程度自然に読めるコードになる
- UseCase層で
- 仕様クラスを用いることで、domain層のコードが肥大化しないようにした
- この規模のアプリケーションで分離するのは冗長であるが、クラスの役割をより細かくすることで凝集度は上がるかなと思い
$ git clone
# APIの動作確認に使っているShellScriptではjqを使っているので、動作確認したい場合には入れる
$ brew install jq
# 起動
$ docker-compose up
// -> http://localhost:9011/ で起動する
# Seed実行でサンプルデータを投入できる(Seed実行の上で `bin` 配下のスクリプトを流すと各ユースケースの結果が返される)
$ bin/seed.sh
-> http://localhost:9011/
- atnos-org/eff というライブラリを使用してUseCase層でのモナドトランスファーを実現している
- QiitaのScalaのEffを使ってDDDのUseCase層をいい感じに書いてみる に詳細を記載しているので、興味がある方はどうぞ
- circe/circe と、 jilen/play-circe というライブラリを使用して、Jsonへの変換処理を行っている
- evolutions と slick-codegen を使っている
- QiitaのPlayFramework(on Scala)にslick(on PostgreSQL)/slick-codegenを入れる に詳細を記載しているので、興味がある方はどうぞ