📐

クリーンアーキテクチャで色々言われたから

に公開

経緯

過去クリーンアーキテクチャとはなんぞやという話で、色々揉めたことがあり
サンプルを書いたものの、あまり伝わった感じがしなかったので、TSで改めてサンプルを作ってみた。

中身の話

中身に関しては、UI入口 → Controller → Usecase → Presenter → UI出口
とデータが流れていく感じにしている。
気を使っている部分は、各階層が自分の役割を行うこと。
各層は、次の層へ情報を引き渡すので、次の階層のことを考えてデータを渡す。

データを取りに行く部分に関しては、UsecaseにRepositoryを注入してあげて
Gateway呼び出して取りに行く感じ。(追記: ちょっとしょっぱかったので直した)

ポイントは:

  • 各層を小さく定義
  • ライブラリは一切使用していません
  • とりあえずTSの環境に貼れば動く

余談

最初は適当に作ってたのに、だんだんこだわりだして、Pipelineまわりが面白いことに。
TSの型で守ってる部分はあれど、どうしても実行するときにany的な動きをさせるしかなかったのが残念。
世の中のライブラリのすごさに感動します。

コード本体

https://github.com/risk/ts-playground/blob/main/src/cleanArchitecture/cleanArchitecture.ts

解説

共通定義

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L11-L40
全体で使うものの定義。
各レイヤーの結果を、一律で扱いたかったので、結果をラップするための構造を作っておいた

Entities

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L42-L63
このシステムを表すデータ構造部分。「システム内でどんなものを扱うのか?」がここに来る。
たまにDatabaseのSchemaとかと一緒くたにされがちだが、

  • Databaseは、データの保存場所
  • Entitiesは、システム内のデータ構造

なので、構造が同じであっても別物。

このサンプルでは、ユーザーデータを例にあげていんだけど
データ保存領域ではデータは配列で管理されている。
だけど、システムではユーザー単体のデータしか扱わない(リストを扱わない)
この場合、Entitiesに、ユーザーがリスト化された構造は存在しないことになる。

Driver

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L65-L87
データに実際にアクセスする部分、。今回は、オンメモリのデータしか扱うつもりがなかったので
ただの配列を利用している。
Driverは、Gatewaysから使われるとして、Gatewaysが言う通りにDriverは実装する必要があるので
implementしているGetewayToDriverUserの持ち主はGatewayになる。
要するに、「ゲートウェイがこの形式でくれ」と指示したとおりにDriverは実装してる

Gateway

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L89-L121
一方で、Gatewayはというと、Driverに「この形式でくれ」と指示しつつも、
Usecaseに使われる存在なので、Usecase側の要求に答える形で実装する。
なので、GatewayUserは、Usecaseの持ち物である UsecaseGatewayUserInterface をimplementsして実装を行っている。
Driverに「この形式でよこせよ」って指示しつつも、Usecaseには逆らえない可愛そうなGateway

Usecase

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L123-L168
このシステムの中核となる、システムの純粋な振る舞いを定義する部分。
自分を覆う層には「私が言う通りにデータをくれ」「私はこの通りにしかデータはださない」
の両方を定義している。
コレには理由があって、前述の通りUsecaseは純粋であることが求められると考えている。
なので、振る舞いに外界の要素が鑑賞しないよう、すべて自分の思い通りに入力を受け取り出力を行う。
だからこそ、しがらみを感じず、目的に向かってのみ処理を行える。

Controller

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L170-L187
外界からの入力を、Usecaseに翻訳してあげる優しい仕組み。Adapterみたいなものだろうか。
なので、入力データを、Usecaseが言う通りの形(UsecaseInputGetUser)に合わせこんでいる。

Presenter

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L189-L208
こっちは、Usecaseの出力を、外界が読める形に翻訳する仕組み。
仕組みとしてはトランスレーターといった感じ。
Usecaseが言ってる言葉は、利用者が受け取りづらいことがおおいので、タダのデータを
利用者がつかえるデータに作り変える感じにしている。
構造的には、

  • 受け取ったデータの変換(presenterGetUser)
  • フォーマッター(Jsonで吐き出す presenterJson / nameだけとりだす presenterName)

の2層構造にしている。

UI

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L210-L220
ここはUIを模した構造にしている。サンプルコードなので uiEntryGetUser がエントリポイントみたいなもの。
すべてのレイヤーを通過すれば、最終的に uiExitGetUser に返ってくる仕組み。

たまに見かけるのは、return等で結果を戻しているケースだけど、あれをやってしまうと
各層の中を逆走していくイメージになってしまう(各層の戻りで処理を挟めてしまう)ので、あまり良くないと思っており、層に入ったら出て、次の層という動きを、以降の処理で実現している

Pipeline

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L222-L256
各層を繋げて実行するための仕組み。
前述の通り、returnなどでデータを繋ぐ場合、戻りの流れで層の中を移動してしまい、書き方によっては思想を壊してしまうような動作ができてしまう。
それを抑制したかったので、層に入る → 出る → 次の層に入る を繰り返す仕組みをつくることで、一方通行の動きを行うための仕組み

実行部分

https://github.com/risk/ts-playground/blob/166364ed1e6b221741ebe35b73dd4e05984ba3fd/src/cleanArchitecture/cleanArchitecture.ts#L258-L273
最終的に、Pipelineはこんな感じで使うことが出来る。
fromで始点を指定して、thenで層を繋げていく。
uiEntry → Controller → Usecase → Presenter(Translate → Formart)→ uiExit
って感じで処理を繋げており、各層のreturnが次の層のInputになる。
tapは、中身を覗き見するためのデバッグ機構で、この層何吐いてるの?ってのが見られる。
最後に、runを呼ぶと、すべての層にデータが一気に流れる。引数を指定すると
fromで指定した層の入力になるんだけど、今回は不要だったか(なんで俺1を渡してんだろ・・・)

Discussion