ドメイン駆動設計とはなんなのか
ドメイン駆動設計(DDD)とは?なんのためにある?
一言で言えばソフトウェア開発の手法のことで、継続的にソフトウェアの品質を保っていくために採用したりする。
DDDでは何をするのか?
開発者とドメインエキスパートが協力して、ドメインを反映したモデル(ドメインモデル)を作り上げる。
ドメイン
多くのソフトウェアはある領域の問題を解決するために存在することがほとんどだが、この「ある領域」のことをドメインと呼ぶ。
会計ソフトならば、そのソフトウェアのドメインは会計業務。
ドメインエキスパート
ドメインに詳しい人や今扱っている業務について誰よりもよく知っている人のこと。
ドメインモデル
現実の事象あるいは概念を抽象化する作業をモデリングと呼び、ドメインの概念をモデリングして得られたモデルをドメインモデル呼ぶ。
具体的には、システムに必要なもの不要なものを取捨選択する作業のようなもの。
ドメインモデルを作り上げ、チーム内で認識を統一するために図などを利用することはある。
しかし、最終的なドメインモデルはコードに存在するものなのでドメインモデル単体として目に見える成果物はない。
値オブジェクト
ドメインモデルを実装したドメインオブジェクトの1つ。
プリミティブな値ではなく、システム固有の値を表現するために定義されたオブジェクト。
システムで数量を扱う際にintの全ての範囲(マイナス21億からプラス21億)が必要になることはほとんどない。
このようにシステムに最適な値がプリミティブであるとは限らないので、システム固有の値を表現するために専用のオブジェクトを作成する。
値オブジェクトが持つ性質
- 不変である
- 交換が可能である
- 等価性により比較される
値オブジェクトを作成するメリット
- プリミティブな値からは読み取れない情報を読み取れるので、コードのドキュメント性が高まる
- 不正な値を存在させない
- ロジックの散在を防ぐ(値オブジェクトにルールをまとめることで修正の際も手間が少ない)
コードを例にして値オブジェクトを見てみる
以下はユーザー名を表現した値オブジェクトの例。
class UserName
{
public function __construct($firstName, $lastName)
{
$this->isValidFirstName($firstName)
$this->isValidlastName($lastName)
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function isValidFirstName($firstName)
{
// 名前のルールが一目で分かり、不正な値(名前が3文字未満)を存在させない
if ($firstName === null || mb_strlen($firstName) < 3) throw new Exception();
}
// 省略
}
仮にこれをプリミティブな値で表現しようとすると以下のようなコードになる。
// 名前のルールが分らず、どんな値でも代入可能
$userName = "yamada taro";
エンティティ
値オブジェクトと同じく、ドメインモデルを実装したドメインオブジェクトの1つ。
値オブジェクトとの違い
属性によって識別されるオブジェクト = 値オブジェクト
同一性によって識別されるオブジェクト = エンティティ
属性で識別されるとは
名前は姓と名の属性からなるものだが、いずれかの属性が変化すると全く異なるものになる。
このようなものを属性で識別されるという。
同一性で識別されるとは
人間を例にすると名前・身長・体重など様々な属性があるが、これらは様々な要因によって変化する。
結婚して苗字が変わったとしても、別人になるわけではない。
このように、人間は属性ではなく同一性で識別される。
エンティティが持つ性質
- 可変である
- 同じ属性であっても区別される
値オブジェクトとエンティティの見分け方
- ライフサイクルが存在し、そこに連続性が存在するか
- ユーザーは作成されて不要になった際に削除される = ライフサイクルがある
アプリケーションサービス(ユースケース)
ドメインオブジェクトを組み立てて、ユースケースを実現するオブジェクト。
ドメインオブジェクトの振る舞いを呼び出す役目はアプリケーションサービスが担う。
自身の振る舞いを変化させる目的で状態を持たない。
リポジトリ
オブジェクトの永続化や再構築を行う。
永続化とはDB等にデータを保存し、復元できるようにすること。
煩雑なDB操作のコードをリポジトリに隠蔽することで、コードの可読性を高める目的がある。
コードを例にしてリポジトリを見てみる
以下はLaravelでリポジトリを実装した場合の例。
class UserRepository implements IUserRepository
{
public function findById($userId)
{
$user = User::find($userId);
// ドメインオブジェクトを構築して返す
return new User($user->firstName, $user->lastName);
}
}
以下のリポジトリを実装しない場合の例では、特定の技術に依存している。
そのため、使用している技術を変更しようとすると影響範囲が増え、修正が難しくなる。
しかし、リポジトリを利用することでリポジトリのコードを変更するだけで済む。
class Program
{
public function findById($userId)
{
//フレームワークの機能(O/Rマッパー)に依存
$user = User::find($userId);
return $user;
}
}
また、リポジトリはインターフェースを定義することで差し替えを容易にすることができ、テストの際にも価値を発揮する。
Discussion