Skip to content

Latest commit

 

History

History
124 lines (95 loc) · 6.88 KB

README.md

File metadata and controls

124 lines (95 loc) · 6.88 KB

DDD on Scala Sample

DDDに基づいた実装例として、Scala(PlayFramework)を使って簡易版モンハンの世界を設計/実装した(DDDは設計思想のため具体的な実装方式は複数存在するが、そのうちの一つとしての位置づけ)。

DDDを実践するにあたってのアーキテクチャや考え方の基礎は KOSKA社 で実践しているものを参考とした上で、自分なりに手を加えている。

Golang版はこちら: ddd_on_golang_sample

Scala版とGolang版を実装してみての比較まとめはこちら: ScalaとGolangでDDDを実装比較してみた

構成

ベース

アプリケーション全体としては以下の構成となっており、いわゆるオニオンアーキテクチャの形式である。 architecture

読み取りアクセス(GET)と書き込みアクセス(POST/PUT/DELETE)では処理フローを以下のように分けている(CQRS)。

access flow

ディレクトリ分け

├── 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
...

ドメインモデル図

このレポジトリで扱っているドメインモデル図は以下の通り。 domain model

ユースケース図

各ユースケースは対応するShellScriptを用意しているので、サーバーを起動してSeedデータを投入した後に実行することで動作確認することが出来る。

ハンター主体のケース

  • ハンターがモンスターを攻撃する
    • 確認コマンド: bin/attackMonster.sh

hunter_attacks_monster

  • ハンターが倒したモンスターから素材を剥ぎ取る
    • 確認コマンド: bin/getMaterialFromMonster.sh

get_material_from_monster

モンスター主体のケース

  • モンスターがハンターを攻撃する
    • 確認コマンド: bin/attackHunter.sh

monster_attacks_hunter

技術スタック

  • Scala v2.12.8
  • PlayFramework v2.8
  • cats
  • Eff
  • Domain-Driven Design
  • CQRS
  • Docker: 19.03.12
  • docker-compose: 1.26.2

DDDらしさを引き出すためのTips

  • Value Objectを生成する際に 必ず成功or失敗のどちらかとなる ファクトリメソッドを用意することで、オブジェクトの生成が不完全なものとならないようにした(完全コンストラクタの実現)
    • Value Objectが何らかの存在条件を持っている場合(例えば 5文字以上20文字以下のStringであること など)には、生成時にこの仕組を取り入れることで条件を満たないValue Objectの生成を防げるので有効である
    • このレポジトリでは試験的にhunterId/monsterIdにその機能を取り入れた(それら以外のValue Objectは特別な条件を有していないため省略)
  • cats を用いることでAdapter層で発生したエラー全てを積み上げ、レスポンスに全件返すようにしている
  • Eff を用いることでUseCase層での型ネストを解消してコードが仕様を反映している(UseCase層のコードを読むことでそのまま仕様として意味が通る)状態を実現
  • implicit classを用いることで表現力を上げる
    • UseCase層で toUCErrorIfNotExistsraiseIfFutureFailed などを用意することで、英語としてある程度自然に読めるコードになる
  • 仕様クラスを用いることで、domain層のコードが肥大化しないようにした
    • この規模のアプリケーションで分離するのは冗長であるが、クラスの役割をより細かくすることで凝集度は上がるかなと思い

セットアップ

$ git clone
# APIの動作確認に使っているShellScriptではjqを使っているので、動作確認したい場合には入れる
$ brew install jq

# 起動
$ docker-compose up
// -> http://localhost:9011/ で起動する

# Seed実行でサンプルデータを投入できる(Seed実行の上で `bin` 配下のスクリプトを流すと各ユースケースの結果が返される)
$ bin/seed.sh
 -> http://localhost:9011/

その他

UseCase層のモナドトランスファー

Adapter層でのJson変換

DBマイグレーション

参考にした情報