RailsのModelからレポジトリ層とドメイン層の責務を分割する設計を考える

前提

  • RailsMVC構成になっている
  • Model部分にDBを使ったデータ操作ドメインロジックの実装が入ってくるのでレポジトリ層とドメイン層の責務が両方入ってくる
  • この状況だとテストをDBが依存する部分と依存しない部分に分離して実装できなくなる
  • 集中的にテストを実装したいドメインロジック部分のコードにDBが絡む事になる。つまり、テストデータの生成にDBの制約が絡んでくるので、テストが重たくなったり、テストデータの生成が時に難しくなり保守性が下がる
  • この状況を改善しテストを実装しやすくするために、レポジトリ層とドメイン層の責務を分割する設計を考えたい

設計一覧

どんな設計をしたら問題を解消できるのかを考えてみる

① RepositoryとEntity or Dtoを登場させてみる

説明

  • DBを使ったデータ操作に関する責務をRepositoryとModelに、ドメインロジックをEntityに分割する
  • DIして渡されたEntityをRepositoryが使う
  • Repositoryに対して、DB関連のテストを実装する
  • Entityに対して、ドメインロジックのテストを実装する
  • 業務システムで、Entityに対して名前をつけるのが難しい場合はDto(data transfer object)でも良い
  • テスト実装数は、 Entity > Repositoryにする

メリット

  • ないかもしれん

デメリット

  • ロジック操作にEntityが入ってくるので、他モデルから辿って使う場合ドメインロジックに触れにくくなる
    • 例えば、member.school.xxxみたいな使い方ができなくなる
  • データ操作に関する責務がModelとRepositoryに散る
  • Entityの実装コストが高そう

② ModelとServiceに分離してみる

説明

  • DBを使ったデータ操作に関する責務をModelに、ドメインロジックをServiceに分断する
  • DIして渡されたModelをServiceが呼び出す
  • Modelに対して、DB関連のテストを実装する
  • Serviceに対して、ドメインロジックのテストを実装する
  • テスト実装数は、 Service > Modelにする

メリット

  • DBを使うデータ操作 = Model, ドメインロジック = Serviceってなるから統制しやすい

デメリット

  • ①と同じように他モデルから辿って使う場合ドメインロジックに触れにくくなる
  • ドメインモデル貧血症
  • Serviceは色んな意味があってチームによってやる事が変わるような事が多いので名前が微妙かも

③ Model/Domain層を作ってみる

説明

  • Modelと対の関係にあるModel/Domain層を作る
  • Model/DomainをModelが呼び出す
  • Model/Domainは、複雑なドメインロジックを実装する責務を持つ
    • 複雑度が高かったり、テストケースが膨れ上がりそうなドメインロジックにフォーカスする
  • Modelに対して、DB関連のテスト+ドメインロジックのテストを実装する
  • Model/Domainに対して、複雑なドメインロジックのテストを実装する
  • テスト実装数は、 Model > Model/Domainになる
    • Model/Domainは複雑なドメインロジックのみ切り出すという制約があるのでModelの方が多くなるはず

メリット

  • 他モデルから辿って使う場合ドメインロジックに触れられる

デメリット

  • レポジトリとドメインの完全な分断ができない
    • Railsを使ってたら仕方ない気はする
  • 複雑な」の基準が人によるので循環的複雑度等で計測して判定する必要がある

まとめ

  • Railsの思想に少しでも乗っかっている③の方向性が一番マシな気がする
  • Railsの思想に出来るだけ逆らわず、良い感じの設計を提案していきたい
  • 今後も何か新しい設計案を思いついたら更新していく