この記事は、 North Detail Advent Calendar 2019 の1日目の記事です。
2020/03/26 追記
本記事は、NorthDetail ブログにも投稿しています。
https://www.northdetail.co.jp/blog/374/
WEB+DB vol.113 の「[体験]ドメイン駆動設計」がとても良い記事だったので、サンプルコードを PHP で写経してみました。
それによる、DDDへの認識の変化などもまとめていきます。
以下、個人の理解の表現のため、本来の意味・意図とずれあある可能性があります。
もし「ここが違うよ」などの情報を頂けると、大変ありがたいです。
前提
参照元
- https://little-hands.hatenablog.com/entry/2019/10/24/web-db-press-ddd
- https://github.com/little-hands/webdbpress-2019-10-ddd
- WEB+DB vol.113 [体験]ドメイン駆動設計
上記を、 記事の内容の段階 (v1 〜 v4) に沿ってPHPへポーティングします。
言語とフレームワーク
PHP / Laravel 6.*
解説
v1
コード: https://github.com/tacck/learning-ddd/tree/v1
まずはベースとして MVC で構築し、 Model の中のロジックを Serviceクラス として扱う形です。
この時点で、 Service は Model に依存した形になっています。
今見直すと、この時点で名前の揺れや迷いがあるのが見えますね。
v2
コード: https://github.com/tacck/learning-ddd/tree/v2
v1との差分: https://github.com/tacck/learning-ddd/compare/v1...v2
Repository
Service からのデータ操作が、 Model に依存しない (Repository に依存した) 形にしていきます。
このあたり、一般的な解説では「依存性の逆転」として「 Interface (ここでの Repository ) に依存する」という解説がなされるところと理解。
v1 の場合、「データを扱うレイヤー(Service)」が「データを管理するレイヤー(Model)」の名前や(具体的な)内部構造、使い方といったものを知っている必要がありました。
v2 では、「データを扱うレイヤー(Service)」は「自分の定義したドメインレイヤー ( Repository と Entity ) 」を知っていれば、実装できる状態になっています。
細かいことは抜きにして、この状態になることが重要であると捉えました。
その上で、具体的な Repository の中身(インスタンス)は、 Service を呼び出す側から与えられることになります。
つまり、「データを管理するレイヤー(Model)」の方が処理の流れとしては「データを扱うレイヤー(Service)」の前に移動しています。
これをもって「依存性の逆転」と捉えました。
Laravel では、 「Service を呼び出す側から与える」仕組み(いわゆるDI)として ServiceProvider を使っています。
これによって、ようやく「ドメイン層でデータを扱う層を意識しない」というところが繋がった感じがしました。
Model と Entity
言葉の定義が色々と混ざりそうなところ。
ここでは、下記のように考えて進めました。
name | description |
---|---|
Model | Laravel の ORMである Eloquent が取り扱う意味での Model 。ここでは "テーブル操作を行なう単位であり、実体" として捉える。 |
Entity | ドメインレイヤーから見て、 Repository を通して得られるデータ、および、Repository に渡して永続化したいデータ、と捉える。いわゆる "IDによる識別が必要なデータ" 。 |
v3
コード: https://github.com/tacck/learning-ddd/tree/v3
v2との差分: https://github.com/tacck/learning-ddd/compare/v2...v3
さらにドメインレイヤーとアプリケーションレイヤーで扱うデータの型を、「より意味あるものとして特徴付けたもの」として表現していきます。これが ValueObject (値オブジェクト) 。
細かく定義するのは手間だと感じるかもしれませんが、設計書で「用語の定義」を行なうのは一般的なことで、それをソースコードで行なうものだと捉えると良いように感じました。
(つまり、そう読めるように名前付け・設計をすることが重要。)
v2〜v3を行なうことで、クラス名・変数名・メソッド名の付け方の重要性を感じることができます。
良い名前がつけば、文章のように説明的なコードに近づいていくのかな、とまだ片鱗ですが感じました。
v4
コード: https://github.com/tacck/learning-ddd/tree/v4
v3との差分: https://github.com/tacck/learning-ddd/compare/v3...v4
さらに Enum を整理。
単に「特定の値を定義した」ものから、「状態遷移を持った」ものへと進化させることになります。
ここは PHP だとそれっぽい実装を自力でやることになりそうなので、チームで実装方針や内容の読み合わせをやるようにしておくと、導入しやすくなるんじゃないかと思います。
(逆に、誰かが「作ったからこれでやって」だと、うまく効果が伝わらないように思う。)
自分の認識の変化
認識の変化を得られたのが、これをやってもっとも良かったことです。
写経前
ざっくりと、DDDを以下のように捉えていた(ことに気づいた)。
- ドメイン層 (ビジネスルール) の変化に対応しやすい (という知識)。
- 外部環境 (フレームワーク・ミドルウェア・OSなど) の変化に対応しやすい (という知識)。
- 上二つが具体的にどう繋がるかわかっていない。
- 特に 「依存性の逆転」 が意味はわかるが効果が掴めていない。
- オニオンアーキテクチャ、クリーンアーキテクチャの 図 (ドメイン層が中にある) に引っ張られて、一番目の「ドメイン層の変化に対応しやすい」がイメージできていない。
一番最後は「周りが囲まれているので簡単に手を出せない」という表現に感じていた、ということに気づきました。
重要(コア)なものが中心にある、という表現だと読んでいたはずですが、それを捉えきれていなかったようです。
写経後
やった結果としての、現在のDDDへの理解です。
- ドメイン層 と プレゼンテーション層(フレームワーク内に実装する範囲) が「独立して変化」できるように頑張るもの。
- そのために、二層の間をインタフェースでの依存関係にしておく (かつインタフェースの実体は DI で渡す)。
- これを実現しておくことで、ドメイン層はドメイン層の中だけで閉じて考えられるという安心感が得られる。
- 結局すべて変化する必要があるので全ての層が独立して変化しやすいように頑張る。
まとめ
v3、v4あたりを実装している時は、「定義をコーディングしている」という感覚を得られたので、 ValueObject や 状態遷移を持つEnum を既存のコードに取り入れるだけでも、ある程度見通しをよくできるのでは、、、と感じました。
とはいえ、コードを変える(リファクタリングする)のは心理的な抵抗も産みやすいかな、と感じます。
特に、複数の会社で開発をしている場合で、さらにコードのメイン管理が他社だったりすると、だいぶハードルがあがるかなぁ、と。
その点は、テストコードと合わせて「これで大丈夫」というのとセットで進められれば良いかなぁ、と思います。
こういうのは、社内でももっと議論していきたいですね。