書籍 [ドメイン駆動設計入門] から学んだこと

書籍 [ドメイン駆動設計入門] から学んだこと

はじめに

今回は [ドメイン駆動設計入門] を読了しましたので、こちらから学べたことをまとめたいと思います。

前提

筆者のプログラマとしての経歴はこんな感じです。

  • 読書当時、社会人としてプログラムを組んだことはVBA1年くらい
  • web業界は完全に素人
  • 学生時代と趣味でC言語pythonをかじっていた

ドメイン駆動設計とは

僕の理解では、ようは作りたい内容に合わせて、世の中のモノやシステムの仕組みを真似てプログラムも実装しましょうということだと思っています。

例えば「ペン」と一言で言っても、「道具としてのペン」と「商品としてのペン」は持つべき属性もライフサイクルも違います。 それぞれのシステムごとに違和感のない形で実装し、システム間を渡る際に、自身のシステムにあったデータに置き換えて使用するイメージなんじゃないかなと。

Image

ドメインモデルの個人的イメージ

ドメイン

モデルの個人的イメージ。 店舗という中のシステムでは、ペンというモノの属性は値段や色味とかですが、 購入という手続きを経て(例えば)勉強というシステムへ移ると、店舗とは違い「残量」といった属性が増え、「値段」という属性は不要のためなくなる。 また、メソッドもそれぞれのシステムにあったものを持つ。

設計方針

この設計をするにあたり、必要な考え方がいくつかあるようです。

バリューオブジェクト

まずはじめに書かれていた「バリューオブジェクト」。 ある値とそのgetterのみをメンバにもつクラスです

class name
{
    private $value;
    construct(string $name)
    {
        $this->value = name;
    }

    public function is()
    {
        return $this->value;
    }
}

何が良いか

  • 間違えて値を書き換える心配がない
  • 値自体に意味を持つ

上記の例では、ただ

public $firstName;

と定義するだけでも良いですが、文字列であればなんでも書き換えられてしまいます。 複数人で開発する場合、特に注意して扱わなければならないデータはバリューオブジェクト化しておくことで、 意図しない上書きをなくす→不具合を減らすことができます。

エンティティ

ドメイン駆動のためのものでもないですが、あるモノを一塊のクラスとして扱うための考え方です。 例えば上記勉強環境でのペンの例ではこんな感じになるでしょうか。

class pen
{
    private $name;
    private $inkColor;
    private $remainingInk;

    construct(Name $name, Color $color, RemainingInk $remainingInk)
    {
        $this->name = $name;
        $this->inkColor = $color;
        $this->remainingInk = $remainingInk;
    }

    changeInk(Color $color)
    {
        $this->inkColor = $color;
    }
    ...
}

何が良いか

おそらく、基本的にはこのエンティティを1データとしてやりとり(データベース保存/取得、外部連携など)するのでしょう。 クラスの基本中の基本のような使い方だと思います。 これはドメイン駆動設計に限らず、うまく使えるように日々考えるようにします。

ドメインサービス

基本的にはエンティティが振る舞いを持ち(上記例ではインクの色を交換するという振る舞いを持っています)、後述するアプリケーションサービスが操作する流れになります。 しかしそれだけでは不自然な記述となる場合があります。

例えば重複確認や要素の移動など、明らかな第三者がいるけど、アプリケーションサービスが行うほど大掛かりなモノではない操作は、ドメインサービスという層で処理をします。

「1種類以上複数個のエンティティを操作する小さな閉じた環境」のようなイメージだと思います。

店舗を例に取ると、次のような感じでしょうか - 店舗:アプリケーションサービス - 店員:ドメインサービス - 商品:エンティティ - 陳列棚:エンティティ

  • 商品を陳列棚Aから陳列棚Bに移動する処理を書きたい
      • 陳列棚が商品を自ら奪いに行くのは不自然
      • 商品が自ら陳列棚を移動するのは不自然
      • 店舗自体が陳列棚の商品を入れ替えるには大層すぎる
      • 店員さんが間を受け持ち、陳列棚の商品を入れ替えるのが一番自然

何が良いか

プログラム的にではなく、あくまでもコードを自然言語として読んだ場合に違和感がないかという観点がすごく大事だと感じました。 変数名やメソッド名も気にしつつ、一番自然な英語になるよう意識するだけでも綺麗なコードが書けるんじゃないかなと。

アプリケーションサービス

ある一つのシステムのことをアプリケーションサービスと定義しているようです。 それだけで一つのサービス・システムとして完結するようにします。 一番イメージしやすいと思うので特記することはないかと。

上記の例では「店舗サービス」

依存注入

これもドメイン駆動設計ならではの考え方ではないと思いますが、すごくコードが綺麗になったので記述します。 「依存注入」って言葉が意味不明すぎてとっつき難かったのですが、ようは あるクラスで使用するオブジェクトをコンストラクタの引数で渡してあげるようにすることのようです。

メソッドの中でオブジェクトをインスタンス化するのをやめて、外部で定義して引数として渡すことで、 クラスそれぞれが疎結合となり、管理しやすくなるという考え方です。

僕はphpのlaravelを使いますが、laravelの場合providerが依存注入を定義するところとなっています。

何が良いか

とにかくコードがスッキリします。 いろんなところでインスタンス化することがなくなるのでテストも書きやすく、またテスト用のクラスを作って引数で渡してやることで、他のクラスができていなくてもある程度の動作確認ができるようになります。

難しかったけれど習得して良かったと思えるものでした。

実装してみて

今回簡単なタスク管理アプリ(それでも人生初、めちゃくちゃ大変でした…。不具合も既に見つかってます笑)を作成してみたのですが、バック側はこのドメイン駆動設計を意識して作ってみました(取り入れるべきところだけ)。

実践してみた感想ですがめっちゃがんじがらめになりました。 僕の設計がまずかったのもあると思いますが、おそらく

  • 中規模以上のアプリを複数人数で実装する
  • コアでめちゃくちゃ重要なデータを操作する

際に威力を発揮する設計思想のように感じました。 だってあんな小さなアプリでどんだけクラス作るのか… 幸にも不幸にも堅牢にできてしまったので、機能を足すときに少しでも不備があるとめっちゃ怒られるわけで…。

でも、上でも述べたように、自然のふるまいになるようなコードの作りを心がけるのはどんな設計思想を取り入れても大事な考え方だと思うので

  • そのシステムで自然な振る舞いができるクラス作成を心がける
  • そのために登場人物を操作する複数の層の存在に気を配る

ことをこれからも続けようと思います。