0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI時代のバックエンド開発を効率化するアーキテクチャを考えてみた

0
Posted at

はじめに

最近、AIによるコード生成がかなり実用的になってきました。

CRUD API程度であれば、AIに仕様を渡すだけでかなりのところまで実装してくれます。

一方で、実際の業務システムを開発していると、コードを書くことよりも難しいのが、

  • DB設計
  • 認可
  • API設計
  • 業務ルール
  • ユースケース
  • ドキュメント
  • テスト

といったものを、どう一貫して管理するかです。

特に問題になるのが、同じ仕様が複数の場所に存在してしまうことです。

例えば、

仕様書
  ↓
OpenAPI
  ↓
TypeScript
  ↓
テスト

のそれぞれに似た情報を書いていると、少しずつ乖離していきます。

そこで今回は、

人間は「何を作るか」を定義し、AIとツールに「どう作るか」を任せる

という考え方で、バックエンド開発のアーキテクチャを考えてみました。


目指すアーキテクチャ

今回考えた構成は以下です。

                         人間が管理するSSOT
┌──────────────────────────────────────────────────┐
│                                                  │
│  Requirements        Use Cases                   │
│  要件・機能一覧        業務仕様                   │
│       │                  │                       │
│       │                  │                       │
│       ▼                  ▼                       │
│  schema.zmodel     business-api.yml             │
│  データ・認可        HTTP API契約                 │
│                                                  │
└──────────────┬───────────────┬───────────────────┘
               │               │
               ▼               ▼
          ┌─────────┐    ┌──────────────┐
          │ZenStack │    │   Fastify    │
          └────┬────┘    └──────┬───────┘
               │                │
               │          Business API
               │                │
               │                ▼
               │           UseCase
               │                │
               └────────────────┘
                        │
                        ▼
                    PostgreSQL


             AI / Code Generation
                      │
        ┌─────────────┼─────────────┐
        ▼             ▼             ▼
     UseCase       Tests        Boilerplate
      code

ポイントは、コードをSSOTにしないことです。

人間が管理するのは、

  • 要件
  • ユースケース
  • データモデル
  • API契約

です。

その情報をもとに、AIやコード生成ツールに実装を作ってもらいます。


1. まず「SSOT」を分割する

ここで重要なのが、

「SSOTを1個にする」

必要はないということです。

むしろ、異なる種類の情報を1つのファイルに詰め込むと破綻します。

そこで、それぞれの責務ごとにSSOTを持たせます。

SSOT 管理するもの
Requirements システムとして何が必要か
Use Case 業務上どう振る舞うか
ZModel データ構造・リレーション・認可
OpenAPI HTTP APIの契約
TypeScript 実装

この考え方が今回のアーキテクチャの中心です。


2. データモデルはZModelをSSOTにする

DB設計にはZenStackのZModelを使います。

例えば、

model Project {
    id        String   @id @default(cuid())
    name      String
    status    ProjectStatus

    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

    members   ProjectMember[]
}

ここには、

  • テーブル
  • カラム
  • 型
  • リレーション
  • 制約
  • 認可

など、データに関する情報を集約します。

DB設計書を別途手で作る必要はありません。

schema.zmodel
      │
      ├── DB schema
      ├── ORM
      ├── CRUD API
      └── Authorization

という形にできます。


3. CRUDは生成する

業務システムではCRUD APIが大量に発生します。

例えば、

GET    /projects
POST   /projects
GET    /projects/:id
PATCH  /projects/:id
DELETE /projects/:id

これを毎回手で実装するのは、かなりもったいない。

そこでZenStackに任せます。

schema.zmodel
      ↓
ZenStack
      ↓
CRUD API

これによって、CRUDの実装コストを大きく減らせます。

さらに、認可ルールもZModel側に定義できます。

つまり、

CRUD
認可
ORM
DB

という、比較的機械的な部分をツールに任せます。


4. 業務APIはCRUDと分離する

ここがかなり重要です。

例えば、

POST /projects/:id/approve

は単なるCRUDではありません。

「プロジェクトのstatusを書き換える」というDB操作だけではなく、

  • 現在の状態を確認する
  • 承認権限を確認する
  • 承認者を記録する
  • 承認日時を記録する
  • 必要ならイベントを発生させる

といった業務処理があります。

これをCRUD APIに無理やり押し込むと、API設計がどんどん複雑になります。

そこで、

CRUD API
    ↓
ZenStack

Business API
    ↓
UseCase

と分離します。


5. Business APIはOpenAPIをSSOTにする

業務APIはOpenAPIで定義します。

paths:
  /projects/{projectId}/approve:
    post:
      operationId: approveProject

      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string

      responses:
        '200':
          description: Project approved

ここではHTTP APIについて、

  • URL
  • HTTP method
  • Request
  • Response
  • Status Code
  • operationId

を定義します。

重要なのは、OpenAPIに業務ロジックを書かないことです。

OpenAPIはあくまで、

「このシステムには、こういうAPIがあります」

という契約です。


6. FastifyをHTTP層にする

HTTPサーバーにはFastifyを使います。

                    Fastify
                       │
          ┌────────────┴────────────┐
          │                         │
      ZenStack CRUD            Business API
          │                         │
          │                         ▼
          │                      UseCase
          │                         │
          └─────────────────────────┘

Fastifyをシステム全体のHTTPレイヤーとして使うことで、

  • 認証
  • Middleware / Hook
  • HTTP logging
  • Validation
  • Routing
  • Error handling

などを一箇所にまとめられます。


7. OpenAPIからBusiness APIを接続する

OpenAPIを手でFastifyのrouteに書き写すのも避けたいところです。

そこでOpenAPIからFastifyのrouteを構成します。

例えば、

operationId: approveProject

という定義から、

POST /projects/:projectId/approve
             ↓
      approveProject
             ↓
    ApproveProjectUseCase

という接続を作ります。

FastifyにはOpenAPIと連携するエコシステムがあり、OpenAPIを起点にrouteを構成することができます。

これによって、

OpenAPI
   ↓
Fastify route
   ↓
Handler

という流れを作れます。


8. UseCaseはコードにする

ここで少し意外な設計をします。

UseCase自体はSSOTにしません。

例えば、

export class ApproveProjectUseCase {
    async execute(command: {
        projectId: string;
        approvedBy: string;
    }) {

        const project =
            await db.project.findUnique({
                where: {
                    id: command.projectId,
                },
            });

        if (!project) {
            throw new Error('Project not found');
        }

        if (project.status !== 'SUBMITTED') {
            throw new Error('Project is not submitted');
        }

        return db.project.update({
            where: {
                id: command.projectId,
            },
            data: {
                status: 'APPROVED',
                approvedBy: command.approvedBy,
                approvedAt: new Date(),
            },
        });
    }
}

という実装になります。

一見すると、

UseCaseもドキュメントから自動生成すべきでは?

と思います。

しかし、ここは無理に自動化しません。


9. AIを「実装者」にする

ここでAIが登場します。

人間が、

UC-PROJECT-APPROVE.md

を整備します。

例えば、

# プロジェクト承認

## アクター

プロジェクト管理者

## 前提条件

- プロジェクトが申請済みである
- 操作者が承認権限を持っている

## 業務ルール

- 申請済み以外は承認できない
- 承認者を記録する
- 承認日時を記録する

## 結果

- プロジェクトが承認済みになる

そしてAIに、

このUseCase仕様を実装してください。

DBモデルはschema.zmodelを参照してください。
API契約はbusiness-api.ymlを参照してください。
既存の実装パターンに従ってください。
テストも作成してください。

と依頼します。

すると、

UseCase document
       │
       ▼
      AI
       │
       ├── UseCase
       ├── Unit Test
       └── Integration Test

を生成できます。


10. なぜUseCaseのコードをSSOTにしないのか

業務仕様と実装は別物だからです。

例えば、

「申請済みのプロジェクトだけ承認できる」

というルールが業務仕様です。

これを、

if (project.status !== 'SUBMITTED') {
    throw new Error(...);
}

と書くのは実装方法です。

将来、

  • TypeScriptから別の言語に変更する
  • ORMを変更する
  • アーキテクチャを変更する
  • エラー処理を変更する

といったことがあっても、

「申請済みのプロジェクトだけ承認できる」

という業務ルールは変わりません。

したがって、

業務仕様をSSOTにして、実装は交換可能にする

という考え方です。


11. AIには「仕様を考えさせない」

ここも重要です。

AIに、

プロジェクト承認APIを作って

とお願いすると、AIが業務ルールを推測してしまいます。

これは危険です。

代わりに、

Requirements
       +
UseCase document
       +
schema.zmodel
       +
business-api.yml
       ↓
      AI
       ↓
    Implementation

とします。

AIは、

「何を作るか」

ではなく、

「決められた仕様をどう実装するか」

を担当します。


12. テストもAIに生成させる

UseCase documentに、

## 業務ルール

- SUBMITTED のプロジェクトのみ承認できる
- DRAFT は承認できない
- REJECTED は承認できない
- 承認者を記録する

と書いてあれば、

AIに、

この業務ルールを網羅するテストを作成してください。

と依頼できます。

すると、

UseCase document
       │
       ├──────────→ UseCase
       │
       └──────────→ Tests

となります。

この構造にすると、業務仕様とテストの対応関係も明確になります。


13. 最終的な開発フロー

この構成での開発フローは、

① 要件を整理
       ↓
② UseCaseを定義
       ↓
③ schema.zmodelを設計
       ↓
④ business-api.ymlを設計
       ↓
⑤ AIに実装させる
       ↓
⑥ AIにテストを書かせる
       ↓
⑦ 人間がレビュー
       ↓
⑧ CI

になります。

人間がコードを一行ずつ書くことが目的ではなく、

人間が仕様を正しく定義し、AIに実装を委譲する

ことを目的にしています。


14. それぞれのツールに「考えさせる範囲」を限定する

このアーキテクチャでは、各ツールの責務をかなり明確にしています。

Requirements
    │
    │ 「何が必要か」
    ▼
UseCase
    │
    │ 「どういう業務であるか」
    ▼
schema.zmodel
    │
    │ 「どんなデータ・権限か」
    ▼
business-api.yml
    │
    │ 「どういうHTTP APIか」
    ▼
AI
    │
    │ 「どう実装するか」
    ▼
TypeScript

この境界が重要です。

例えばAIにDB設計まで考えさせるのではなく、

schema.zmodel

を与えます。

API仕様までAIに考えさせるのではなく、

business-api.yml

を与えます。

業務ルールまでAIに考えさせるのではなく、

usecase.md

を与えます。


15. このアーキテクチャのメリット

ボイラープレートを減らせる

CRUDはZenStack。

HTTP routingはFastify/OpenAPI。

型定義やクライアントもOpenAPIから生成できます。

AIにはUseCase実装とテストを任せられます。


ドキュメントとコードの乖離を減らせる

人間が管理する仕様を明確にします。

UseCase
schema.zmodel
business-api.yml

が仕様の中心です。

コードは仕様から派生するものと考えます。


AIに仕事をさせやすい

AIに大量の既存コードを読ませて、

「なんとなくこのシステムっぽく実装して」

とするのではなく、

UseCase
+
Data Model
+
API Contract
+
Coding Convention

を入力として与えられます。

これはAIにとっても扱いやすい形式です。


16. この設計の本質

結局、このアーキテクチャでやりたいことは、

ソフトウェア開発における「人間が考える部分」と「機械が作業する部分」を分離する

ことです。

人間がやること:

要件を決める
業務ルールを決める
データモデルを決める
API契約を決める

機械にやらせること:

CRUD実装
HTTP routing
型生成
UseCase実装
テスト生成
API Client生成

という分担です。


17. 最終形

最終的には、こんな開発環境を目指します。

                 HUMAN
                   │
       ┌───────────┼───────────┐
       │           │           │
       ▼           ▼           ▼
 Requirements   UseCase    Data Model
                   │        schema.zmodel
                   │
                   │
                   ▼
             business-api.yml
                   │
       ┌───────────┴───────────┐
       │                       │
       ▼                       ▼
    ZenStack                Fastify
       │                       │
       │                   Business API
       │                       │
       │                       ▼
       │                     AI
       │                       │
       │                       ▼
       │                    UseCase
       │                       │
       └───────────┬───────────┘
                   │
                   ▼
              PostgreSQL


       AI-generated
       ─────────────
       UseCase
       Tests
       Client
       Boilerplate

おわりに

AIによって「コードを書くコスト」が下がっていく一方で、これから重要になるのは、何を作るのかを機械に正しく伝えることだと思っています。

そのためには、AIにコードを書かせる前に、

  • 業務仕様
  • データモデル
  • API契約

を明確な形式で管理する必要があります。

今回考えたアーキテクチャでは、

人間が仕様を管理し、ツールが機械的な部分を生成し、AIが実装を担当する

という役割分担を目指しました。

特定の業務システムに限らず、CRUDが多く、業務APIも存在する一般的なWebバックエンドであれば応用できるのではないかと思っています。

まだ実際の開発を通して検証している途中なので、特に

  • UseCase documentをどこまで形式化するか
  • AIにどこまで実装を任せるか
  • OpenAPIとUseCaseの境界をどうするか
  • 仕様変更時にどのSSOTを変更するか

については、今後さらに検討していきたいところです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?