バックエンドエンジニアの@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/ までお気軽にご連絡ください!