ROBOT PAYMENT TECH-BLOG

株式会社ROBOT PAYMENTのテックブログです

「Moneytree連携」機能に関するモジュール群をクリーンアーキテクチャ化してみた話

こんにちは、請求管理ロボ開発チーム所属の塚本です。
今回は、請求管理ロボの一部をリファクタリングし、クリーンアーキテクチャ化にした話について書いていこうと思います。
請求管理ロボでは、Moneytree LINK API(Moneytree社)というサービスを使用して、請求管理ロボの請求元銀行口座にMoneytreeの口座情報を連携する「Moneytree連携」という機能を提供しています。 今回はこの「Moneytree連携」機能に関するモジュール群をリファクタリングし、クリーンアーキテクチャ化することを試みました。

なぜ「Moneytree連携」機能でクリーンアーキテクチャ化を行ったのか

1. 設計面の課題

  • 設計面で当初に考慮していなかった課題が散見され、保守性が悪化している。
    • モジュールの責務が肥大化しており、DBアクセスからAPIリクエストなど様々な処理を行っている。
    • クラス間の依存関係が複雑化している。
    • 連想配列を多用しており、データの流れが分かりづらくなっている。
    • 今後Moneytree連携に関する機能改修があるため、さらに複雑化する前に設計をスッキリさせたい。

2. 個人的な動機

  • 自分がMoneytree連携周りの機能開発を担当することが多く、ドメイン知識が身についていたため。
  • クリーンアーキテクチャの輪読会をちょうど終えたところなので、実践したいと考えたため。

クラス図と各クラスの役割

下記が「Moneytree連携」機能のリファクタリング後の設計になります。

↓各層ごとの色付けは、よく見かけるこのクリーンアーキテクチャの図から持ってきています。

出典:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
下記にそれぞれのクラスの役割などを解説します。

Controller

  • Usecase層の呼び出し口として機能します。
  • Controllerでは直接ビジネスロジックを処理しない。

ViewModel

  • MVVMパターンで言うところのViewModel。Presenterに近い位置付けの役割を担う。
  • Usecase層から返されるデータをフロントエンドが欲しい形式に整えることで、フロントエンドをビジネスロジックから分離できる。

Model

  • DBに対してクエリを実行し、データを連想配列として取得。
  • 必要に応じてDTOに変換して利用しているが、現状DTOへの変換処理はビジネスロジックに別途実装されている。

Gateway

  • Moneytreeの仕様に合わせてAPIリクエストを行う。
  • Moneytreeの仕様に則ってレスポンスデータ・リクエストデータをフォーマットする。
  • いわばSDKのようなもの。 APIリクエスト用モジュールの実装例:
<?php
/**
 * 「法人口座」系APIのリクエストを行うクラス
 */
class Gateway_Account_Requester
    extends Client
    implements Gateway_Account_IRequester{
    /**
     * @see Gateway_Account_IRequester
     */
    public function get_corporate_accounts(string $token, int $page): GetCorporateAccounts_Response
    {
            //requestはAPIリクエストを行うメソッド(Clientに定義)
        $response = $this->request(
            self::HTTP_METHOD_GET,
            Endpoint::GET_COURPORATE_ACCOUNTS,
            $token,
            '',
            [],
            ['page' => $page]
        );
        return new GetCorporateAccounts_Response($response);
    }
}

Handler

  • Gatewayに実装されているAPIリクエスト用のモジュールを用いて、ロボのユースケースに合わせてAPIリクエストを行う。 Handlerの実装例:
<?php
/**
 * Moneytreeアカウントに紐づくMoneytree法人口座を全件取得するHandler
 */
class Handler {
    /** @var \\IRequester */
    private \\IRequester $requester;
    /**
     * コンストラクタ
     */
    public function __construct(\\IRequester $requester)
    {
        $this->requester = $requester;
    }
    /**
     * Moneytree法人口座を全件取得
     * @return void
     */
    public function handle(): Handler_OutputData
    {
            $total_accounts = [];
        $page = 1;
        while (TRUE) {
            $response_accounts = $this->requester->get_corporate_accounts($token, $page++)->accounts;
            $this->accounts = array_merge($total_accounts, $response_accounts);
            if (sizeof($response_accounts) === 0) break;
        }
    }
}

Service

  • HandlerとModelをハンドリングする
  • ServiceとHandlerはUsecase層であり、ServiceはUsecase層のとりまとめみたいな役割をする。

課題と考慮事項

今回のリファクタリングで「Moneytree連携」に関するモジュール群はかなりスッキリしましたが、実はまだまだ課題があります。備忘録的に書いていこうと思います。

Gateway・Handlerの配置

Gatewayは現在SDKのように機能しており、Frameworks & Driver層(青)とInterface Adopter層(緑)の間に位置しています。そう考えると、HandlerもInterfaceを設けてInterface Adopter層(緑)内に配置されるのが正しいように感じます。

Modelの改善

現在、モデルはデータを連想配列として返し、それをDTOに変換しています。理想的には、Repositoryパターンを実装してEntityを直接取得するようにすべきです。これにより、データアクセスロジックの明確さと保守性が向上します。 Repositoryもまた、Gatewayと同様にFrameworks & Driver層(青)とInterface Adopter層(緑)の間に位置するので、RepositoryをハンドリングするInterfaceAdopter層(緑)に位置付けられるモジュールが必要です。

展望

上記の課題感を踏まえると、最終的に下記のような設計になると良さそうです。

  • リポジトリ層を追加→エンティティを返すように
  • Handlerで担っていた役割をGatewayに移行(プロダクトのユースケースに合わせてAPIリクエストする)
  • Usecase層がスッキリした

感想とまとめ

良かった

  • 各層のインプットとアウトプットがオブジェクト化されたので、データの流れが追いやすくなった
  • 処理の流れもわかりやすくなった
  • 実装力がついた
  • クラスの責務が細分化されているのでテストが書きやすい 難しかったこと
  • 実装量が多いので結構時間かかる(→ただ今後は横展開して行くだけなので労力は低いはず)
  • アーキテクチャに対する理解が甘かった
    • 気づかないうちにアンチパターン的なことをしていた
  • 当初層は展望の方のクラス図に近かったが、Entityがない状態で実装したため、アウトプットとインプットがごちゃついた印象になってしまった。 今回のリファクタリングは、難しいところこそ多かったものの、複雑化したモジュールを綺麗にすることができたのと、個人的にはアーキテクチャに対する理解を深めたり、実装力をつける良い機会になったので良かったなと思いました!



We are hiring!!

ROBOT PAYMENTでは一緒に働く仲間を募集しています!!!


www.robotpayment.co.jp