バックエンドエンジニアの@akiraです。
今回はチームで採用しているドメイン駆動設計について、実際に開発してみて三ヶ月ほど経ちましたので、その知見等を公開したいと思います。
まず初めに、ドメイン駆動設計の基礎をおさらいしましょう。
ドメイン駆動設計(Domain-Driven Design)とは
Eric Evans氏が提唱した設計手法です。
ドメインモデルを使って、実装とドメインの概念を一致させることで、ドメインの複雑な問題を解決できます。
ドメインとは対象ビジネスが扱う業務領域のことであり、ソフトウェアによって解決しようとしている問題とも言えます。
ドメインモデルとは、ドメインから必要な概念を抽出したものであり、ユビキタス言語から構築されるものです。
ドメインモデルとユビキタス言語は、定義だけでは理解し辛いかもしれませんので、10円玉を例に説明します。
現実世界における10円玉の属性をいくつかピックアップすると、以下などが挙げられるかと思います。
- ほぼ銅で作られている
- 約4.5グラム
- 所々傷がある
- 10円の金銭的価値をもつ
- 硬貨
しかし、ソフトウェアの世界において10円玉の概念を実装するならば、上記全ての属性が扱われることはないでしょう。
例えば「10円の金銭的価値を持つ」という属性だけが抽出されてクラスとして定義されます。
このクラスがドメインモデルであり、10円玉から「10円の金銭的価値を持つ」属性だけを抽出することをモデル化と言います。
また、この「10円の金銭的価値を持つ」自体がユビキタス言語です。
ユビキタス言語は、ドメイン駆動設計で開発を行うチーム内で共有する言語のことで、ドメインの概念や仕様を指します。
ところで、券売機の組み込みソフトウェアをドメイン駆動設計で開発する場合、「紙幣」または「硬貨」として定義される「お金」の「種類」というユビキタス言語があるかもしれません。
この場合、上記の10円玉の属性における「硬貨」はユビキタス言語として定義されているため、ドメインから抽出され、モデル化されます。
ドメインはそれ単体では広範囲な領域となるため、通常は複数の小さなドメインに分かれます。
この複数に分けられたドメインをサブドメインと言います。
サブドメインで最も価値が大きく重要なものをコアドメインと呼び、他のサブドメインと区別します。
ドメイン駆動設計はStrategic Design(戦略的設計)とTactical Design(戦術的設計)の二つで構成されます。
Strategic Designはドメインの分析とモデリングをどう行うかを示すビジネス的設計であり、Tactical Designはどのようにコードを書くかの技術的設計を指します。
ドメイン駆動設計には主に二種類の登場人物がいます。
デべロッパーとドメインエキスパートです。
デべロッパーは、要するにコードを書く人です。
ドメインエキスパートは、そのドメインに詳しい人を指しますが、必ずしもビジネス側で働く人を指すとは限りません。
デべロッパーがドメインエキスパートを兼ねていることもあります。
ユビキタス言語は、デべロッパーとドメインエキスパートの両方が理解できる言語でなければなりません。
Eric氏はドメイン駆動設計においてレイヤードアーキテクチャーを推奨していますが、関心の分離ができれば他のアーキテクチャーでも実装は可能でしょう。
また、プログラミング言語としては、オブジェクト指向プログラミングが可能な言語を推奨しています。
クリーンアーキテクチャー
私のチームでは、クリーンアーキテクチャーに則ってコードを書いています。
クリーンアーキテクチャーには次の三つの層が存在します。
- Domain層:ドメインモデルを配置し、ビジネスロジックが集約されている層
- Application層:ドメインモデルを使ってユースケースを実装する層
- Infrastructure層:Application層における技術的詳細を実装する層
また上記の三層には次の依存関係があります。各層が矢印の方向に依存しています。
Domain層 <- Application層 <- Infrastructure層
Domain層はどこにも依存せず、Domain層内でドメインロジックの実装が完結します。
Application層はDomain層に依存し、Domain層で定義されたドメインモデルを使って特定のユースケースを実装します。
Infrastructure層はApplication層に依存し、Application層で定義されたServiceやRepositoryなどのインタフェースの実装を行います。
クリーンアーキテクチャーによって依存性を一方向に保ちながら関心を分離することができ、コードが非常に読みやすくなります。
ドメイン駆動設計の採用直後からドメインモデルの作成まで
昨年末からドメイン駆動設計による新規開発がスタートしましたが、当時はドメイン駆動設計に詳しいメンバーが”SAMURAI”こと@karkiしかいませんでした。
私はWeb上の記事を読み漁って基礎知識を吸収しつつ、理解した内容を社内勉強会を開催してチームメンバーに共有しました。
また、Eric Evans著書『Domain-Driven Design: Tackling Complexity in the Heart of Software』(英語版)を福利厚生で購入して読み込み、理解を深めました。
12月あたりからおおよそドメイン駆動設計を理解できてきたので、@karkiと共に初期のTactical Designを進めていくことにしました。
@karkiは、くらしのマーケットのビジネスに詳しいのはもちろんのこと、システムにも非常に詳しいため、デべロッパーでありながらドメインエキスパートでもありました。
彼からも随時ドメインについての知識を吸収し、理解を深めていきました。
Tactical Designはまず、ホワイトボードでドメインモデルを描くことから始めました。
UMLほど正確には描かずに、本当にラフな感じで描いて修正していきました。
おおよそドメインモデルが描けたら、今度はTypeScriptでざっくり実装していきました。
ドメインモデルのメソッドを追っていき、目的の処理ができることを確認して、初期のTactical Designを完了しました。
Strategic Designで悩む
ドメインの中で、共通のドメインモデルを使う三つのサブドメインが存在していました。
これらのサブドメインを別々に実装するか、一つにまとめて定義するかで苦悶しました。
まとめたサブドメインから別々に切り出して実装することは比較的容易だが、別々に実装したドメインモデルを統合するのは難しいとチームで判断し、結局サブドメインをまとめることにしました。
今振り返ると、この判断で間違いなかったと思います。
サブドメイン単位でISSUEを切って、各々開発に着手し始めました。
まとめたサブドメインは共通のドメインモデルを使うため、設計変更やリファクタリングが発生する度にマージコンフリクトが発生しました。
コンフリクトが発生するとDXも低下するため、リファクタリングは別PRを立てて一回のリファクタリングのスコープを限定することでコンフリクトの量を減らすことができました。
Tactical Designとテーブル設計
各サブドメインのDomain層とApplication層のコードが書き終わったあたりで、テーブル設計にも着手し始めました。
インタフェースを実装するクラスが二種類存在するドメインモデルは、それぞれの具象クラス独自の属性が少なく、クラスの種類も二種類と少なかったため、シングルテーブル継承で設計しました。
一方、インタフェースの実装クラスが多数存在するドメインモデルは、具象テーブル継承で設計しました。
ドメイン駆動設計とテーブル設計
実はこの開発が始まる前に、私は既存機能のリプレイスをドメイン駆動設計で行なっておりました。
既存機能のPythonコードをドメイン駆動設計 + TypeScriptで実装し直し、DBのテーブルは既存のテーブルを使う、といった開発でした。
しかし、この開発で大いに苦労しました。
というのも、テーブルは一切変更しないため、ドメインモデルのプロパティがテーブルのカラムに依存したものとなってしまったためです。
なんとか実装したのですが、DBとのやり取りでDBModelとドメインモデルの変換処理が毎回発生し、そのためのコードを大量に書く結果となりました。
ドメイン駆動設計によるリプレイスでは、テーブルを変更しない場合は、ドメインモデルがデータモデルに依存しないように気をつけましょう。
ユビキタス言語とドキュメントについて
ユビキタス言語は、ドメインエキスパートとデべロッパーの両方が理解していなければいけない言語です。
ユビキタス言語をコードとして実装する上で、日本語のユビキタス言語を英語に変換する必要があり、その対応表をチーム専用の辞書として作成しました。
辞書には特定の概念(単語)が英語と日本語で表現されているため、コードを読んでどんなドメインの概念が表現されているかがよく分かるようになりました。
ドメインモデルのプロパティ名やメソッド名もユビキタス言語を使うことで、より一層ドメインの知識を反映したコードが書けました。
ドメイン駆動設計を成功させるためのTips
ドメイン駆動設計を三ヶ月やってみて非常に大切だと感じた点が二つあります。
一つ目は、”とにかく早くプロトタイプを作ること”です。
ドメイン駆動設計は、一度の設計でベストなドメインモデルが手に入ることはなく、数多くのリファクタリングを経てそれに近づいていくとEric氏は指摘しています。
毎回のリファクタリングに時間がかかっていると設計が改善せず、見通しの良い設計となっていくこともないでしょう。
そのため、ドメイン駆動設計の導入段階で、ある程度コードを書ける必要があります。
二つ目は、”コミュニケーション”です。
これは、仕事をする上での一般的なコミュニケーションの重要性とは少し意味が異なります。
ドメイン駆動設計では日々のコミュニケーションでもユビキタス言語が使われるため、そこでユビキタス言語自体が鍛えられ洗練されていくのです。
その結果、認識違いを見つけたり、より良い設計のアイデアが生まれたりと、Eric氏が著書で述べている”ブレークスルー”に繋がります。
おわりに
現在もドメイン駆動設計でコードを書いており、改善点はまだまだたくさんありそうです。
今後も色々なやり方を試してみて、より良い方法を見つけていきたいです。
私たちテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。
みんなのマーケットでDDD開発をしたい方は、ぜひコーポレートサイト https://www.minma.jp/ までお気軽にご連絡ください!