クリーンアーキテクチャの右下の図

クリーンアーキテクチャの右下の図

概要

クリーンアーキテクチャの右下の図(これでわかるかな)についてです。

この記事は二つ目です。

クリーンアーキテクチャ関連記事

◆実践クリーンアーキテクチャ(最新)
記事リンク: https://nrslib.com/clean-architecture/
※※※↑の記事はこの記事に書いている内容も網羅しています※※※

◆クリーンアーキテクチャの概要
記事リンク: https://nrslib.com/clean-architecture-old/

◆クリーンアーキテクチャの右下の図について(イマココ)
記事リンク: https://nrslib.com/clean-flow-of-control/

◆ ClArc.CLI : CleanArchitecture のクラスを生成して登録まで行うツール
記事リンク: https://nrslib.com/clarc-csharp/
github: https://github.com/nrslib/ClArc.CLI

◆ ClArc : ClArc.CLI のコア部分だけを抜き出したライブラリ
github: https://github.com/nrslib/ClArc-CSharp
nuget: https://www.nuget.org/packages/ClArc/

解説

前回のクリーンアーキテクチャの解説で使った図で意図的に無視した部分があります。

クリーンアーキテクチャの図はこちらですね。

そして今回の話の右下の図というのはこれのことです。

この図については原文においても全く触れられていません。
では、これは一体何を意味しているのでしょうか。

矢印

それぞれを繋ぐ矢印に着目してみます。

Flow of control については日本語に訳すと「制御の流れ」となり、プログラムの流れを示しているようです。
つまりプログラムは Controller から UseCaseInteractor に流れ、Presenter を終着地点とすることを示唆しています。

次に補足のない矢印についてですが、白抜きの矢印はどこかで見たことがありますね。

これは UML のクラス図における汎化を表しています。

ということで UseCaseInputPort は抽象です。
この UseCaseInputPort のアイコンをよくみると <I> という文字が書かれているので interface を表しているのでしょう。
よって、UseCaseInputPort が interface で、その実装が UseCaseInteractor になります。

同じことが UseCaseOutPutPort と Presenter にもあてはまり UseCaseOutPutPort が interface で、それを実装しているのが Presenter となります。

さて、残ったもう一つの矢印は何かというと、これは関連の矢印です。

よって Controller は UseCaseInputPort を利用し、UseCaseInterfactor は UseCaseOutPutPort を利用します。

これをまとめると

1. Controller が UseCaseInputPort(interface) を利用する(メソッドを呼ぶ)
2. UseCaseInputPort の実装である UseCaseInteractor の処理が実行される
3. UseCaseInteractor は処理の結果を UseCaseOutputPort に伝える
4. UseCaseOutputPort の実装である Presenter において、出力が表現される

となります。

そして制御という観点だけでこれを見直してみると、処理は Controller -> UseCaseInteractor -> Presenter という順序で実行されるため、見事 Flow of control に制御の流れが合致します。

クラス構成

それではこの図を実現するクラス構成を作ってみましょう。

Flow of control の順序に従って記述していきます。

まずはコントローラです。
このように inputPort を利用するだけで非常に単純なものです。

次に UseCase InputPort と UseCase Interactor です。

こちらも非常に単純です。
UseCaseInteractor はコンストラクタで受け取った outputPort に対して処理結果を伝えます。

最後の出力先となる UseCase OutputPort と Presenter は以下の通りです。

Presenter は図で見ても利用されるだけなので、コンストラクタで受け取るもの=依存するものがありません。

実践的なコード

では少し処理が書かれたプログラムを記述してみます。
文字を加工し、それを出力するだけの単純なプログラムです。

まずはコントローラです。
コントローラはあまり変わりません。データを渡して処理を任せます。

次は UseCaseInputPort と UseCaseInteractor です。

UseCaseInteractor は csv に変換するものと tsv に変換するものの二つを用意しました。

最後は UseCaseOutputPort と Presenter です。

Presenter もコンソールに表示するものとファイルに保存するものの二つを用意しました。

これらを実際に使うコードは以下のようになります。

組み合わせを全て用意しました。
こうしてみると、Controller 周りは殆ど変わらないのがわかります。

オブジェクト指向らしくポリモーフィズムを利用して書くと

このように記述できます。

例えば出力方法が増えたとき、例えば編集方法が増えたとき、Main のロジックはどうなるでしょうか。

そう。全く変更する必要がなくなります

上記のコードではメソッドで抽象を作るようにしていますが、DDD ではこの部分を Dependency Injection で解決します。


Dependency injection のモジュールとして Unity.Mvc を利用した例です。
もしも手元で動かしてみたい場合は package console で Install-package Unity.Mvc と打って nuget してください。
このサンプルコードでは Dependency injection をメソッドで登録していますが xml などで定義をしてもかまいません。

xml に定義して injection してあげれば、ソースコードをいじることなく動作を変更することができるようになります。これが最大の利点といってもよいでしょう。

省略した理由

前回のクリーンアーキテクチャにおいて意図的に右下の図に触れなかった理由としては、この作り方が MVC フレームワークにマッチしないからです。

MVC フレームワークは Controller( クリーンアーキテクチャの Controller ではなく、MVC の C ) のメソッドで Request を受け取り、その結果を Response として戻すという動作が求められます。

つまり、UseCase の処理の出力先は戻り値になるので OutputPort に通知することができません。

もし無理やりに Flow of control に当てはめるのであれば、Presenter に受け取ったデータを取得するメソッドを追加して、データを取りにいき、そのデータを Response として戻すようにします。
それはあまりにも冗長な記述になるでしょう。

これらの理由のため、前回は触れませんでした。

活躍する場所は

Presenter

変更することで製品モードはブラウザに、デバッグモードは Console に結果を出力したり、Xamarin などで iOS と Android で異なる表示の api を使って表示するときに活躍すると思います。

UseCaseInteractor

製品用モジュール以外にスタブのデータを返すテスト用モジュールを作ってテストを行うことが可能になります。

まとめ

この Flow of control の図に表れているクラス構成は「どの層においてもテストができるように」と考えて作ると自然と行き着く形になります。
イメージさえついてしまえば簡単なものですので、メリットが多いと判断できたときに採用するとまさにクリーンなコードを記述できるようになるでしょう。

Architectureカテゴリの最新記事