/note/tech

Goでクリーンアーキテクチャを導入するとinterfaceが爆発する問題への処方箋

要約:

■ 1. 概要

  • Goプロジェクトにクリーンアーキテクチャをそのまま適用するとinterfaceが過剰に増殖する問題が生じる
  • 問題の本質はinterfaceの個数ではなく「大きすぎるinterface」と「Goの言語特性を活かせていない設計」にある
  • Go的思想に則った再設計により問題を解消できる

■ 2. Goの思想との3つのズレ

  • 提供側でinterfaceを定義している:
    • Go公式Wikiは「interfaceは利用側のパッケージで定義すべき」と推奨している
    • 提供側での定義はGoのイディオムに反する
  • interfaceが太すぎる:
    • Rob Pikeの言葉「The bigger the interface, the weaker the abstraction」に反し1つのRepositoryに5〜8メソッドが存在する
    • 抽象化の粒度が大きすぎるため再利用性と柔軟性が低下する
  • Preemptive Interfaceが残存している:
    • 実装が1つしか存在しないinterfaceはGoコミュニティではアンチパターンとされる
    • 不必要な抽象化がコードの複雑性を増大させる

■ 3. 4つの処方箋

  • Input Portの廃止:
    • usecase/port/input/ディレクトリを削除する
    • Handler層のみでinterfaceを定義しGoの暗黙的interface満足を活用する
  • Output Portの選別保持:
    • 複数のInteractorが共有する外部サービス連携はOutput Portとして維持する
    • 単一Interactorにしか使われないものは廃止対象とする
  • RepositoryをReader/Writerに分離:
    • 単一の太いinterfaceを読み取り用と書き込み用に分割する
    • 各Interactorは必要な操作にのみ依存する設計とする
  • ディレクトリ構成の整理:
    • port/input/を廃止し出力ポートのみ残す
    • ディレクトリ構造を簡潔に保つ

■ 4. よくある疑問への回答

  • 依存性注入の方法:
    • structを直接渡すだけでimplicit interfaceにより要件が自動的に満たされる
    • 明示的なinterface指定は不要
  • interfaceが散らばる懸念:
    • private interfaceは利用箇所の直近で定義されるため実運用では管理上の問題になりにくい
  • 小規模プロジェクトへの適用可否:
    • CRUD中心のアプリケーションではこの複雑な設計は不要との見解が示されている