TL;DR
- クリーンアーキテクチャをフロントエンドに当てはめ、React で作るクッキーストアの決済ユースケースを例に、ドメイン・アプリケーション・アダプターの 3 層への分け方を示した記事である。
- 作者はこの手法を守るべき標準ではなく、トレードオフの集まりとして紹介している。コストを削るときも、ドメインを切り出すことと依存のルールを守ることは残すよう勧めている。
内容
クリーンアーキテクチャの 3 層
作者は設計を「システムを、後で組み直しやすい部品に分けること」と捉え、要件が変わっても大きく作り直さずに済む拡張性をもう 1 つの目標に挙げる。クリーンアーキテクチャはアプリケーションのドメインにどれだけ近いかで責務を分け、次の 3 層を置く。
- ドメイン層: エンティティと、そのデータを変換するロジック。アプリケーションごとの違いを生む中心部分である
- アプリケーション層: ユースケースと、外の世界とどうやり取りしたいかを表すインターフェースであるポート
- アダプター層: UI のようにアプリケーションへ信号を送る駆動側のアダプターと、バックエンドへのアクセスのようにアプリケーションから呼ばれる被駆動側のアダプター
依存のルールでは、外側の層だけが内側の層に依存してよい。このルールを破ると、循環依存、テストのしにくさ、強い結合が生まれる。
利点とコスト
ドメインが独立するのでテストしやすく、新しい開発者も理解しやすい。ユースケースは外の世界に合わせず、外の世界をアプリケーションに合わせる。アダプターがあるので、外部サービスの変更や不具合が広がりにくい。
一方で、設計と実装に時間がかかり、小さなプロジェクトには冗長になる。過剰な設計は新しく入る人の負担を増やし、抽象化が増えるとバンドルサイズも大きくなる。
クッキーストアの設計
ドメインには、ユーザー、商品、カート、注文のエンティティと、合計金額の計算などの純粋な変換関数を置く。Email や UniqueId のような共通の型エイリアスは、どのモジュールからも使える共有カーネルにまとめる。
決済のユースケースは「不純なサンドイッチ」の形をとる。副作用でデータを取り、純粋な変換をかけ、もう一度副作用で結果を保存する。ユースケースは PaymentService や NotificationService などのポートに対して書くので、外部サービスを直接呼ばない。UI からは React Hooks 経由でユースケースを渡し、作者はこれを「ゆがんだ依存性の注入」と呼んでいる。保存先は React Context で実装し、機能ごとにフックを分けてインターフェースを狭く保つ。
改善できる点
価格を通貨と組みにしたオブジェクトにすること、層ではなく機能ごとにコードを分けること(Feature-Sliced Design にも触れている)、型エイリアスの代わりにブランド型を使うことを挙げる。ほかに、日時を引数で渡してドメインを純粋に保つことや、カートと注文がずれてきたら射影や DTO を使うことも挙げている。
実際のプロジェクトで業務ロジックが枝分かれするときは、継承を使わず、違いがはっきりするまでエンティティを複製しておくよう勧めている。互いに依存するユースケースは、小さな単位に分けて組み合わせる。最後に作者は、アーキテクチャはオブジェクト指向とは直交しており、関数でもオブジェクトでも適用できると結論づけている。