こんにちは!OPTiMに2022年4月に新卒入社予定の川田剣士です。新卒としての入社前に2ヶ月ほどアルバイトをしており、OPTiM StoreというサービスのAPIの開発に携わっていました。具体的には、APIのリクエストパラメータ/レスポンスパラメータに新しいデータを追加する実装をしたり、新規APIを0から設計して開発したりなどです。OPTiM Storeでは、言語がJava,フレームワークがSpring Boot,ソフトウェアアーキテクチャにオニオンアーキテクチャを採用しています。また、オニオンアーキテクチャやDDDを学ぶために、ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)を拝読しました。
以上の経験を活かして、まだDDDに慣れていない入門者の方々にわかりやすく説明できたらと思います。本記事を読んだ後に、DDDに関する難しい記事や本を読むと良いかなと思います。記事に対するコメント・アドバイス大歓迎です!
目次
DDDとは
・ドメインモデリングとは
・高凝集とは
・低結合とは
・高凝集・低結合であることのメリット
DDDを取り入れるための方法
・3層アーキテクチャではないソフトウェアアーキテクチャを導入する
・値オブジェクトを利用する
終わりに
DDDとは
DDD(Domain-Driven Designの略。日本語ではドメイン駆動設計と呼ぶ。)とは、ソフトウェアの設計手法の1つであり、Eric Evansさんが考案したものです。DDDの最大の特徴は「ドメインモデリングを最重要と捉え、かつコンポーネントが高凝集・低結合になること追求することによって、より保守性が高く、かつビジネス上の課題を解決し続けるソフトウェアを目指す」というものだと私は考えています。例えば、RailsのMVCフレームワークですと、
・Controllerでクライアントとのデータの受け渡しを行い、かつDBへの実行命令も記述する。(低凝集)
・Controllerで複数のクラスのインスタンスを利用する(高結合)
・ModelではRDBTableとActive Recordのモデルが1対1で対応しているため、複数のユースケースごとで利用するそれぞれのモデルを構築できず、1つのActive Recordモデルで複数のユースケースに対応することになってしまう。(低凝集・高結合)
などが発生し、RailsとDDDの相性は悪い方なのかなと思います。解決したいビジネスの課題がシンプルな場合は、高速で開発可能なRailsなどを採用し、複雑なビジネスの課題を解決し、長期的に運用することを想定する場合は、Java,Spring BootのようなDDDを取り入れることが可能な言語・フレームワークを採用するべきかなと思います。具体的には、
・interfaceを利用可能
・静的型付け言語
・ディレクトリ構成を自由に作成できる
・DIコンテナの利用が可能
などに該当する言語・フレームワークです。
ところで、先ほど
ドメインモデリングを最重要と捉え、かつコンポーネントが高凝集・低結合になること追求する
と述べましたが、「ドメインモデリング」「高凝集」「低結合」について説明したいと思います。
ドメインモデリングとは
ドメインモデリングとは、ドメインモデルを作成することです。ドメインモデルとはWikipediaで以下のように説明されています。
ドメインモデル(Domain model)は、システムに関わるさまざまな実体とそれらの関係を説明するシステムの概念モデルである。
基本的にソフトウェアを開発する上で以下のサイクルが必要となりますが、下記の2の過程がドメインモデリングを指します。
1.ビジネス上の課題を解決するために、解決したいビジネス領域のドメイン知識に関して深く理解する。(解決したい課題のあるビジネスの分野を深く理解する)
2.ビジネス上の課題をソフトウェア開発で解決するために、必要となるドメインモデルをアウトプットする。
3.アプトプットしたドメインモデルをソフトウェアに落とし込む。
ドメインモデリングは、ユースケース図やドメインモデル図などのUMLを利用することで基本的に表現されます。ドメインモデリングの際は、ユースケース図から作成し、次にドメインモデル図を作成することをドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)で推奨しています。理由は、モデルの振る舞いから理解しなければ、どのようなモデルを作れば良いか判断できないからです。例えば、自社の商品を自社のECサイトで販売しているとします。この時、「ユーザーとして商品情報を閲覧する」のと「管理者として商品情報を閲覧する」のとでは行動要因が異なると思います。例えば、ユーザーとして商品情報を閲覧する場合は「値段」や「商品の特徴」などを見るためにアクセスする一方、管理者として商品情報を閲覧する場合は、「その商品の月毎に購入された回数の一覧」や「商品の価格の変更履歴」などを見るためにアクセスするかもしれません。このように、異なるユースケースによって必要となるドメインモデルも異なるため、最初にユースケース図から作成することを推奨しています。
ドメインモデリングは初めから完璧なものを目指さなくても問題ありません。サービスを運用していく中で、よりよいドメインモデリングが思いつくことがあると思います。その際に、変更が容易にできるような状態(保守性が高い状態)にすることが理想的です。変更が容易な状態にするためには、コンポーネントが高凝集・低結合で実装されていることが必要不可欠です。
高凝集とは
凝集度とは、凝集度と結合度についてで以下のように表現されています。(個人的に1番しっくりきました。)
プログラムのひとつのコンポーネントの中に含まれる機能の純粋度を表す尺度です。
言い換えるならば、コンポーネントの責務がどの程度はっきりしているかということです。責務ははっきりしていればいるほど良いです。(つまり高凝集であるほど良い。)それぞれのコンポーネントの責務がはっきりしている場合、変更を加えたい時にどのコンポーネントに変更を加えたらよいかわかりやすく、保守性が高いソフトウェアになります。例えば、
・Controllerでクライアントとのデータの受け渡しを行い、かつDBへの実行命令も記述する場合
・複数のユースケースごとで利用するそれぞれのドメインモデルを構築せずに、1つのドメインモデルで複数のユースケースに対応する場合
などは複数の責務を1つのコンポーネントで抱えているため、低凝集であると言えます。
保守性の高いソフトウェアにしていくためには、コンポーネントが複数の責務を負わないことが重要です。
低結合とは
結合度とは、コンポーネントが他のコンポーネントにどれだけ依存しているかのを表す尺度です。結合度は低結合であるほど良いです。特定の実現したい処理を、多くのコンポーネントを組み合わせないと実現できない場合、結合度が高いと考えます。例えば、
・あるクラスで複数のクラスのインスタンスを利用して、特定の処理を実現する場合
・複数のユースケースごとで利用するそれぞれのドメインモデルを構築せずに、1つのドメインモデルで複数のユースケースに対応する場合
などは結合度が高いと言えます。あるコンポーネントに対して変更を加えても、他のコンポーネントに影響が及ばないようにすることは、低結合を実現する上で重要です。
高凝集・低結合であることのメリット
ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)では高凝集・低結合であることのメリットを以下のように説明しています。
高凝集・低結合にすると、一般に以下のようにソフトウェア特性が改善します。
• Understandability(理解容易性): コードを読んで理解しやすくなる
• Flexibility(拡張性): コードを修正、拡張しやすくなる
• Reliability(信頼性): 修正時にバグを埋め込みにくくなる
• Reusability(再利用性): 同じコードを別の場所で再利用しやすくなる
• Testability(テスト容易性): テストを実施しやすくなる
そのため、基本的に設計時には高凝集・低結合を目指します。
上記の5つのメリットは、アルバイトをしていて強く実感しました。上記のメリットに魅力を感じる方は、DDDを取り入れることを検討するべきかなと思います。
DDDを取り入れるための方法
以下では、DDDを取り入れるための具体的な方法を説明したいと思います。注意点として、私の経験や知識による紹介であることをご了承ください。私が0からAPIを設計して構築して実装した経験から、重要だと思うことを抽出して紹介していることをご了承ください。
3層アーキテクチャではないソフトウェアアーキテクチャを導入する
3層アーキテクチャの問題点は、コンポーネントが低凝集・高結合になりやすいため、高凝集・低結合であることのメリットで紹介したメリットがなくなりやすいです。そのため、より責務が細分化されたソフトウェアアーキテクチャを採用する必要があります。ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)では、オニオンアーキテクチャを採用することを推奨しています。また私がアルバイトをしていたプロジェクトでもオニオンアーキテクチャを採用しています。そのため、ここではオニオンアーキテクチャの説明をしようと思います。
オニオンアーキテクチャとは以下のような構成をしています。(ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)の図を利用しています。)
矢印は依存の方向性を表しています。黒矢印は実装されたプログラムの依存の方向性を表し、白矢印はインターフェースの依存の方向性を表しています。
それぞれの層の責務は以下の通りです。(ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)の内容を主に参考にしています。)
プレゼンテーション層
・エンドポイント定義
・HTTP Requestで渡された値とユースケース層に渡す値のマッピング
・入力値のバリデーション(1部)
ユースケース層(アプリケーション層)
・ユースケースの実現
・エンティティの生成・使用・永続化/検索依頼
・値オブジェクトの生成・使用
・エンティティからプレゼンテーション層に渡す値への変換
ドメイン層
・ドメインオブジェクト(エンティティ・値オブジェクト)の実装
・ドメイン知識(ルール/制約)
・Repositoryのインターフェースの作成
・Domain Serviceの実装(ドメイン駆動設計 モデリング/実装ガイド(松岡幸一郎)や私のあるバイト先のチームではあまり実装は推奨されていない。Domain Objectの実装でドメイン知識はできる限り表現した方が良い。)
・ドメイン知識(ルール/制約)
インフラストラクチャ層
・Repositoryの実装
・エンティティの永続化/検索の実装
となります。(エンティティは同一判定を識別子で行うオブジェクト、例えば社員番号の存在する社員などが該当します。一方、値オブジェクトは同一判定を保持する値で行うオブジェクト、例えばお金などが該当します。)3層アーキテクチャと比較して、オニオンアーキテクチャを採用するメリットは以下の通りです。
・ドメイン層が他の層に対して依存しないため、技術的な理由で最適な実装ができないという問題が生じない。(ドメインモデリングを最重要と捉えるDDDの特徴に適したアーキテクチャといえる。)
・ドメインオブジェクトの特性を理解したい場合、ドメイン層にアクセスするだけでよく、保守性が高い。(三層アーキテクチャの場合、ドメインオブジェクトの特性をControllerで実装しているため、保守性が低くなる。)
・高凝集かつ低結合なソフトウェア設計が可能である。
特に1つ目の
ドメイン層が他の層に対して依存しないため、技術的な理由で最適な実装ができないという問題が生じない。
は最も重要です。技術的な理由で最適な実装をせずに、妥協したプログラムを実装してしまうと、保守性が下がってしまい、最終的にはビジネス課題を解決できなくなる恐れがあります。ソフトウェア開発でビジネスの課題を解決し続けるためにも、最適なソフトウェアアーキテクチャをプロジェクトで採用する必要があります。
値オブジェクトを利用する。
オブジェクトの属性を実装する際、型宣言でプリミティブ型(String,Integer)ではなく、値オブジェクトのクラスの型を利用した方が良い場合が比較的多いです。理由は、値オブジェクトを利用しなければ、ユースケース層でドメインオブジェクトの特性を実装しなくてはいけなくなるからです。(保守性が低くなる。)しかし、必ず値オブジェクトのクラスを型宣言で利用しなければいけない訳ではありません。特性のロジックを実装するコストがかからない属性(例えば、Nameなど)はそのままStringを使用して良いと思います。私のアルバイト先のプロジェクトでも、特に値オブジェクトを利用する必要のない属性に関しては、プリミティブ型を利用しています。状況に応じて使い分けることが大切です。
終わりに
本記事でDDDの基礎をお伝えしましたが、いかがでしょうか?私としては、本記事を読むことで、DDDをあまり知らなかった方がDDDに興味・関心を持っていただけますと非常に嬉しいです。DDDをあまり知らなかった方や、DDDの知見が豊富なエンジニア、様々なバックグラウンドの方々の本記事に対する意見・感想お待ちしております!