やりすぎないドメイン駆動設計 on Rails
ドメイン駆動設計(DDD)では、Springのようにドメインをほかの関心事(データソース、プレゼンテーション)から分離したフレームワークが用いられることが多です。一方で、Ruby on Railsは、ドメインとデータソース層が密に結合しており、DDDには向いていないと考えられています。
Railsでドメイン駆動設計をする大変さ
それでも、アーキテクチャに手を加えることで、Railsでもドメイン駆動設計を行った事例が公開されています。以下の取り組みでは、Railsの提供するActive RecordをDAOとして利用してドメインを分離することで、ドメインを切り出しやすくしています。
ドメイン駆動設計の比類なきパワーでRailsレガシーコードなど大爆殺したるわあああ!!! - Qiita
Railsにおけるドメイン駆動設計の実践 · Linyclar
Railsでドメイン駆動設計を行う上での課題は、ドメインオブジェクトがデータソース層(ActiveRecord)から分離できていない点にあります。 ドメインオブジェクトにドメイン以外の知識が入り込むことを防ぐことで、柔軟なモデルを作ることができるとされています。
このため、例に挙げた取り組みでは、ActiveRecordとドメインオブジェクト用のクラスを分離する、アプローチをとっています。ドメインオブジェクトを利用するときは、ActiveRecordから得た値を使ってドメインオブジェクト生成します。ドメインオブジェクト生成用の値の詰め替え処理は、自前で書いていくことになるので結構骨の折れる作業になります。
また、Railsの開発効率の核となるActiveRecordのメソッドがコントローラやViewから使えなくなります。これも大きな痛みを伴う点です。
反対に、hanamiやSpringでは、クリーンアーキテクチャで示されるような、ドメインモデルをDBやUIから分離する構成を比較的簡単に実現できます。
密結合なアーキテクチャでDDDをやってはダメなのか
ドメイン駆動設計で何を得たいのか、というと、主には下記の二つが挙げられます。
- 変更容易性
- ドメインの知識が反映されたソフトウェアのモデル
クリーンアーキテクチャでは、ドメインモデルがほかの要素から分離されたつくりにすることで、上記二つの効果を高めています。
一方で、Railsのような密結合なアーキテクチャで、ドメイン駆動設計をしたとしても、上記の効果は部分的ではありますが得ることができます。
部分的にドメインオブジェクトを使う
Railsのアーキテクチャを壊さないままドメインモデルを表現しようとするととどうなるでしょうか?
ActiveRecordと重なるドメインオブジェクト
ActiveRecordは、ドメインオブジェクトとデータソースの両方の性質を併せ持つオブジェクトです。 このため、ActiveRecordはそのままドメインオブジェクトとして扱えます。
ActiveRecordでないドメインオブジェクト
ActiveRecordではないドメインオブジェクトは、下記のようなものが考えられます。
- あるActiveRecordをもとに構成されるが、異なる構造をもつドメインオブジェクト
- ActiveRecordのプロパティの一部から構成されるドメインオブジェクト
どちらの場合も、必要な時だけドメインオブジェクトにする、というアプローチが取れます。
読み込むときは、下記のようにします。
class Address attr_accessor :prefecture, :postal_code, :address_detail def domain_logic; end end # usersテーブルに、prefecture, postal_codeなどをもつ class User < ActiveRecord def address_as_object Address.new(prefecture, postal_code, address_detail) end end
書き込むときはこのようになメソッドを生やせばよいです。もし、この変換処理が複雑で分離する必要があるなら、Repositoryを適宜つくります。
class Client def save self.to_user.save end def to_user User.find_or_initialize_by(id: id,...) end end
部分的に作ってうれしいのか
みんなアーキテクチャをごっそり変えてDDDしているのに、こんな中途半端な使い方で大丈夫か、と思うかもしれません。 DDDの核となる考えは、ドメインの知識を反映したモデルを作ることで、ドメインに対する洞察と変更容易性を得ることです。
たとえ部分的であっても、ドメインオブジェクトを作って使うことでその効果は得られます。
この方法の利点は、ActiveRecordの世界観をできるだけ壊さずに、ドメインモデルを使うことができる点です。 ドメインモデルをほかの層から分離することによるアーキテクチャの柔軟性は得られません。
既存のRailsアプリケーションで、特に複雑な部分から少しずつドメイン知識をコード化していくときに向いている手法だと考えます。
つらくなったら
つらくなったらアーキテクチャからごっそり変えて、ドメインを分離しましょう。 あるいは、ActiveRecordを普通に使ったほうが嬉しいと感じたら、またどこかのActiveRecordかコントローラにロジックを戻してあげましょう。後戻りはおそらく簡単なはずです。