CONTENTS
三行で
- Use Case → Service → Repository → Mapper → Table のレイヤー構成
- Service と Repository は 1:1 の構成とする
- Table は SQL クエリを実装し、 Mapper でドメイン型との変換を行う
なぜこの構成にするのか
前提として、コードは AI エージェントで生成する。
AI エージェントはデータベースへの影響をできるだけ少なくするように振る舞っているように感じている。 これは特に問題だと思っていない。 破壊的なマイグレーションを連発するよりは、データベースへの変更は可能な限り後方互換性を保つようなコードを生成してくれた方がありがたい。 この方向性はおそらくこれから AI エージェントが進化したとしても変わらないだろう。
では、この方向性によって何が起こるか。
AI エージェントはデータベースを重要視するために、データベースを主軸にコードを設計していると感じることが多い。 リポジトリパターンが単純なテーブルをそのまま反映するような形で設計され、これらを組み合わせる形でドメインサービスを組み立てようとしているように見える。
これを抑制するため、リポジトリを意図的に薄いレイヤーとして設計し、実装レイヤーとドメインレイヤーを明確に分けることでドメインサービスの設計を守る必要性があるだろう。
そこで、一つのドメインサービスに対して一つのリポジトリ定義のみが存在する形にすることで、リポジトリの再利用をさせない構成にする。 この構成であれば、 Table 実装と Mapper 実装は機械的に実装しても大きな間違いは起こらない。 Table 実装は特定のテーブルの SQL 実装しか含まないし、 Mapper 実装は変換を行うだけだ。
この構成で、なんとかドメイン駆動設計で AI エージェントでコード生成ができないかと考えている。
Table レイヤー
Table レイヤーではデータベーステーブルに対応する SQL 実装を行う。
ここではドメイン型は使用せずに SQL 実装を行う。
select の結果は *Row で、 insert の引数は *Data で命名する形で整理した。
Row や Data をドメイン型と変換するのは Mapper レイヤーで行う。
Mapper レイヤー
Mapper レイヤーでは Table レイヤーで定義した Row や Data とドメイン型の変換を定義する。
全く同じ構造であっても、 Row や Data として、 Table レイヤーの構造とドメイン型を分離するべきだ。 Table 実装でドメイン型を参照すると、むしろ Table 実装が素直にできるようにドメイン型を設計するようになる。 ドメイン型は Table 実装を意識することなく設計されるべきだ。
ドメイン型と全く同じ構造の Row や Data を定義するのは人間の仕事ではないが、 AI エージェントで生成させることを考えれば大きな問題にはならない。 むしろ、同じ構造でもそれぞれ Table 実装のための Row や Data 型が定義されていることで、このパターンを踏襲してコード生成が行われることが期待できる。
Service と Repository レイヤー
Service レイヤーではドメインロジックを記述する。
Service の処理に必要な Repository が必要になるが、これは Service と 1:1 で定義することにする。 複数の Service で同じ Repository メソッドを使用することは当然あり得るが、 Service 間で Repository の共有は行わない。 Repository の実装で同じ Table 実装を使用することでコードの重複を解消する。
まとめ
AI エージェントの生成するコードがどうしてもデータベースを軸に設計されるように思えるので、一旦この構成で設計を進めてみようという試み。 リポジトリをうまく設計することを一旦諦めて、ドメインサービスの設計を主体とする設計ができないか試行錯誤しているところ。
参考資料
- 特になし