name: clean-architecture
description: クリーンアーキテクチャの依存ルールを moka-1 のミニマリズム制約下で適用する指針。レイヤ分割・interface 設計・依存方向の判断基準と、過剰抽象化を避けるガードレールを提供する。
TRIGGER when: 新しいパッケージ/モジュールの設計時、interface を切るか迷った時、依存方向の判断時、リファクタリングで層の再編を行う時、「アーキテクチャ的にどうか」と聞かれた時。
DO NOT TRIGGER when: 既存構造内の小さなバグ修正、言語固有の書き方の質問(bp-go / bp-svelte の領分)、インフラ・compose の変更のみ。
Clean Architecture (moka-1 流)
原典: Robert C. Martin の The Clean Architecture。ただし moka-1 は「1コマンド・少数コンテナ・単一言語」制約(tenets §2)が先にあるので、教科書の4層をそのまま持ち込まず、依存ルールだけを不変条件として運用する。
唯一の不変条件: 依存ルール
ソースコードの依存は内側(ビジネスルール)にだけ向く。内側は外側を知らない。
内側 → 外側の順:
- ドメイン: 記事・フィード・濃縮結果のデータ構造と規則。DB・HTTP・LLM を知らない
- ユースケース: 「取得→正規化→保存」「濃縮キュー処理」「ハイブリッド検索」の手順。具象でなく interface に依存
- アダプタ:
store(pgx)、llm(OpenAI互換HTTP)、httpapi(ServeMux)、feed のフェッチャ
- フレームワーク・ドライバ: PostgreSQL、llama.cpp、SvelteKit、Plecto
チェック方法: import 文を見る。ユースケース層のファイルが pgx や net/http を import していたら違反。
moka-1 でのレイヤの畳み方
教科書の同心円を Go のパッケージ境界に畳む(tenets §3.1「プロセス境界ではなくパッケージ境界」)。ディレクトリを domain/ usecase/ adapter/ に切り直すのではなく、機能パッケージ(feed, enrich, rag, ...)の内部で依存方向を守る:
internal/enrich/
├── enrich.go # ユースケース: キュー処理の手順。interface にのみ依存
├── types.go # ドメイン: 濃縮結果の構造と検証規則
└── (具象は注入) # llm.Client, store.Pool は main で配線
- interface は消費側で定義する(Go 流の依存性逆転)。
enrich が要約を必要とするなら enrich パッケージ内に最小の interface を書き、llm の具象を cmd/moka/main.go で注入する。詳細は bp-go 参照
- main.go が唯一の合成の根(composition root): config 読み込み → 具象生成 → 配線 → 起動。ここ以外で具象同士を結線しない
- 横断で共有するのはドメイン型だけ。ユーティリティパッケージ(
common/, utils/)は作らない
過剰抽象化ガードレール(ミニマリズム側の拘束)
クリーンアーキテクチャの最頻出の失敗は層の不足ではなく層の過剰。以下を守る:
- 実装が1つで、テストでも差し替えないなら interface を切らない。pgx を直接呼ぶ store 関数で十分な場面に repository interface を発明しない
- 切ってよい interface は「プロセス外 I/O の境界」だけ: DB、LLM、外部フィード取得、時計。これらはテストでフェイクに差し替える実需がある
- 貧血ドメインにしない: 検証・正規化の規則はドメイン型のメソッドに置く。「struct は入れ物、ロジックは全部ユースケース」に流れたら分割を見直す
- 層のためのマッピング型を作らない: DTO ⇄ entity ⇄ model の三重写経が始まったら、層が多すぎるサイン。moka-1 の規模なら1つの型が2層を貫いてよい(DB タグと JSON タグの同居は許容)
- 新しい抽象は「2回目の実装が現れた時」に導入する。予測で作らない
判断フローチャート
新しい依存を足したい
├─ プロセス外 I/O か? ── yes → 消費側に最小 interface + main で注入
│ no ↓
├─ 同一パッケージ内で完結するか? ── yes → 直接呼ぶ。抽象化しない
│ no ↓
└─ ドメイン型の共有で足りるか? ── yes → 型だけ共有し、挙動は各パッケージに
no → パッケージ境界の再検討(設計判断なので ADR 候補)
テストとの関係
- ユースケースのテストはフェイク注入で DB・LLM なしに走ること。
httptest や testcontainers が必要になったらそれはアダプタ層のテスト
- 依存ルール違反は import 検査で機械的に検出できる。迷ったら
go list -deps で内側パッケージの依存を確認
参照
docs/tenets/moka-tenets.md §2(設計原則)・§3.1(パッケージ境界)
- bp-go スキル(interface 消費側定義、main の配線パターン)
- 層の再編・境界変更を実施したら moka-adr-writer で記録する