はじめに(勉強し始めたきっかけなど)
プロジェクトで目の前のコードを読んで書くだけではエンジニアとして視野が狭くなってしまうと感じたので、会社のほとんどのプロジェクトで使われていると聞いたドメイン駆動設計(DDD) について勉強してみました。
バックエンドエンジニアとしてキャリアアップしていきたいと思っているので、将来的には自分でソフトウェアの設計もできるようになりたいのも理由です。
読んだ本
「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」
成瀬充宣著
ドメイン駆動設計とは?
早速本題です。
ドメイン駆動設計を一言で表すと、システムで実装するドメイン(事業領域)に関する概念とその扱いを目立たせて表現しましょう! という設計思想のことです。
PMもコーディングも担当されている方に、この解釈で間違っていないかお聞きしてみたところ、「要点はつかめていると思います!」と言っていただけたので、私はこういうものだと思っています(補足や別の意見があれば、コメント欄で教えてください)。
ドメイン駆動設計の核となる2つの概念
この設計思想(方針とも言い換えられるかも?)を理解して実現するには、いくつかの概念を理解する必要があります。
ドメインオブジェクト
ドメインモデル(ドメインに関わる概念)をプログラムとして表現したオブジェクトのことです。
前項で紹介した「システムで実装するドメイン(事業領域)に関する概念とその扱いを目立たせて表現する」の**「ドメインに関する概念」** という部分のことです。
ドメインモデルというのは、例えば物流管理システムでいうと、
・システムを操作するユーザ
・荷物
・配達先に届けるまで荷物を保管しておく物流拠点
・ユーザ名
・荷物の重さ
・配達先の住所
などです。
ドメインモデルは、生成されて変更されて削除される...というライフサイクルを表現する必要があるか否かで、2種類に大別されます。
①エンティティ
ライフサイクルを表現することに意味があるドメインオブジェクト を、エンティティと言います。
上記の例でいうと、上の3つがそれにあたります。
ユーザは、新規登録処理で生成され、必要に応じて名前・メールアドレス・パスワードなどが変更され、使う人にとってシステムが不要になれば削除される。
荷物は、配送依頼後に新しく登録され、配送先に無事に届けばデータとして削除される。
など、ライフサイクルがありますよね。
エンティティであることの条件としては下記の3つです。
・オブジェクト内の属性が可変であること(必ずしもすべての属性を可変にする必要なない)
・同じ属性のオブジェクトが複数あっても、まったくの別物として区別される(同姓同名=同一人物ではない)
・属性を変更しても変更前と同じオブジェクトとして扱われること(IDなどの識別子で区別することが多い)
②値オブジェクト
ライフサイクルを表現することに意味がないドメインオブジェクト を値オブジェクトと言います。
上記の例でいうと、下の3つがそれにあたります。
ユーザ名も荷物の重さも配達先の住所も、オブジェクトの中の属性に過ぎませんよね。
注意しなくてはいけないのは、同じドメインモデルを表現するにしても、何をエンティティにして何を値オブジェクトとして扱うべきかはシステムによって異なる ということです。
例えば、自動車工場にとってタイヤはただの一部品であり属性(値オブジェクト)に過ぎませんが、タイヤ工場にとってタイヤは生産・出荷・メンテナンスなどをしなくてはいけない製品です。同じものでもシステムの利用者によって扱いが違うので、プログラム上でも扱いが異なることが想像できると思います。
よく「システム開発者もドメイン知識や利用者の視点を持っておくと良い」と言われるのは、この辺りも理由としてあるんだろうなと思いました。
エンティティを表現する方法
エンティティは、それ単体で一つのクラスとして表現することが一般的です。
例えば、ユーザクラスは下記のようになります。
class User
{
private $id
private $name
private $mailAddress
private $passwordHash
(以下、getter/setter、UserのCRUD処理を行うメソッドなど)
}
このように、エンティティはそれ自体を一つのクラスとして、その中に値オブジェクト(属性)をいくつか入れる ことで表現します。
そのドメインモデルの属性やそのルールをエンティティに集約させて表現するのです(ルールに適合するかどうかは、インスタンス生成時にコンストラクタなどでチェックするのが良いです)
そして、そのエンティティをユースケースやリポジトリなど色々な領域で利用します。
ドメインオブジェクトの定義を散在させてはいけません(DRYの原則 )。
ドメインモデルに関する情報に変更があったとき(名前・メールアドレスの重複不可→メールアドレスのみ重複不可にする、ユーザエンティティにメルマガ配信可否の属性を追加するなど)、プログラム上の変更箇所を少なくするため です。
ユースケース
ユースケースとは、ドメインオブジェクトをどのような場合にどう扱うかを使う人目線で表現するプログラム上の領域のことです。
「システムで実装するドメイン(事業領域)に関する概念とその扱いを目立たせて表現する」の**「扱い」** に当たります。
例えば、
①ユーザ新規登録のためのデータとして、ユーザ名・メールアドレス・パスワードを受け付ける(ユースケースを表現するRegisterUserクラス内で、Userクラスを扱う。以下同様)
②入力されたユーザ名とメールアドレスが既存ユーザのものと重複していないかチェック
③どちらか一方でも重複していれば、クライアントにデータの再入力を促す。どちらも重複していなければ、ユーザ作成処理(永続化)を行う。
というようなものです。
ユースケースには、純粋に上記のようなロジックだけを書かなくてはいけません。
「システムで実装するドメイン(事業領域)に関する概念とその扱いを目立たせて表現する」には、次に紹介するいくつかのことを意識するのが有効な手段です。
ドメイン駆動設計を実現するために意識すべきこと
リポジトリクラスを活用する
前項では、ユースケースで「ユーザ作成処理(永続化)を行う」と書きました。
しかし実際には、永続化処理や検索などといったデータベースとのやり取りは専用の別クラスに逃がす必要があります。
そのクラスがリポジトリ です。
DBとのやり取りは煩雑で、ユースケースに書いてしまうとコードが長くなりドメインオブジェクトに関わるロジックが分かりづらくなってしまうからです。
クラス間の依存を管理する
依存とは、あるクラスを部分的に変更すると別のクラスを変更する必要が生じるというものです。
クラス間の依存が高すぎると、少ない変更で多くの箇所を変更しなくてはいけなくなり、非効率的です。
しかし、依存を極力排除しようとするとクラス間で整合性が取りづらくなってしまいます。
大切なのは、依存を管理し、有効活用することです。
依存には、
・親クラス-子クラスの関係
・抽象クラス-実体クラスの関係
・参照-被参照の関係
などがあります。
例えば、リポジトリクラスでは、メソッド名だけを記述する抽象クラスとその処理内容を記述する実体クラスを作成し、リポジトリで行うメソッドをわかりやすくするなど、依存を管理・利用することによってコードの可読性・保守性を上げることもできます。
ファクトリを活用する
ドメインオブジェクトのインスタンス生成には、複雑なルールが存在することもあります。
そのようなときは、ファクトリ を活用するという手もあります。
インスタンス生成に関する処理だけをファクトリに集約させることで、それ以外のビジネスロジックやドメインオブジェクトをわかりやすく表現することができます。