のんラボ

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

2020/03/10 2020/03/14 社内でドメイン駆動設計入門の読書会 #4

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

こんにちは。Nonです。

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

内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。

より具体的なコードや内容がみたい!という方は購入しましょう!

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

読んでいる本

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

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


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

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

進行方法

読書会の進行方法は

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

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

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

今回読んだ内容

第4章の不自然さを解決する「ドメインサービス」です。

  • サービスが指し示すもの
  • ドメインサービスとは
    • 不自然なふるまいを確認する
    • 不自然さを解決するオブジェクト
  • ドメインサービスの乱用が行き着く先
    • 可能な限りドメインサービスを避ける
  • エンティティや値オブジェクトと共にユースケースを組み立てる
    • ユーザーエンティティの確認
    • ユーザー作成処理の実装
  • 物流システムに見るドメインサービスの例
    • 物流拠点のふるまいとして定義する
    • 輸送ドメインサービスを定義する
  • まとめ

これは後で気づいたのですが、ドメインサービスとアプリケーションサービスを混同してしまう人は多いようです。

私はこの章を読んで自分も同じようになっていたことに気づきました。

ディスカッション

ドメインサービスいらなくね?いつ使うの?

最初は私の質問でした。

私がそのように考えた理由は、以前に勉強した、「値オブジェクト」「エンティティ」の2つがあれば、実装ベースで困ることが無いと思っていたからです。

その2つさえあれば、大抵のことができますし、ドメインサービスが具体的にどのような実装をするのかわからなかったので、不要論を発言してしまいました。

当然この著書には4.2.1「不自然なふるまいを確認する」で、具体的なコードが書かれています。

エンティティは自分に関連する情報を操作する事はできますが、自分以外のエンティティの操作はすることはできませんし、自分自身がデータストアなどに存在するかどうかを確認することも不自然とあります。

class User
{
    public function __construct()
    {
        // 省略
    }

    public function exists(User $user): bool
    {
        // 存在確認コード
    }
}

自分自身に存在するexists関数に自分自身を放り込むというこの実装はとても不自然です。
ここでようやく私も気づけました。

(正直私は自分自身の存在チェックも自分自身が行うものという日々の価値観があったので、この考えにたどり着けなかったのです。)

上記のようなときに、ドメインサービスを利用します。

class UserService
{
    public function exists(User $user): bool
    {
        // 存在確認コード
    }
}

ドメインサービスにエンティティを渡すことで、エンティティ自身が解決できないことを解決しようということですね。

ドメインサービスって存在チェックくらいしか実装できなくね?

続いても私の疑問でした。

エンティティが自分自身で解決できないふるまいってなんだろうか?と考えたことがきっかけでした。

エンティティが自分自身だけでは解決できないようなふるまいを想像したとき、なにも思い浮かばなかったのです。


Uさん『それは4.5「物流システムに見るドメインサービスの例」にあるように、エンティティ同士の値のやり取りがその例に当てはまります。』

私『正直この例よくわからなかったんですよね。』

Uさん『この例以外によく見る例は、銀行口座のお金のやり取りです。エンティティは銀行口座ですが、口座自身がその相手先に対して出金や入金を行うわけではないですよね?』

私『そりゃそうですね。出金や入金を行うのは銀行です。』

Uさん『これをプログラムに落とし込むときに、口座エンティティは入金と出金という振る舞いを持つことになりますが、自分自身のエンティティしか操作できないという制約で、入金と出金をセットにした振る舞いは実装できないということになります。そこで、口座サービスを作成して、2つの口座エンティティの操作を行うのです。』

私『なるほど、物流システムの例もそれと同じなんですね。』


$money = new Money(1000);

$fromAccount = new AccountEntity();
$toAccount = new AccountEntity();

// 出金完了
$fromAccount->withdraw($money);

// 入金完了
$toAccount->deposit($money);

class AccountEntity
{
    // 省略

    public function deposit(Money $money)
    {
        // 入金処理
    }

    public function withdraw(Money $money)
    {
        // 出金処理
    }
}

このように処理の最中にダラダラと直接の書くのではなく、ドメインサービスにまとめてしまうのです。

$money = new Money(1000);
$fromAccount = new AccountEntity();
$toAccount = new AccountEntity();

$accountService = new AccountService();
$accountService->moneyTransafer($fromAccount, $toAccount, $money);

class AccountEntity
{
    // 省略

    public function deposit(Money $money)
    {
        // 入金処理
    }

    public function withdraw(Money $money)
    {
        // 出金処理
    }
}

class AccountService
{
    public function moneyTransafer(
        AccountEntity $fromAccount,
        AccountEntity $toAccount,
        Money $money
    ) {
        // 出金完了
        $fromAccount->withdraw($money);
        // 入金完了
        $toAccount->deposit($money);
    }
}

要はエンティティの操作をするサービスなんでしょうか?

これは最近入社したばかりの方の質問でした。

私もこれまでディスカッションした内容をまとめる限りそのようなサービスを連想するので、概ね納得できています。(より正確に言うと、値オブジェクトとエンティティに持てないような振る舞いを実装するサービス

あれ、じゃあアプリケーションサービスってなんなん?

その方の質問というよりまとめで納得はしたものの、私はそのような操作はすべてアプリケーションサービスに書くべきなのでは?と思っていました。

それが完全に否定される形になったので、読書会が終わった今でも悶々とした日々を送っていますw

これは第6章の「ユースケースを実現するアプリケーションサービス」がとても楽しみですね。

最後に

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

サービスというと、値オブジェクトやエンティティと違い、具体的なものを連想できないので 「理解するのが難しい」というより、「勘違いしている」 方が多いのでは?と思っています。

この著書は簡単なプログラムが載っていますので、わかりやすいですし、GitHubに完成された実務向きのコードもあるので、より実装ベースでサービスの考え方を勉強することができますので、そちらもオススメです。

次回はリポジトリですね。

その時はよしなに。

.