んだ日記

ndaDayoの技術日記です

ユースケース実践ガイド 第2章の感想

こんにちわ、福岡でエンジニアをやっております、んだです。

今回も、「ユースケース実践ガイド―効果的なユースケースの書き方」の感想です。今回は第二章です。

第2章で勉強になった箇所

第2章で勉強になったのは、

「サブ目的」という概念

です。

「サブ目的」という概念について

第2章ではサブ目的というワードが登場します。

サブ目的をイメージしやすくするために、例として

「管理者がユーザー情報をPDF印刷する」

というユースケースを考えていきます。

「管理者がユーザー情報をPDF印刷する」というユースケースですが、このユースケースが達成されるためには、どんなフローが必要になるでしょうか?

たとえば、こんなフローを経てユースケースが完遂するとします。

  1. ユーザー情報をデータベースから取得する
  2. ユーザー情報を元にPDFデータを生成する
  3. プリンタに印刷のジョブを飛ばす

この1つ1つのフローがサブ目的です。

通常、1つのユースケースは、こうしたいくつかのサブ目的で構成されています。

サブ目的をどう活用するか?

さて、このサブ目的ですが、どのように活用すればよいかについて、本書の解説と僕の活用法をお伝えします

サブ目的の失敗条件・失敗処理のイメージを膨らませる

システムがサブ目的のひとつを達成しようとしているときに失敗に出くわすことがあります (p,35)

サブ目的は出して終わりではなく、「失敗」に着目することでドメインの理解に効果を発揮します。

ユースケースはいくつかのサブ目的で構成されると書きましたが、このサブ目的は失敗する場合が当然あります。

たとえば、先ほどの例の中では

  1. ユーザー情報をデータベースから取得する
    →存在しないユーザーを取得しようとして失敗

  2. ユーザー情報を元にPDFデータを生成する
    →データ生成時に失敗
    →取得したデータに不足がありデータ生成が失敗

  3. プリンタに印刷のジョブを飛ばす
    →プリンタが死んでてジョブ送信失敗
    →ジョブは飛んだけど、プリントアウト失敗

などなど、ざっと考えただけでも、いろんな失敗パターンがあります

サブ目的が失敗した時にはどうするか?をユースケース記述を作成しながら、想像し、その場合のフローをどうするか?を考えていきます

さて、これだけでも、実装のイメージが湧いてきそうですが、この失敗条件・失敗処理を考えることは同時にテストコードもしくはテストケースの観点にもなりそうです。

サブ目的を切り出して、その失敗条件や失敗処理をユースケース記述時に考えておくことは、後の実装にかなり有用でした。

サブ目的をコードに落とし込んでみる

さてここからは、ユースケースとサブ目的をどのように実装するかを考えてみます(あくまでも僕の考えであり本書には登場しませんのでご了承くださいませ)

まず、このサブ目的をどのようにコードに落とし込むか?の1つ目のアイデアを書いてみます

サブ目的を素直にクラスに置き換えて、Servicesに置く

サブ目的を素直にクラスに置き換えて、Servicesに置いてみます

App 
 ├ UseCases/
     ├ PrintOutUserData/ 
          PrintOutUserDataUseCase.php // 管理者がユーザー情報をPDF印刷する
 ├ Services/
     ├ PrintOutUserData/
          FetchUserDataService.php // ユーザー情報をデータベースから取得する  
          GeneratePDFDataService.php   // ユーザー情報を元にPDFデータを生成する  
          TransferJobToPrinterService.php // プリンタに印刷のジョブを飛ばす  

まず、UseCases/にユースケースを表現したクラスを置きます。

そして、サブ目的の一つ一つはServices層に置きます。

UseCasesもServicesもPrintOutUserData/ というディレクトリの下に配置し、1対1の構造にしておきます。そうすることで、仕様の変更や不具合が起きたときには、PrintOutUserData/だけを読めばよくなるので修正がしやすいはずです

そして、この切り出したServicesをUseCasesのPrintOutUserDataUseCase.php で呼び出します。 また、それぞれのServiceはInterfaceを定義して、UseCaseは、Service層のInterfaceを呼び出すようにします。

PrintOutUserDataUseCase.phpですが、こんな実装アイデアが湧きました

class PrintOutUserDataUseCase implements PrintOutUserDataUseCaseInterface
{
    public function __construct(
        private FetchUserDataServiceInterface $fetchUserDataService,
        private GeneratePDFDataServiceInterface $generatePDFDataService,
        private TransferJobToPrinterService $transferJobToPrinterService
    ) {
    }

    public function print(int $userId): bool
    {
        try {
            $userData = $this->fetchUserDataService->fetch($userId);
            
            $pdfData = $this->generatePDFDataService->generate($userData);
            
            $this->transferJobToPrinterService->transfer($pdfData);
            
            return true;
        } catch (Throwable $e) {
            throw new Exception($e->getMessage());
        }
    }
}

切り出したServicesを呼び出し、ユースケースを達成する実装です

ただそのまんまServiceを手続きに従って呼び出しているだけで、、、この実装にはどことなく違和感があります。まだ、改善の余地がありそうです。

サブ目的の変更可能性を考えてみる

さて、前述のコードではユースケース記述に出てきたサブ目的をそのままクラスにしてみました。

ここから、さらにもっと実装がよくならないか?を考えていきます。

実装を改善する観点はいくつもあると思いますが、今回は「サブ目的の変更可能性」という観点で考えていきたいと思います

  1. ユーザー情報をデータベースから取得する
  2. ユーザー情報を元にPDFデータを生成する
  3. プリンタに印刷のジョブを飛ばす

さて、この3つのフローの中で今後変更がありそうな部分はどこでしょうか??そして、変更がなさそうな部分はどこでしょうか?

、、怪しいのは「PDF」 です。

現状の仕様ではPDFデータで、と指定がありますが、データ形式にはPDFだけではなく、さまざまなバリエーションが考えられますよね。

一方で、変更がなさそうな部分は

ユーザー情報印刷したい」

でしょうか。もちろん、業務内容にもよりますが、ユーザー情報を印刷したいという要求は変更がなさそうです。

となると、たとえばですか、こんな仕様の追加や変更が考えられます

「PDFデータとテキストデータ、どちらかを選択してプリンタアウトできるようにして欲しい」

この仕様変更に対してはGeneratePDFDataServiceInterface.phpは根本的に変える必要があります。PDFを生成するだけのクラスになっているので、他の形式には対応できないからです。

これは良くない。

「どんな仕様変更があるかは予知できないが、どこが変更になりそうかは予想できる」とボブおじさんか誰かがゆーてましたよね。

あらかじめ変更可能性がありそうなところは、抽象度を上げて変更容易な実装したい。

という視点で考えると
GeneratePDFDataServiceInterface.php // ユーザー情報を元にPDFデータを生成する

GeneratePrintingDataServiceInterface.php // ユーザー情報を元にプリントするデータを生成する

と変更し、PDFだけを生成するクラスではなく、プリントアウトしたいデータ形式を外側から受け取ってプリントするデータを生成するクラスにすると良さそう

うーん、1つ目のアイディアよりは変更に強い実装なりそうです!!

議論の中でユースケースを作り上げる醍醐味

サブ目的を素直にクラスに置き換える実装から、変更可能性を考え GeneratePDFDataServiceInterface.php // ユーザー情報を元にPDFデータを生成する

GeneratePrintingDataServiceInterface.php // ユーザー情報を元にプリントするデータを生成する

に変えてみました。

実はこの変更可能性は、僕が一人で考えたものではなく、僕のパイセンとブログの下書きを見て話しながら出た想像とアイディアでした。

実際の案件でも、ユースケースロバストネス図をチームメンバで作成しながら
「ここって将来的にxxxに変更になるかもね」
「これは今は一個だけど、追加あるかもよ」

などなどの議論を重ねたのですが、ユースケースをチームで作り上げる醍醐味は、こうした議論による情報整理と理解の深化にあると思います

ユースケースを作りながらドメインについて理解を深め、変更可能性を想像し、より変更に強いソースコードを作り上げていく

議論による情報整理と理解の深化、醍醐味ですねぇ(二回目!

まとめ

さて、今回は第2章について感想を書いてみました。次回は第3章でっっす、楽しんで読むぞ。

僕から以上。あったくして寝ろよ