クリーンアーキテクチャで色々言われたから
経緯
過去クリーンアーキテクチャとはなんぞやという話で、色々揉めたことがあり
サンプルを書いたものの、あまり伝わった感じがしなかったので、TSで改めてサンプルを作ってみた。
中身の話
中身に関しては、UI入口 → Controller → Usecase → Presenter → UI出口
とデータが流れていく感じにしている。
気を使っている部分は、各階層が自分の役割を行うこと。
各層は、次の層へ情報を引き渡すので、次の階層のことを考えてデータを渡す。
データを取りに行く部分に関しては、UsecaseにRepositoryを注入してあげて
Gateway呼び出して取りに行く感じ。(追記: ちょっとしょっぱかったので直した)
ポイントは:
- 各層を小さく定義
- ライブラリは一切使用していません
- とりあえずTSの環境に貼れば動く
余談
最初は適当に作ってたのに、だんだんこだわりだして、Pipelineまわりが面白いことに。
TSの型で守ってる部分はあれど、どうしても実行するときにany的な動きをさせるしかなかったのが残念。
世の中のライブラリのすごさに感動します。
コード本体
解説
共通定義
全体で使うものの定義。
各レイヤーの結果を、一律で扱いたかったので、結果をラップするための構造を作っておいた
Entities
このシステムを表すデータ構造部分。「システム内でどんなものを扱うのか?」がここに来る。
たまにDatabaseのSchemaとかと一緒くたにされがちだが、
- Databaseは、データの保存場所
- Entitiesは、システム内のデータ構造
なので、構造が同じであっても別物。
このサンプルでは、ユーザーデータを例にあげていんだけど
データ保存領域ではデータは配列で管理されている。
だけど、システムではユーザー単体のデータしか扱わない(リストを扱わない)
この場合、Entitiesに、ユーザーがリスト化された構造は存在しないことになる。
Driver
データに実際にアクセスする部分、。今回は、オンメモリのデータしか扱うつもりがなかったので
ただの配列を利用している。
Driverは、Gatewaysから使われるとして、Gatewaysが言う通りにDriverは実装する必要があるので
implementしているGetewayToDriverUserの持ち主はGatewayになる。
要するに、「ゲートウェイがこの形式でくれ」と指示したとおりにDriverは実装してる
Gateway
一方で、Gatewayはというと、Driverに「この形式でくれ」と指示しつつも、
Usecaseに使われる存在なので、Usecase側の要求に答える形で実装する。
なので、GatewayUserは、Usecaseの持ち物である UsecaseGatewayUserInterface をimplementsして実装を行っている。
Driverに「この形式でよこせよ」って指示しつつも、Usecaseには逆らえない可愛そうなGateway
Usecase
このシステムの中核となる、システムの純粋な振る舞いを定義する部分。
自分を覆う層には「私が言う通りにデータをくれ」「私はこの通りにしかデータはださない」
の両方を定義している。
コレには理由があって、前述の通りUsecaseは純粋であることが求められると考えている。
なので、振る舞いに外界の要素が鑑賞しないよう、すべて自分の思い通りに入力を受け取り出力を行う。
だからこそ、しがらみを感じず、目的に向かってのみ処理を行える。
Controller
外界からの入力を、Usecaseに翻訳してあげる優しい仕組み。Adapterみたいなものだろうか。
なので、入力データを、Usecaseが言う通りの形(UsecaseInputGetUser)に合わせこんでいる。
Presenter
こっちは、Usecaseの出力を、外界が読める形に翻訳する仕組み。
仕組みとしてはトランスレーターといった感じ。
Usecaseが言ってる言葉は、利用者が受け取りづらいことがおおいので、タダのデータを
利用者がつかえるデータに作り変える感じにしている。
構造的には、
- 受け取ったデータの変換(presenterGetUser)
- フォーマッター(Jsonで吐き出す presenterJson / nameだけとりだす presenterName)
の2層構造にしている。
UI
ここはUIを模した構造にしている。サンプルコードなので uiEntryGetUser がエントリポイントみたいなもの。
すべてのレイヤーを通過すれば、最終的に uiExitGetUser に返ってくる仕組み。
たまに見かけるのは、return等で結果を戻しているケースだけど、あれをやってしまうと
各層の中を逆走していくイメージになってしまう(各層の戻りで処理を挟めてしまう)ので、あまり良くないと思っており、層に入ったら出て、次の層という動きを、以降の処理で実現している
Pipeline
各層を繋げて実行するための仕組み。
前述の通り、returnなどでデータを繋ぐ場合、戻りの流れで層の中を移動してしまい、書き方によっては思想を壊してしまうような動作ができてしまう。
それを抑制したかったので、層に入る → 出る → 次の層に入る を繰り返す仕組みをつくることで、一方通行の動きを行うための仕組み
実行部分
最終的に、Pipelineはこんな感じで使うことが出来る。
fromで始点を指定して、thenで層を繋げていく。
uiEntry → Controller → Usecase → Presenter(Translate → Formart)→ uiExit
って感じで処理を繋げており、各層のreturnが次の層のInputになる。
tapは、中身を覗き見するためのデバッグ機構で、この層何吐いてるの?ってのが見られる。
最後に、runを呼ぶと、すべての層にデータが一気に流れる。引数を指定すると
fromで指定した層の入力になるんだけど、今回は不要だったか(なんで俺1を渡してんだろ・・・)
Discussion