TL;DR
- 実践クリーンアーキテクチャを参考に 頭を使わずに ADRを作成し、クラス構成・ディレクトリ構造を決定
- ユーザーの入力→処理→出力を1機能単位としてドキュメントを作成し、ADRに従わせて実装
- 利点: AIが生成するコードをかなり意図通り制御できる。UI部分でエラー判定をしなくていい(Interactorで判断、Presenterで規定)
- 機能ドキュメントを自然言語で書き、クラス設計以降はAIに任せる分担が効果的
- 技術駆動ディレクトリの意外な良さ: 人間に優しくないがAIには優しい。リポジトリ全体で規則を統一できる
- Clean Architectureのenterprise-business-rules層が示すのは、ルールではなく、願望 と考えるとわかりやすいかも
発端
なんとなくClean Architectureをずっと学びたいと思っていたが、どの記事を読んでもピンとこない。
そんなときに下記の記事に出会った。
特に腑に落ちたのは次の表現
(画像を引用)
どこかで見たことありませんか?
そう、クリーンアーキテクチャの図の右下のこれです。
(画像を引用)
すぐに真似したくなり、Chrome拡張機能の個人開発で実践してみた。
まずやったこと:Clean ArchitectureのADRをChrome拡張機能プロジェクトに適用
- 先行記事のClean Architectureをただただ追従。リポジトリ全体のADR、ディレクトリ構造を決定。
ディレクトリ構造にはあえて技術駆動構造を採用(後述)。
その結果がこちら
src/
├── enterprise-business-rules/ # 第1層: Entities, Value Objects
│ ├── entities/
│ ├── value-objects/
│ └── constants/
│├── application-business-rules/ # 第2層: Use Cases
│ ├── ports/
│ │ ├── input/ # Input Port (Interface)
│ │ ├── output/ # Output Port (Interface)
│ │ └── gateway/ # Gateway Interface
│ ├── interactors/ # Use Case Interactor
│ └── dto/
│├── interface-adapters/ # 第3層: Controllers, Presenters
│ ├── controllers/
│ ├── presenters/
│ ├── factories/
│ └── mappers/
└── frameworks-and-drivers/ # 第4層: UI, Chrome API, DB
├── ui/ # ユーザーインターフェース関連。この中でatomic-designパターンを適用
├── persistence/ # データベース、ストレージ関連
├── messaging/ # chrome拡張機能に必要なメッセージング関連
├── browser/ # chrome拡張機能のブラウザAPI関連(DOM要素取得・操作等)
└── di/ # 依存性注入関連。npmのinversifyを直接利用するコードをここに集約
その結果、Clean Architectureについて現時点で自分は下記のように解釈をしている
第4層 frameworks-and-drivers
- 外部の "なにか" を 直接 操作していい唯一の場所
-
よくUIやDBをここにまとめるとか言われる。部分的にはあってはいるがあまりピンとこなかった -
自分が作ってない、メソッドやロジックはここ以外では直接使わないと理解したら腑に落ちた - なのでnpmパッケージの直接利用コード、chrome拡張機能開発のためのフレームワーク、WXT都合のコードもすべてここに集約
第1層 enterprise-business-rules
- PMや社長、お客様が仰ること
- ソフトウェア工学における合理性とか関係ない。
- ソフトウェアがどう動いてほしいかという 願望 が記述される
- あえて願望と書く。よくClean Architectureの記事には
ビジネスルールと書かれている。この単語だと合理的な印象をあたえてしまう。 - だからエンジニアの理屈で書き換えられるという勘違い、他の層との混同が発生。
- だが実際には、エンジニアの常識からすると非合理に思える機能も要求される。(むしろそれが世間の望むものであったりする)
- だからエンジニアが容易には境界を超えられない、という意味で 願望 と書いた。
- あえて願望と書く。よくClean Architectureの記事には
第2層 application-business-rules
- 第1層と第4層に振り回される、可哀想な仲介役
- 加えて、Clean Architectureの
内側の層は外側の層に依存してはいけないというルールを守るためにいろいろと苦心している。- その苦心の表れが、
Input PortOutput PortGateway InterfaceUse Caseという要素-
Gateway Interfaceという単語のせいで、第3層と第2層の違いを長い間理解できずにいた。
-
- その苦心の表れが、
第3層 interface-adapters
- わからん。 言葉遊びの域を出ないので一言での理解は諦めた。
とにかくディレクトリ構造ができたので、これを利用し、次に述べる開発フローをやってみた。
開発フロー
以下のフローで開発を進めた:
- 機能単位ドキュメント作成: ユーザーの入力→処理→出力を1機能単位としてドキュメントを作成
- 正常系1パターン実装: ドキュメントとADRに従わせて正常系1パターンのE2Eテストを通る最低限の実装。
- モジュール単位の実装ユニットテスト: 各モジュール単位でテスト戦略書とユニットテスト、不具合修正
- 結合テスト、E2Eテスト網羅: 最後に結合テストとE2Eテストを網羅、不具合修正
- ユーザーテスト: 実際にブラウザ上で動かして動作確認
1. 機能単位ドキュメント作成
機能ごとに以下の構成で設計書を作成する:
| ファイル | 役割 | 従属関係 |
|---|---|---|
| 00-overview.md | 開発者が作りたい完成形を記述。フレームワークや実装詳細、現在の実装状況に振り回されない | なし(起点) |
| 01-class-design.md | 00-overview.mdを実現するクラス設計。ADRに完全従属。設計者はほぼレビューしない | 00-overview.md, ADR |
| 02-sequence.md | クラスのシーケンス図。01-class-design.mdを開発者がレビューするためのドキュメント。画像出力してレビュー | 01-class-design.md |
| 03-directory-structure.md | 登場するクラスのディレクトリ構成。実装時に使用 | 01-class-design.md |
| 04-arrow-diagram.md(任意) | 各モジュールの開発タスクのアローダイアグラム。 | 01-class-design.md |
| 05-test-strategy.md | メソッド単位の単体テスト戦略。これは各モジュール単位で作成される | 01-class-design.md |
| 06-integration-test-strategy.md | 機能単位の結合テスト戦略 | 00-overview.md |
| 07-e2e-test-strategy/ | E2Eテスト戦略(ディレクトリ) | 00-overview.md |
00-overview.mdには、自然言語で開発者が作りたい完成形を書く。大事なのは 願望 でよいことだ。この切り分けができるようになっただけでも、今回Clean Architectureを学んで良かったと思う。コアだけ書いたら、AIに補完させてもよい。
あとはオートマティックだ。
ADR、00-overview.mdに従って、01-class-design.md、02-sequence.md、03-directory-structure.md、04-arrow-diagram.md を作成して
で指示すれば、AIが自動生成してくれる。
現状、開発者の私がレビューするのは、02-sequence.mdの画像出力部分だけだ。これは私がシーケンス図が好きだからと言うだけであって、01-class-design.mdのクラス図を読める人はいらないかもしれない。シーケンス図でレビュー指摘を行い、問題なければドキュメントとしてはPR通過としている。
これは今も軸が定まっていないが、ここまでAI駆動開発が進んでいると、ある程度はAIが作るものを信用してdumbレビューで済ませて良い気がしている。
今の自分としては、02-sequence.mdの画像出力部分に自分の願望が反映されていることが確認できれば良い、ということにしている。
ここができたらe2eテスト1パターンを作らせる
ADR、00-overview.mdに従って、01-class-design.md、02-sequence.md、03-directory-structure.mdに従って、正常系e2eテスト1パターンを07-e2e-test-strategy直下に作成して
これでe2eテスト戦略書ができる
2. 正常系1パターン実装
Claude Code Webへの指示はこう
ADR、00-overview.mdに従って、01-class-design.md、02-sequence.md、03-directory-structure.mdに従って、07-e2e-test-strategyに通るよう実装を行って
肝なのは、"ログイン機能を実装して"、という受け取り方が無限に膨らむ指示ではないということ。
ドキュメント、ガイドラインを複数示すことで、AIの出力パターンを可能な限り抑え込もうとしている。
またドキュメントを指示することで、プロンプトのうまさに影響を受けないというのも利点と考えている。
これでほぼオートマチックにほぼ意図通りの実装ができる。
開発初期や新規機能追加時にはこれだけでもよいくらいだ。
3. モジュール単位の実装ユニットテスト
AI生成で04-arrow-diagram.mdを作れば、どのモジュールから実装を着手すればいいかもわかる
[画像]
アローダイアグラムには開発タスクナンバーもついているので
アローダイアグラムのP2-3タスクを始めてください。
でP2-3タスクに紐づけられているモジュールのテスト戦略書、ユニットテスト作成が始まる。
場合によっては並行も可能。
ロジックであればここまで半自動で実装可能。
あとは UI実装、 / 結合テスト / E2Eテストだが、これらのドキュメントについてはまた別の機会に述べたいと思う
利点
AIが生成するコードを意図を持って制御できる
別のドキュメントでコード規約等を読み込ませることも可能だ。「動けばいい」ではないチーム開発ではかなり有効だと感じた。
ADRに沿って責任の分離を頭を使わずにできる
一番良かったのは、UI部分でエラー判定をしなくていいことだ。エラー判定はあくまでもロジックの心臓部たるInteractorで判断する。UI部はそれを受け取るだけだ。
ちなみにUI部が受け取る情報も、Presenterパターンで規定できる。
特にUI部は編集機能、削除機能等、1ページに機能が集まるとタコツボ化して影響不具合・競合が発生しやすい。可読性向上という部分でも良かった。
技術駆動パッケージングの意外な良さ
技術駆動パッケージングには「機能ではなくアーキテクチャの都合でディレクトリ分けがされ、人間に優しくない」という批判がある。
ただ、AIには優しいと思う。
機能単位でディレクトリを分けると、機能ごとにアーキテクチャや命名規則がバラバラということもあり得る。だが技術駆動単位であれば、リポジトリを通じてそのへんの規則を統一することができる。
便利に感じたこと
人間は人間のわかる形で考えた方が良い
実装詳細をAIに任せ、人間は「何を作りたいか」「なぜそうするのか」に集中できる。
00-overview.md(完成形のイメージ)を自然言語で書き、クラス設計以降はAIに任せる——この分担が効果的だった。
設計書さえできていればほぼオートマチック
設計書が整備されていれば、実装は「この設計に従って実装してください」の一言で済む。
最初にドキュメントを整備する手間をかければ、その後は楽ができる。
あらためてClean Architectureについて
必死に学習して感じたこと
要素1個1個わけると膨大に感じる。けれど実はClean Architectureを使わなくても同じことを考えている。
Clean Architectureの各概念(Entity、Use Case、Gateway、Presenter...)は、名前がついていないだけで、どんな開発でも考慮している事項だ。
- 「このロジックはどこに書くべきか」
- 「外部APIへの依存をどう隔離するか」
- 「テストしやすい構造にするにはどうするか」
Clean Architectureは、これらに名前と置き場所を与えているだけだ。
名前がつくことで、チーム内の認識が統一される。「このロジックはEntityに書いて」「GatewayでAPIを隔離して」という会話が可能になる。名前がなければ、毎回「ビジネスロジックを書くところ」「外部APIを呼び出すところ」と説明する必要がある。
まとめ
クラス分けこそ、先行記事に従って何も考えずに行った。
だがおかげで、設計書(そこから派生するテスト戦略書)をオートマチックに書くことができ、そこから生成されるコードもブレが少ないものになった。
- 最初にドキュメントを整備する手間をかければ、その後はAIが自動化してくれる
- 「これに従って」の一言で実装が進む
- 設計と実装の乖離もAIが一括修正してくれる
むしろ楽ができる——これがズボラなクリーンアーキテクチャだ