生成AIの時代にレイヤードアーキテクチャは古いかなと思った
概要
導入
生成AIの進化に伴いシステム開発においても生成AIにとっても管理しやすいコードベースを作ることが重要になってきてるかなと思います。
そこで重要な点は生成AIに一度に読み書きさせる文章量や概念の広さ(コンテキストウィンドウ)が広い程に品質が下がるので判断材料は多いほど良いのですが効率的に関連情報のみにフィルタリングして渡す情報を節約するのが重要なのでなはいかと(お値段的にも)。
そこで今回お話ししたいのは、昨今流行っているレイヤードアーキテクチャのレイヤー構造が生成AIのコンテキストウィンドウと相性が悪いのではないかと気付き新しいアーキテクチャのコンセプトを描きたくなったので記事を書きました。
コード生成処理の流れ
1.コード生成対象ディレクトリの特定(pwd)
2. 関連パス(親ディレクトリ含む)を列挙
3. module.yaml(と親のmodule.yaml)から設計情報を読み込む
4. gen_code_rag.indexから関連クラス情報をベクトル検索しgen_code_class.dbから詳細コメントを取得
5. Githubキャッシュ用のDBから最後のコメントの日時を取得し、Github APIから現在までの一覧を取得しキャッシュに登録
6. Github用のDBから対象パスへのレビューを抽出し生成AIで要約と要点の列挙をさせる
7. レビューで指摘されやすい観点と関連する自動生成クラス情報と関連クラスのyamlを元にコード生成
レイヤードアーキテクチャーの問題点
レイヤードアーキテクチャでは一番最初にレイヤーごとにディレクトリを分割するため関連するクラスが様々な場所に散らばり生成時の変更点に影響するクラスのみを機械的に抽出するのは手間がかかります。
そのため生成AIがコードを生成・修正する際に全体の構造・ドキュメント・レビューを把握する必要がありコンテキストが大きくなりがちです。
これにより生成結果がブレたり意図しない補完が行われやすくなります。
代案としての新しいアーキテクチャ
要素技術としてのVertical Slice Architecture
Vertical Slice Architectureは処理の類型ではなく各機能を独立したスライスとして扱い、
機能ごとに必要な要素(UI、ビジネスロジック、データアクセスなど)を一つのユニットとしてまとめます。
これにより各スライスが独立して動作し他のスライスに影響を与えずに開発やテストが可能になります。
階層化を組み合わせたアーキテクチャイメージ
今回提案するアーキテクチャはVertical Slice Architectureに階層構造を組み合わせた「Vertical Slice Layered Architecture」とも呼べるようなものです。
大機能の中に中機能や小機能が入れ子になり、依存するのは直下のクラス達と直上のクラスのみになります。
app
├─ module.yaml
├─ base_controller.*
├─ base_repository.*
├─ base_service.*
├─ base_schemas.*
├─ base_util.*
├─ config.*
├─ external_service/
│ ├─ gen_code_class.db
│ ├─ gen_code_rag.index
│ └─ hoge_swagger_gen/
└─ user/
├─ module.yaml
├─ user_controller.*
├─ user_repository.*
├─ user_service.*
├─ user_schemas.*
└─ user_util.*
├─ profile/
│ ├─ module.yaml
│ ├─ profile_controller.*
│ ├─ profile_repository.*
│ ├─ profile_service.*
│ ├─ profile_schemas.*
│ ├─ profile_util.*
│ └─ update/
│ ├─ module.yaml
│ ├─ update_controller.*
│ ├─ update_repository.*
│ ├─ update_service.*
│ ├─ update_schemas.*
│ └─ update_util.*
└─ follow/
├─ module.yaml
├─ follow_controller.*
├─ follow_repository.*
├─ follow_service.*
├─ follow_schemas.*
└─ follow_util.*
何が嬉しいのか
直下のディレクトリと相対パスで辿っていく親ディレクトリのmodule.yaml(README.mdでも可)や関連情報を参照するだけで現在開いているファイルを生成AIが修正する時にクラスの役割や修正方針が分かるようになります。
最低限のコーディングルール
- 直上のクラス1つと直下のクラスしか参照しない
- 直上のクラスはコンストラクタで受け取る(ex.new ProfileController(parent: userController))
- 各機能の階層数は1つ1つのクラスが小さい方が生成品質が上がるため(体感)機能の複雑さに応じて増えていく
- 兄弟モジュールの機能が使いたくなったら親クラスにコードを移して共有して使う
LLMとの組み合わせ方
階層化と相対パスを組み合わせたコンテキスト絞り込み
pwdや各IDEの機能で取得できる現在のパスから/**を1つずつ外して親機能を特定しlsのディレクトリのものから子機能を特定します。
孫機能以降は不要です。
他社実装機能にに対するRAG検索
Swagger自動生成クラスや有名でないライブラリの機能検索自体はSentenceTransformerとベクトルDB(FAISS)を用いたローカルでのRAG検索で行います。
SQLiteに実クラスのパスやクラスドキュメントなどのデータを入れ、ベクトル検索結果のid一覧を用いてSQLiteからクラスの情報を取得します。
自動生成したら洗い替えしたい場合はidを発番する親テーブルと各ライブラリの子テーブルでも良いかもしれません。
CREATE TABLE external_code (
id TEXT PRIMARY KEY, -- UUIDなどを使用(FAISSと連携)
namespace TEXT, -- 例: 'user/profile'
type TEXT, -- 'api', 'doc', 'rule', etc
title TEXT, -- 見出し
body TEXT, -- 内容(LLMが読ませる用)
tags TEXT -- カンマ区切り、分類用
);
Github APIのレビュー一覧とEmbedding
概要
GitHubなどで行われた「Pull Requestレビューのコメント」をAPIで取得し該当ディレクトリおよびその親ディレクトリに関連するレビューのみをフィルタリングしてコーディング規約と共に生成AIに要約させて「該当クラスに対する機能専用のレビュー方針」を都度作成します。
こうして「過去のレビュー内容・指摘・判断・設計思想」も取り込むことでレビュー指摘する必要の無い人間らしい判断とコーディングを再現します。
キャッシュ戦略
実装面ではAPI制限や生成AIコスト抑えるためにデータはSQLiteに構造的に格納してコストと速度を最適化する
- Github APIをキャッシュの一番新しいコメントから現在時刻までのものを取得。
- レビュー方針も関連機能のレビュー一覧をハッシュ化して変化がなければ前回のを使い回す
CREATE TABLE review_comments (
id TEXT PRIMARY KEY,
file_path TEXT NOT NULL,
pr_number INTEGER NOT NULL,
author TEXT,
body TEXT NOT NULL,
created_at TEXT
);
まとめ
今後の生成AIを活用したシステム開発は、
- プロンプト設計力
- システム設計力
- UX設計力
の3つを持つ人が圧倒的に有利です。
新しいアーキテクチャ設計を導入することでLLMとの親和性を高め開発品質を効率を向上させるために、この記事の通りにCursor君にまずは実証実験からしてもらおうと思います。
追記
2025/04/09
Twitterで議論をしてもらって漏れがあったため追記します。
前提としてこのアーキテクチャじゃなくてもCursorを使えばコードは提案してもらえます。
私がこのアーキテクチャで解決したいのはDevinのような体験をSlackとGithub Actionsだけで安く作りたいから考えました。
現状のAIエージェントが完全自立型でさえIDEとセットになっているのはIDEがコードジャンプ(JetBrainsでいうCmd+B)のためにのクラス間の依存関係を解析して保持する仕組みを利用したいから(らしい)です、DevinでもVPS的な環境で人間でもないのにコードエディタを立ち上げているそうです。
今回の提案したアーキテクチャだとクラス間の依存関係をディレクトリ構造で表現しているのでシンプルなCLI操作で影響範囲の情報を揃えられるからGithub Actions上でも簡単に自動プルリク作成ができるだろうという目論見があります
Discussion
Package by featureっぽい感じですかね?(Next.jsのApp routerみたいなディレクトリ構造の考え方)
この記事を見る感じ合っています!
(スルーしようと思ってたんですが言及されたので書きます)
本文で言及されてる「クリーンアーキテクチャ」はclean architectureをなぞった方法論(もどき)を指してると思います。僕の記事でpackage by layerと言及してたのがそれですね。
package by layerに関してはそもそも 書籍で言及されてるcleanなarchitectureでさえありません。 なので、「生成AIの時代にクリーンアーキテクチャは古いかなと思った」という主張はできてません。
本記事でいう間違った"クリーンアーキテクチャ"(package by layer)は、生成AI時代以前から役に立ってないアーキテクチャだった、が正しいと思います。
重箱の隅をつつくようになってしまい恐縮ですが、Clean Architecture の原著からの引用で補足させていただきます。
34章に、以下のように書かれています。
詳細までは理解しきれていませんが、このほかにも「ポートとアダプター」「コンポーネント(デプロイ単位)によるパッケージング」、合わせて 4種類のパッケージング戦略を 例示(これ以外の可能性が無いことは決して無い、というニュアンスを込めて) しています。
@pandanoir @honey32
コメントありがとうございます。
使っている単語を狭い範囲の意味のものに変更いたしました。
s/クリーンアーキテクチャ/レイヤードアーキテクチャ