クリーンアーキテクチャ Application Business Rules の役割


はじめに

ここ最近でアーキテクチャについて考える機会があり、これまであまり触れることがなかったクリーンアーキテクチャについて理解しようとしています。

※ 理解が間違っている部分などありましたらご指摘いただけると幸いです。

クリーンアーキテクチャについて調べようとするとよく目にするのがこちらの図。今回はこの中の赤色の部分、Application Business Rules について個人的に理解できた内容をメモしておきたいと思います。

クリーンアーキテクチャについてはこちらの記事が非常にわかりやすくまとまっていて、大変参考になりました。

Application Business Rules とは

簡単に言えば「このソフトウェアは何ができるのか」を表現する部分と理解しました。

図の赤色の部分には「Use Cases」と書いてあります。ユースケースについて Wiki にはこのように説明されています。

ユースケースは、ソフトウェア工学やシステム工学でシステムの機能的要求を含む振舞を把握するための技法である。

つまり、ユースケースはソフトウェアの動きを(大雑把に)把握するためのものであり、細かい処理の中身について表現するものではないと読み取れます。確かに、Wiki に掲載されているユースケース図を見ると「料理を食べる」「ワインを飲む」「お金を払う」など、できることについては理解できますが、具体的な食べ方や支払い方法については読み取れません。

出典
Slashme - 投稿者自身による作品  このW3C-unspecified ベクター画像はInkscapeで作成されました 。, CC 表示-継承 4.0, https://commons.wikimedia.org/w/index.php?curid=13896580による

ユースケースを実装に落とし込む

それではユースケースを実装に落とし込むにはどうすればいいのでしょうか?

上で参考にした Wiki のユースケース図を参考に考えてみます。レストランの利用者は「料理を食べる」「ワインを飲む」「お金を払う」ことができます。これをプログラム上で表現すると以下のようになります。

UserUseCaseInterface.java
public interface UserUseCaseInterface {
    void eat(Food food);
    void drink(Wine wine);
    void pay(int check, int payment);
}

ユーザーができること = 振る舞い だけを表現すればいいので、インターフェースを使います。このインターフェースを見れば、「なるほど、このレストランでは料理を食べてワインを飲んでお金を払うことができるんだな」とざっくり理解できるのではないでしょうか。

ユースケースを実現する

Use Case Interactor

ユースケース(振る舞い)だけ定義しても、その中身が実装されていないとソフトウェアとして成立しません。具体的な実装は Use Case Interactor に実装します。

Interactor はユースケースを実現するため、Entity を操作したり、操作の結果を適切な形式(Output Data)で返却したりします。実際に Entity を操作するのは Repository だったり、Output Data への変換は Presenter が行ったりするのですが、このあたりの操作を仲介してよしなに動いてくれるイメージでしょうか。

Controller から見れば、適切な形式(Input Data)で値を渡すだけで OK という状態になります。

Use Case Input/Output Port

Interactor の上下に Use Case Input/Output Port という見慣れない言葉が並んでいますが、これは何でしょうか。

冒頭で紹介した記事「実装クリーンアーキテクチャ」では、Use Case Input Port は Use Case のインターフェース、Use Case Output Port は Presenter のインターフェースとして紹介されていました。これは Controller から見ると Input Data を渡す相手は Interactor ではなく Use Case インターフェースだからだと理解しました。Controller と Interactor は直接関係を持つことなく、Use Case Input Port という中間地点(Port = 港、出入り口)を介して値をやりとりしていることを表しているのだと思います。Use Case Output Port もまたしかり。

Interactor を実装してみる

上で示したレストランの例を使って RestaurantInteractor を実装してみます。

class RestaurantInteractor implements UserUseCaseInterface {

    RestaurantRepositoryInterface repository;

    CheckPresenterInterface presenter;

    public RestaurantInteractor(RestaurantRepositoryInterface repository, CheckPresenterInterface presenter) {
        this.repository = repository;
        this.presenter = presenter;
    }

    // 省略

    @Override
    public void pay(CheckInputData inputData) {
        // 支払う金額
        int check = inputData.total;
        // 支払った金額
        int payment = inputData.payment;
        // おつり
        int change = payment - check;

        CheckOutputData outputData;
        if (change >= 0) {
            // お金が足りている場合
            Sales sales = new Sales(check);
            Boolean completed = repository.saveSales(sales);
            outputData = new CheckOutputData(change, completed);
        } else {
            // お金が足りない場合
            outputData = new CheckOutputData(0, false);
        }

        // 支払い完了: 完了メッセージの表示とおつりの返却
        // 支払い未完了: 未完了メッセージの表示
        presenter.complete(outputData);
    }
}

pay() 関数は CheckInputData を引数にとり、支払い可能であれば Repository 経由で売上を計上します(Sales エンティティの操作)。その後 CheckOutputData を生成して Presenter に渡します。Presenter は CheckOutputData の値を見て支払いが完了していれば「支払いが完了しました。おつりは xxx 円です。」的なメッセージを、未完了であれば「支払いできませんでした。」的なメッセージを表示するイメージです。

まとめ

  • Use Case はシステムの振る舞いを表すインターフェース

  • Interactor は Use Case を実現するために Use Case を実装したクラス

  • Interactor と Controller/Presenter は直接関係を持たず、Use Case Input/Output Port(インターフェース) を介して値をやりとりする