のんラボ

社内でドメイン駆動設計入門の読書会 #3

2020/03/04 2020/03/07 社内でドメイン駆動設計入門の読書会 #3

社内でドメイン駆動設計入門の読書会 #3

こんにちは。Nonです。

今回も会社で読書会をしている話をしようと思います。

内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。内容が気になる方は購入しましょう!

前回:会社でドメイン駆動設計入門の読書会 #3

読んでいる本

読んでいる本はこちらのドメイン駆動設計です。

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本


以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。

そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。

進行方法

読書会の進行方法は

  1. 今回読書の対象にする章を決める。
  2. 20分間その章を読む
    1. 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう
  3. その後40分間で、その章に対する疑問や考え方をディスカッションする
  4. 1〜3を毎週定期的に行う

という進行方向となっています。

社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。

今回読んだ内容

第3章のライフサイクルのあるオブジェクト「エンティティ」です。

  • エンティティとは
  • エンティティの性質について
    • 可変である
    • 同じ属性であっても区別される
    • 同一性を持つ
  • エンティティの判断基準としてのライフサイクルと連続性
  • 値オブジェクトとエンティティのどちらにもなりうるモデル
  • ドメインオブジェクトを定義するメリット
    • コードのドキュメント性が高まる
    • ドメインにおける変更をコードに伝えやすくする
  • まとめ

僕はこのエンティティというのが、DDDを勉強していて一番わからないことでしたので、読むのが楽しみでした。

ディスカッション

エンティティってなに?

最初は私の質問でした。

そもそもエンティティという言葉に馴染みがなく、エンティティとは言葉としてどういう意味を持つのかすら怪しいまでありました。

そこで意味を調べてみることにしました。

https://ejje.weblio.jp/content/entity

主な意味
実在、存在、実在物、実体、本体、自主独立体

Google翻訳では実体という意味を持つらしいです。

更に、私でも知っていたER図はEntity Relationship Diagramというとその時に教えていただきました。

ただ、ER図のエンティティと違うのは「振る舞いを持つ」ということですね。
ER図はDBの構造を示すので、ただの値の集合体ですが、この著書では振る舞いを持ちます。

値オブジェクトが、プログラムの知識をしめすなら、エンティティはその知識を集め、振る舞いを持たせることで実体にするという考えなのかもしれません。

値オブジェクトと違い、可変である

これは本にも記載されていますが、値オブジェクトと違い可変です。

copied.class ProductId
{
    private $id;

    public function __construct(int $productId)
    {
        $this->id = $productId;
    }

    public function toInt(): int
    {
        return (int)$this->id;
    }
}

上記のように値オブジェクトは、一度生成したら、値を変更することはできませんが、

copied.class Product
{
    /** @var ProductId */
    private $id;

    /** @var ProductName */
    private $name;

    /** @var ProductPrice */
    private $price;

    public function __construct(ProductId $id, ProducrtName $name, ProductPrice $price)
    {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
    }

    public function changePrice(ProductPrice $price)
    {
        $this->price = $price;
    }
}

エンティティは上記の用にクラスの持つ情報を変更することが許容されています。

更に変更する際に代入で変更するのではなく、振る舞いで属性を変更することになります

ライフサイクルをもつって何?

私達は、「プログラム、アプリを使用する上で変更が生じること」と表現しました。

ここでは生と死と変更と表現されていますので、それと同じですね。

そう考えると、LaravelやCakePHPのModel機能にも納得ができるような気がしてきました

これまで、私はMVCモデルのMをどんなコードを書けばいいのかわかりませんでしたが、この章を見た後に考え直したらこうなりました。

copied.// ProductはModelを継承
$model = new Product();
$product = $model->find((int)$id);

// ライフサイクル
$product->changePrice(200);

// 先取りになってしまうが、永続化
$product->save();

class Product extends Model
{
    public function changePrice(int $price): self
    {
        $this->price = $price;
        return $this;
    }
}

上記はModelクラスをEntityに当てはめたときのコードですが、不自然じゃなさそうです。

この章を読むまでは

copied.// ProductはModelを継承
$model = new Product();
$product = $model->findByName((string)$name);

class Product extends Model
{
    public function findByName(string $name): Product
    {
        return self::where('name', $name)->get();
    }
}

のような、ただのデータベースに対するInterfaceのような使い方をしていました。

ということはライフサイクルのあるオブジェクトを操作するときはupdateで、入力されている値を操作するのではなく、一度Entityとして取得したあとにsaveするような動作の方がいいのかもしれません

copied.$id = $request->get('id');
$name = $request->get('name');

// ProductはModelを継承
$model = new Product();
$product = $model->find((int)$id);

// IDを使った強制的な更新のように見える
$product
    ->update([
        'name' => (string)$name,
    ]);

と書くより、

copied.$id = $request->get('id');
$name = $request->get('name');

// ProductはModelを継承
$model = new Product();
$product = $model->find((int)$id);

// IDで参照したエンティティに対して、振る舞いで名前変更し、それを永続化
$product
    ->changeName((string)$name)
    ->save();

と書いたほうがライフサイクル的。

この例ではModelクラスを使用しているので、この著書のいうところのエンティティでは無いところに注意して下さい。

あくまでも、Modelクラスを利用してエンティティ的な動作をさせてみたというだけです

何かを変更するとき、その変更されるものが他に影響するときのライフサイクル

私は今まで、何か要望が来たら「データ」から考えて設計する、DBから設計する手法をしていました。

しかしDDDはデータベースのデータから設計するのではなく、やりたいこと、叶えたいことすなわちドメインから設計をしていくという手法です。

考えを改めてみると、ライフサイクルですから何かが変更したときに、同期するように他の変更も変わるということはありそうです。

例えば、

copied.$id = $request->get('id');
$birthday = $request->get('birthday');

// 現在の日付と誕生日から年齢を計算する処理
$old = DateUtil::calcOld((string)$birthday);

$user = new User();
$user = $user->find((int)$id);

$user
    ->changeBirthday((string)$birthday)
    ->changeOld((int)$old)
    ->save();

class User extends Model
{
    public function changeBirthday(string $birthday): self
    {
        $this->birthday = $birthday;
        return $this;
    }

    public function changeOld(int $old): self
    {
        $this->old = $old;
        return $this;
    }
}

とするよりも、

copied.$id = $request->get('id');
$birthday = $request->get('birthday');

$user = new User();
$user = $user->find((int)$id);

$user
    ->changeBirthday((string)$birthday)
    ->save();

class User extends Model
{
    public function changeBirthday(string $birthday): self
    {
        $this->birthday = $birthday;
        // 現在の日付と誕生日から年齢を計算する処理
        $this->old = DateUtil::calcOld((string)$this->birthday);
        return $this;
    }
}

としたほうが、感覚的にも自然に変更できますので、ライフサイクル的ですね。

最後に

ディスカッションで出たのはこんなものでしょうか?

値オブジェクトにエンティティがでてきたので、ディスカッションの中でも永続化の単語や、サービスの単語が飛び出してくるようになりました。

一応、その章以外の話にならないように意識しながらディスカッションを進めているのですが、材料が揃ってくるとその先について考えたくなってしまいますね。

今回はModelクラスに困惑した私が一つのブレイクスルーを手にした回でもありました

次回はドメインサービスですね。

その時はよしなに。

.