のんラボ

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

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

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

こんにちは。Nonです。

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

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

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

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

読んでいる本

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

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


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

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

進行方法

読書会の進行方法は

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

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

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

今回読んだ内容

  • ユースケースを実現する「アプリケーションサービス」
  • アプリケーションサービスとは
  • ユースケースを組み立てる
    • ドメインオブジェクトから準備する
    • ユーザ登録処理を作成する
    • ユーザ情報取得処理を作成する
    • ユーザ情報更新処理を作成する
    • 退会処理を作成する
  • ドメインのルールの流出
  • アプリケーションサービスと凝集度
  • 凝集度が低いアプリケーションサービス
  • アプリケーションサービスのインターフェース
  • サービスとはなにか
    • サービスは状態を持たない
  • まとめ

待ちに待ったアプリケーションサービスの章ですね。
ドメインサービスとアプリケーションサービスを混同して考えていた私にとって、とても勉強になる内容でした。

ディスカッション

ドメインサービスとの違いは?

結論から言うとドメインサービスはVO・Entityに書ききれないサービスを記述するサービス。アプリケーションサービスはユースケースを記述するサービスです。

ドメインサービスはドメインのためのサービスですが、アプリケーションサービスはユーザー(使い手)のためのサービスとも言えるかもしれません。

ふるまいを持ったVOやEntityを用意し、必要に応じてRepositoryで永続化処理を行う。
これら一連の流れそのものがユースケースなので、この内容が書かれたクラスはアプリケーションサービスという事になります。

MVCモデルにおけるコントローラーアクションと混同しがち!?

私はこの考えで進んでしまっていました。

copied.class UserController extends Controller
{
    public function store()
    {
        // ここに書かれることが、アプリケーションサービス
    }
}

アプリケーションサービスはあくまでもサービス

アプリケーションサービスはあくまでサービスですので、リクエストの処理やレスポンスの処理をアプリケーションサービスに書くことはできません。

リクエスト形式が違う場合や、レスポンスを同出力するかだけで、やりたいことは同じということは結構あります。(ターミナルからやるか画面でやるかの違いだけとか)

その時、inputoutputだけが違う内容が同じアプリケーションサービスを2つ作成することはとても管理が面倒臭くなります。

なので、こうすべき。

copied.class UserController extends Controller
{
    private $useCase;

    public function __construct(UserApplicationService $useCase)
    {
        $this->useCase = $useCase;
    }

    public function store(Request $request)
    {
        // アプリケーションサービス用のinputクラスを作成
        $input = new UserCreateInput($request);

        // アプリケーションサービス用のoutputクラスを作成
        $response = new UserCreateOutput();

        // inputとoutputをアプリケーションサービスへ渡して実行する
        $this->useCase->process($input, $response);

        // outputクラスからプロパティにアクセスし、コントローラーがレスポンス挙動の責務をもつ
        return response()->json($output->toArray());
    }
}

このときに、Dtoを採用するのも良いかもしれません。

copied.class UserApplicationService
{
    private $service;

    public function __construct(UserService $service)
    {
        $this->service = $service;
    }

    public function process(UserCreateInputPort $input, UserCreateOutputPort $output)
    {
        // ユーザー名を取得しVOへ
        $name = new UserName($input->name());

        // ユーザーメールアドレスを取得しVOへ
        $email = new UserEmail($input->email());

        // ドメインサービスを介し、永続化
        $user = $this->service->create($name, $email);

        // ユーザーIDを使用して、ユーザー用の拡張情報を取得
        $userExtendInfo = $this->service->getExtendInfo($user->id());

        // ユーザーに関わるエンティティをDtoへ渡し、Dtoを作成
        $dto = new UserDto($user, $userExtendInfo);

        // ユーザーDtoをアウトプットのふるまいをもつクラスへ渡す。
        $output->output($dto);
    }
}

こうすることで今までたくさん見てきた、責務の大きいコントローラーを少なくし、サービス(モデル)へ責務を押し付けることができます。

コントローラーはリクエストに応じて、必要なユースケース(アプリケーションサービス)を実行し、適切はレスポンスを作成することに集中することができます。

更に凝集度を下げることができる

前述したソースコードを見てもらえるとわかりますが、それぞれのふるまいが色々なクラスに分散されています。

結果的にですが、凝集度の観点からも好ましい状態にあります。

最後に

実はこれまで作成していたMain処理がそのままアプリケーションサービスだったとは驚きでした。

今回触れたのはそのアプリケーションサービスを実行するときの入力と出力の部分をできるだけ、抽象化するように変更しただけでしたね。

なにかのリクエストが来たときにユースケースを実行し、正しくレスポンスを返すところまでかけるようになりました。

次回以降はもっとプログラムの構成を整理する章となっていきそうです。

また記事にしますので、その時はよしなに。

.