はじめに
最近、AIにコードを書かせることが当たり前になってきました。
CursorやCopilotでガンガンコード生成する、いわゆるvibeコーディングってやつです。
自分も使い始めた頃はなんて便利なんだと感動していたのですが、しばらくすると壁にぶつかりました。
「動くコードは出てくる。でも、意図した設計になっていない。」
「最初に作ろうと思っていたものと何か違うな。」
この記事はその体験から始まります。
vibeコーディングあるある
たとえば、こんな指示を出したとします。
「CognitoとAPI GatewayとLambda,Dynamoで認証付きAPIを作って」
コードは出てきます。動きます。でも出てきたコードを見ると……
- userIdをリクエストボディで受け取っている(本来はトークンから自動取得すべき)
- DynamoDBのテーブル設計がアクセスパターンに合っていない
要するに、動くけど直すを繰り返すことになる。
この後、何度もAIに修正依頼を出して機能改修に取り組むことになったり、自分の開発力じゃ修正できないから許容することになったことが何度もあります。
なぜこうなるのか
原因はシンプルです。
vibeコーディングは、仕様を決めずにいきなりコードを生成するからです。
設計をすっ飛ばしているから、AIはそのとき一番それっぽいコードを出す。
設計意図が伝わっていないから、自分の想定とズレたものが出てくる。
この問題を解決するには、コードを書く前に設計を固める必要があります。
でもそれを毎回手でやるのは面倒……。
Kiroとの出会い
そんなときに Kiro という AI IDE を知りました。
Kiroが他のAI IDEと違うのは、**「いきなりコードを書かない」**という点です。
vibeコーディング: 指示 → コード
Kiro: 指示 → 仕様書(spec)→ 設計書(design)→ タスク一覧(tasks)→ コード
Spec駆動開発と呼ばれるこのアプローチ、仕様と設計を先に固めてからコードを生成するため、「動くけど意図と違う」が原理的に起きにくい。
「これなら設計意図がズレない状態でコード生成できるのでは?」
そう思って、実際に試してみることにしました。
今回の題材
検証の題材として選んだのは 「認証付きメモ管理API」 です。
[クライアント]
|
v
[API Gateway]
|
+-- /auth/signup --> [Lambda] --> [Cognito]
+-- /auth/login --> [Lambda] --> [Cognito]
|
+-- /memos (POST) --(Cognito Authorizer)--> [Lambda] --> [DynamoDB]
+-- /memos (GET) --(Cognito Authorizer)--> [Lambda] --> [DynamoDB]
+-- /memos/{id} (GET) --(Cognito Authorizer)--> [Lambda] --> [DynamoDB]
+-- /memos/{id} (DELETE) --(Cognito Authorizer)--> [Lambda] --> [DynamoDB]
| サービス | 役割 |
|---|---|
| API Gateway | HTTPリクエストの受付・ルーティング・認証チェック |
| Cognito | ユーザー管理・トークン発行 |
| Lambda (TypeScript) | ビジネスロジック |
| DynamoDB | メモデータの永続化 |
| AWS CDK | インフラのコード管理 |
KiroがSpecを生成する
仕様書として以下の内容をKiroに渡しました(要約):
「メールアドレスとパスワードで登録・ログインができ、自分のメモを作成・閲覧・削除できるAPI。他人のメモは見えない。Cognito + API Gateway + Lambda + DynamoDB + CDKで構築する。」
すると Kiro は 3つのドキュメントを自動生成 しました。
生成されたドキュメント① requirements.md(要件定義書)
ユーザーストーリー形式で要件が整理されます。
### 要件3: メモ作成
**ユーザーストーリー:**
ログイン済みユーザーとして、新しいメモを作成したい。
そうすることで、情報を記録できる。
#### 受入基準
1. WHEN 認証済みユーザーがメモ内容を提供する
THEN Lambda SHALL 新しいメモをDynamoDBに保存する
2. WHEN メモが作成される
THEN Lambda SHALL 一意のメモIDを生成する
3. WHEN メモが作成される
THEN Lambda SHALL 作成日時をタイムスタンプとして記録する
4. WHEN メモが作成される
THEN Lambda SHALL メモを作成したユーザーのIDを記録する
5. WHEN 未認証のリクエストを受信する
THEN API_Gateway SHALL リクエストを拒否し、401エラーを返す
WHEN ~ THEN ~ SHALL という形式で、各機能の受入基準が明文化されます。
この段階で「何をするシステムか」が確定します。
要件は全9項目生成されました:
- 要件1: ユーザー登録
- 要件2: ユーザーログイン
- 要件3: メモ作成
- 要件4: メモ閲覧
- 要件5: メモ削除
- 要件6: データの分離
- 要件7: APIエンドポイント
- 要件8: エラーハンドリング
- 要件9: インフラストラクチャのデプロイ
生成されたドキュメント② design.md(設計書)
ここが vibeコーディングとの最大の違いです。
コードを1行も書く前に、設計判断が明文化されます。
アーキテクチャ図(Mermaid)
認証フローの設計判断
### Cognito Authorizer設定
- Authorizationヘッダーから `Bearer <token>` 形式でトークンを取得
- Cognitoでトークンを検証
- 検証成功時、ユーザーIDをLambdaのイベントコンテキストに追加
Lambda側ではトークン検証済みのユーザー情報(
event.requestContext.authorizer.claims)からuserIdを取得する
これが重要なポイントです。「APIの認証はCognito Authorizerで行う。Lambda側ではclaimsからuserIdを取り出す」 という設計判断が、コードを書く前に文書として確定するわけです。
vibeコーディングでは、ここをAI任せにするから「Lambda側でJWT検証を始める」ずれが発生していました。
DynamoDBのテーブル設計
**テーブル名**: Memos
**インデックス設計**:
1. プライマリインデックス:
- パーティションキー: memoId
- 用途: 特定メモの取得・削除
2. グローバルセカンダリインデックス(GSI): UserIdIndex
- パーティションキー: userId
- ソートキー: createdAt
- 用途: ユーザーのメモ一覧取得(作成日時順)
「ユーザーのメモ一覧を取得する」というアクセスパターンに対して、GSIを設計することまでspecに含まれます。
生成されたドキュメント③ tasks.md(実装タスク一覧)
要件と設計を踏まえ、実装手順がタスク分解されます。
- [x] 1. プロジェクト構造とCDKスタックのセットアップ
- [x] 2. 共通型定義とエラークラスの実装
- [x] 2.1 共通型定義ファイルの作成
- [x] 2.2 カスタムエラークラスの実装
- [x] 3. DynamoDBテーブルとCognitoのCDK定義
- [x] 3.1 DynamoDBテーブルをCDKで定義
- [x] 3.2 Cognito User PoolをCDKで定義
- [x] 4. ユーザー認証Lambda関数の実装
- [x] 4.1 Signup Lambda関数の実装
- [x] 4.3 Login Lambda関数の実装
- [x] 5. API GatewayとCognito Authorizerの設定
- [x] 7. メモ作成Lambda関数の実装
- [x] 8. メモ取得Lambda関数の実装
- [x] 9. メモ削除Lambda関数の実装
- [x] 10. Lambda関数へのIAM権限付与
- [x] 14. デプロイ用スクリプトとドキュメントの作成
各タスクには対応する要件番号(_要件: 3.1, 3.2...)が紐づいており、要件→設計→実装のトレーサビリティが保たれています。
ここまでのまとめ
Kiroに仕様を渡すと、以下の3つが自動生成されます。
| ドキュメント | 内容 | 役割 |
|---|---|---|
| requirements.md | ユーザーストーリー + 受入基準 | 「何を作るか」の確定 |
| design.md | アーキテクチャ + 各コンポーネントの設計 | 「どう作るか」の確定 |
| tasks.md | 実装タスクの分解(要件との紐付き) | 「どの順で作るか」の確定 |
コードを1行も書く前に設計が確定する。
これがSpec駆動開発の本質です。
次回予告
Part2では、このspecをもとに実際にAWSサーバーレスAPIを実装した過程を紹介します。
- CDKスタックの実装(Cognito Authorizerの設定など)
- Lambda関数の実装
- specの通りに生成されたコードと手動修正が必要だったコードの具体例
「specがあれば完璧に生成されるのか?」という疑問の答えも、正直に書きます。


