Rails でドメイン駆動開発 (DDD) をしたいという時に、考えたディレクトリ構成案です。
開発をしながら改善をして、なるべく Rails と仲良く共存できる形になったのでメモとして残します。
アーキテクチャ
- オニオンアーキテクチャ
その他前提
- 複数のコンテキストは含めない
- なるべく Rails の機能を無効化しない (目標)
ディレクトリ構成案
app/ # いつもの app ディレクトリ
┣ controllers/ # アダプターとしてみなす
┣ models/ # アダプターとしてみなす
┣ mailers/ # アダプターとしてみなす
┣ views/ # アダプターとしてみなす
┣ view_models/ # アダプターとしてみなす
┃
┣ services/ # アプリケーション
┃ ┗ users/ # `ユーザ` モジュール
┃ ┣ commands/
┃ ┃ ┗ register.rb # 登録コマンド
┃ ┃
┃ ┗ application_service.rb # アプリケーションサービス
┃
┣ domain_models/ # ドメインモデル
┃ ┣ common/ # `共通` モジュール
┃ ┃ ┣ entity.rb # エンティティ共通クラス
┃ ┃ ┣ value_object.rb # 値オブジェクト共通クラス
┃ ┃ ┗ domain_event.rb # ドメインイベント共通クラス
┃ ┃
┃ ┗ users/ # `ユーザ` モジュール
┃ ┣ user.rb # `ユーザ` 集約
┃ ┣ email.rb # `メールアドレス` 値オブジェクト
┃ ┗ repository.rb # リポジトリ (この中で Rails の機能を使用する。主に ActiveRecord。)
┃
┗ infrastructures/ # インフラ
┣ persistences/
┃ ┗ user_repository.rb
┃
┗ services/
┗ user_email_send_service.rb
app/controllers
いつものコントローラーです。
私が開発をするときはコントローラーで ViewModel
を使用して、アプリケーション層のサービスを呼びます。
app/models
いつものモデルです。ActiveRecord
のクラスと集約
を同一にはしていません。
モデルには業務ロジック以外のロジックを実装します。(スコープ、エンティティへのシリアライズ、etc)
ActiveRecord
と集約
を同一にすることも出来ます。(composed_of について)
ただし、インフラ層のインタフェースや知識がドメインモデル層に漏れてしまうのでトレードオフについて考えて、チームで決断するのが良いと思います。
app/view_models
ユーザからの入力値 (params) を受け取り、ユースケースを実行する ViewModel
で、一種のアダプターとして捉えています。
入力値のバリデーションが必要な場合にはここで行います。(ActiveModel
を使用しています。)
また、ビュー層は ViewModel
をもとに描画をすることで、ビュー層とドメインモデル層が直接やり取りをしないようにしています。
app/services
アプリケーション層のサービスです。
app/services/#{モジュール名}/commands
アプリケーション層のサービスへ渡すコマンドオブジェクトです。
Ruby のネームスペースを活かしてこのディレクトリ配下に配置します。
app/domain_models
ドメインモデルです。
ドメインモデル内ではバリデーションは行いません。(ViewModel 内で行います。)
代わりにドメインモデル層ではアサーションを用意して、常に正しく存在し続けられるようにしています。
app/domain_models/common
ドメインモデル内の共通モジュールです。
エンティティや、値オブジェクトの親クラスを定義して、
各ドメインモデルで継承して使用します。
app/infrastructures
インフラ層の実装クラスをまとめます。
app/infrastructures/persistences
永続化に関する実装クラスです。
この中で ActiveRecord
や、Rails.cache
などのインフラの力を使います。
app/infrastructures/services
アプリケーション層のサービス、ドメインサービスの実装クラスです。
HTTP を使った外部への通信や、メール送信などを実装します。
メール送信をする際には、ここから ActionMailer
を使用しています。
まとめ
もっといい案があれば教えて下さいませ。