🤖

生成AIの時代にレイヤードアーキテクチャは古いかなと思った

に公開
5

概要

導入

生成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. 直上のクラス1つと直下のクラスしか参照しない
  2. 直上のクラスはコンストラクタで受け取る(ex.new ProfileController(parent: userController))
  3. 各機能の階層数は1つ1つのクラスが小さい方が生成品質が上がるため(体感)機能の複雑さに応じて増えていく
  4. 兄弟モジュールの機能が使いたくなったら親クラスにコードを移して共有して使う

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上でも簡単に自動プルリク作成ができるだろうという目論見があります

GitHubで編集を提案

Discussion

ひものひもの

Vertical Slice Architectureは処理の類型ではなく各機能を独立したスライスとして扱い、
機能ごとに必要な要素(UI、ビジネスロジック、データアクセスなど)を一つのユニットとしてまとめます。

Package by featureっぽい感じですかね?(Next.jsのApp routerみたいなディレクトリ構造の考え方)

クロパンダクロパンダ

(スルーしようと思ってたんですが言及されたので書きます)

本文で言及されてる「クリーンアーキテクチャ」はclean architectureをなぞった方法論(もどき)を指してると思います。僕の記事でpackage by layerと言及してたのがそれですね。

package by layerに関してはそもそも 書籍で言及されてるcleanなarchitectureでさえありません。 なので、「生成AIの時代にクリーンアーキテクチャは古いかなと思った」という主張はできてません。

本記事でいう間違った"クリーンアーキテクチャ"(package by layer)は、生成AI時代以前から役に立ってないアーキテクチャだった、が正しいと思います。

Honey32Honey32

重箱の隅をつつくようになってしまい恐縮ですが、Clean Architecture の原著からの引用で補足させていただきます。

34章に、以下のように書かれています。

このアプローチの場合も、登場するクラスやインターフェイスは先ほどと同じだ(図34-2)。しかし、それらを3つのJavaパッケージに分けるのではなく、すべてを1つのパッケージにまとめている。先ほどの「レイヤーによるパッケージング」を少しだけリファクタリングしたものだが、トップレベルのコードの構成がビジネスドメインについて叫ぶようになった。ウェブ、サービス、リポジトリといったものではなく、注文に関する何かをするものであることがわかるようになった。別のメリットもある。仮に「注文を閲覧する」ユースケースに何らかの変更があったとしても、こちらの構成のほうが修正すべきコードを把握しやすい。あちこちのパッケージに散らばっているのではなく、1つのパッケージにまとまっているからだ*3。

水平方向のレイヤーリング(レイヤーによるパッケージング)でうまくいかなくなったソフトウェア開発チームが、垂直方向のレイヤーリング(機能によるパッケージング)に切り替える事例をよく見かける。だが、個人的にはどっちもどっちだと思っている。 本書をここまで読んできたみなさんなら、もっといいやり方があるのではないかと思うことだろう。そのとおりだ。

Robert C. Martin; 角 征典; 高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (アスキードワンゴ) (pp.353-355). 株式会社ドワンゴ. Kindle 版. 強調は私がしました。

詳細までは理解しきれていませんが、このほかにも「ポートとアダプター」「コンポーネント(デプロイ単位)によるパッケージング」、合わせて 4種類のパッケージング戦略を 例示(これ以外の可能性が無いことは決して無い、というニュアンスを込めて) しています。

まとめ:言い残したこと(注:上記と同じ章のまとめ)

いくらうまい設計をしても、その実装方法の複雑さを考慮しなければ、あっという間に設計が崩れてしまう。これが本章のポイントだ。あなたが望む設計をコードの構造にマッピングする方法、そのコードをとりまとめる方法、実行時とコンパイル時に依存性を分割する方法について考えてみよう。使える選択肢は可能な限り残しておきたいが、理想に走りすぎてもいけない。チームの規模やメンバーのスキルやソリューションの複雑さ、そして時間と予算の制約などを考慮しよう。選んだアーキテクチャスタイルを守らせるためにコンパイラが使えるかどうかを検討して、データモデルなどのほかの領域と結合してしまわないように注意しよう。悪魔は実装の詳細に宿るものだ。

同 (p.372)

kooo5252kooo5252

@pandanoir @honey32
コメントありがとうございます。
使っている単語を狭い範囲の意味のものに変更いたしました。
s/クリーンアーキテクチャ/レイヤードアーキテクチャ