エンティティについてまとめていきたいと思います。
・ドメイン駆動設計とは
・モデル
・値オブジェクト
・エンティティ ←ここ
・リポジトリ
・DIP
ドメインを実現させるためのモデルのひとつ、エンティティのお話です!
前回の値オブジェクトとは対になる部分が多いので、なるべく比較しながらまとめていきます。
☝エンティティとは
エンティティも値オブジェクトと同じく、モデルをクラスとして表すものになります。
値オブジェクトとなにが異なるかというと、割と真逆の性質をしています。
値オブジェクトが値を保持するオブジェクトであるのに対して、エンティティは値の状態を管理するオブジェクトです。
人間で例えると、田中さんというエンティティがあり、田中さんは体重・身長・居住地の値オブジェクトを持つ(管理している)形です。
さて、田中さんは生きているので、体重が変化したりお引越ししたりすることもあるでしょう。
ということで、エンティティは値オブジェクトとは異なり、変化していく性質を持ちます。
以下に具体的に違いをまとめていきます。
- 等価性ではなく同一性で判断される
- 変更できる(mutable)
- ライフサイクルがある
わたしクエスチョン
あれ?値オブジェクトは不変じゃないっけ?体重という値オブジェクトは変化するのか?
わたしアンサー
値オブジェクトは不変です!
たとえば体重という値オブジェクトが65kgから60kgに変化することはありません。
あくまで、値65kgの値オブジェクトと、値60kgの値オブジェクトを入れ替えるのです。
そしてエンティティから見たときに、値オブジェクトが入れ替わる=状態が変化した、とみなすことができるのです。
☝等価性ではなく同一性で判断される
値オブジェクトの説明として、
プロパティ値が同じオブジェクトは同じものとしてみなす(等価性)ということを書きました。
値オブジェクトはオブジェクトである=オブジェクト型は同一性(住所が同じかどうかで判断する)ため、苦肉の策で、値オブジェクトにequals()みたいなメソッドを使って判定
エンティティは、このオブジェクト型の同一性の性質を踏襲します。つまり住所が同じかどうかで判断します。
$a = {count: 5};
$b = {count: 5};
このaとbは、プロパティ値は同じですが、メモリ上の住所が異なるためイコールとはなりません。
人間で例えるなら、身長が165cmの人が2人いたとして、同一人物かと言われるとそうではないよねって感じです。
また、エンティティの変化を管理するために必要な要素が識別子になります。
識別子は、エンティティに割り振られたユニークなIDです。
人間で言うとマイナンバーみたいなものですね。
変更したり情報を追加したりするときは、エンティティのIDを指定して、対象を特定して操作することになります。
またちょっと踏み込んだ話をすると、データベースでエンティティの情報を操作するときに、エンティティがユニークなIDを持っていると、データを容易に変更することができます。
☝変更できる(mutable)
Immutable(不変)な値オブジェクトと違い、エンティティはプロパティ値を変更できます(Mutable)。
田中さんというエンティティの体重は変化するように、状態を変更できるものになります。
逆に、変化が全く起こらないものはエンティティでしょうか。
それはきっと値オブジェクトにすべきものかと思います。
変更できるエンティティと、変更できない値オブジェクト。これが二者の大きな違いです。
☝ライフサイクルがある
これまでの話の総まとめてきな話になりますが、エンティティにはライフサイクルがあるのが特徴です。
人間と同じですね。ゆりかごから墓場までと言われるように、エンティティにも、ドメインに応じたライフサイクルというものがあります。
勘違いしやすいですが、ここでいうライフサイクルとはnewしてかメモリから消えるまで、ということではなくあくまでドメイン上での扱われ方の話です。
例えばオンラインショッピングのシステムというドメインで、注文というエンティティがあるとします。
状態として、「ステータス」という値オブジェクトを持つとしましょう。
注文エンティティは、ステータスを変化させながら以下のようなライフサイクルをたどるでしょう。
- 確定
- 支払い済
- 発送済み
- 完了
登録会員エンティティであれば、「入会→休止→退会」のようなサイクルを持つでしょう。
このように実際の業務の流れに沿ったライフサイクルを持つのがエンティティになります。
☝エンティティを使うとなにがいいのか?
エンティティは一体何なのか、がわかってきたところで、
じゃあなんでエンティティを使うのか?ということになってきます。
値オブジェクトは、例えばstringには持たせられない固有のふるまいを持たせられる、といったメリットがありました。
エンティティを使うメリットは大きく3つあるかと思います。
1.状態変化のルール違反を防げる
例えば先程の注文エンティティの「ステータス」について、このステータスが「発送済」に変更されるには、支払済であることや荷物Noが存在するか、などの確認が必要だとします。
このとき、私のような無知エンジニアが、「うーん、支払済みから3日経ってたら流石に発送済だろうから、ステータス変更していいでしょ」と以下のようなコードを組んでしまったらもう大変です。
class Order
{
private string $id; // 識別子
public Status $status; // 値オブジェクト
public function __construct(string $id, OrderStatus $status)
{
$this->id = $id;
$this->status = $status;
}
.....
}
// 無知操作
$pastDay = ... // 経過日数計算
if($pastDay > 3) {
order.status = "発送済";
}
このような悲劇を防ぐことができるのがエンティティの真髄になります。
上のコードでは、わざと[public Status $status;]のようにpublicにしましたが、これをprivateとし、無知なエンジニアが直接変更できないようにします。
その代わり、Statusを入れ替えるメソッドを用意します。
class Order
{
private string $id; // 識別子
private Status $status; // privateにした
public function __construct(string $id, OrderStatus $status)
{
$this->id = $id;
$this->status = $status;
}
.....
/**
* 商品を発送するメソッド
*/
public function ship(): void
{
if (...){} // 発送前の確認や、発送手続きを行う
$this->changeStatus(Status::SHIPPED); // 確認して初めてステータスを変える!
}
}
// 無知エンジニアは無知操作ができない
order.status = "発送済"; // privateだからアクセスできないよ!
// その代わり、ship()メソッドを呼び出すことで、確実に必要な処理を実行できる
order.ship(); // これで完結!
このように、エンティティに定義したメソッドに、ドメインのルールを閉じ込めておくことで、変な形で状態を変更されることを防ぐことができる、というメリットがあります。
ちなみにこのprivateにして勝手に書き換えられないようにする、というのは値オブジェクトでも同じでしたね。
可変なエンティティでは、値を書き換えるためのメソッドを用意して、クラス内で変更を行います。
2.状態が変わっても追跡できる
エンティティは識別子を持つため、例えばID:101のエンティティの状態変化を追っていくことができます。
体重が100kgになり北海道に引っ越した田中さんでも、マイナンバーを照合することで、あああの田中さんだね、と追っていくことができます。
もしIDを持たなければ、オブジェクトの性質により別物になってしまうため、同じものとして追っていくことができません。
3.システムの仕様をまとめて実装できる
これは1.にも通じることですが、ライフサイクルに必要な仕様をエンティティにまとめておくことができます。
仕様変更があったら、そのエンティティを見て修正するだけで済みます。
私のような無知エンジニアが初めて見たときでも、エンティティに実装されている内容を見れば、ドメインでどのようなルールがあるのかわかり、仕様書のような役割を持つことになります。
──────────────────────────────
ということで、「エンティティ」についてでした。
次はエンティティのデータを保存する役割をもつ、リポジトリについてまとめていきます!
.png)
.png)
0 件のコメント:
コメントを投稿