Slide 1

Slide 1 text

Honoを技術選定したAI要件定義プラットフォーム Acsimでの意思決定 スキーマ駆動開発による型安全な開発体験 株式会社ROUTE06 CTO 重岡 正 1

Slide 2

Slide 2 text

自己紹介 重岡 正 (SHIGEOKA Tadashi) 株式会社ROUTE06 取締役 CTO / Co-founder 普段はフルリモートワーク 🏠 本日は熊本からやって参りました 🚄 2

Slide 3

Slide 3 text

背景: 課題の整理 解決したかった課題 Frontend・API間の実装速度の向上 実装の一貫性を保ちたい 開発体験を改善したい 型情報の共有による品質向上 型不一致によるバグを防ぎたい リファクタリングを安全にしたい 3

Slide 4

Slide 4 text

決定: OpenAPIを使ったスキーマ駆動開発 アーキテクチャ API (Hono + @hono/zod-openapi) ↓ OpenAPI Schema 生成 ↓ Frontend (orval) ↓ 型情報 + TanStack Query Client 生成 4

Slide 5

Slide 5 text

なぜHonoか: @hono/zod-openapi の選定理由 1. TypeScriptネイティブな記述 const route = createRoute({ method: 'post', path: '/users', request: { body: { content: { 'application/json': { schema: UserSchema } } } }, responses: { 200: { content: { 'application/json': { schema: UserSchema } } } } }); YAML/JSONではなくTypeScriptで記述可能 学習コストが低い 5

Slide 6

Slide 6 text

なぜHonoか: @hono/zod-openapi の選定理由 2. Honoとの高い親和性 Honoの開発者が作成したライブラリ Honoのミドルウェアとシームレスに統合 フレームワークとスキーマ定義が一体化 3. 言語非依存の拡張性 OpenAPIは統一規格 将来的に別言語へ移行する際も活用可能 6

Slide 7

Slide 7 text

なぜorvalか: Frontend側のコード生成 TanStack Queryとの親和性 // 自動生成されたコード export const useGetUsers = () => { return useQuery({ queryKey: ['users'], queryFn: () => getUsersApi(), }); }; TanStack Query用のhooksを自動生成 Query Key管理を自動化 リクエスト処理を簡略化 7

Slide 8

Slide 8 text

他の選択肢との比較 検討した選択肢 方式 採用 理由 orvalでAPI/Frontend両方生成 Middlewareの差し込みが困難 openapi-generator TanStack Queryとの連携で設定量が多い @hono/zod-openapi + orval 学習コスト・親和性が最適 8

Slide 9

Slide 9 text

メリット: 得られた開発体験 1. 型安全性の確保 Frontend・API間の型不一致を防止 コンパイル時にエラーを検出 2. 開発速度の向上 APIクライアントコードの自動生成 ボイラープレートの削減 3. 保守性の向上 Query Key管理の自動化 一貫性のあるコードベース 9

Slide 10

Slide 10 text

デメリット: トレードオフ 運用上のコスト API変更時にOpenAPI Schemaの更新が必要 自動生成の仕組みで軽減 OpenAPI Schemaの記述コスト TypeScriptで記述できるため学習コストは低い orvalの設定コスト 初期設定は必要だが、 一度設定すれば再利用可能 10

Slide 11

Slide 11 text

実践例: レスポンススキーマバリデーションミドルウェア 背景と課題 フロントエンドとAPI間は型定義により型安全性が保証されているが、 バックエンド側のレス ポンスには問題がありました: 型チェックの限界: TypeScriptの型は必要なプロパティの存在のみをチェックし、 余分 なプロパティを検出できない セキュリティリスク: パスワードなどの機密情報を誤ってレスポンスに含めてもコンパイ ルエラーにならない レビューだけでは不十分: 人的チェックだけで防ぐのは困難かつ影響度が大きい 11

Slide 12

Slide 12 text

解決策 実行時にZodスキーマでレスポンスをパースすることで: 1. 型定義に含まれないプロパティを自動的に除外 2. 機密情報の誤送信を防止 3. レスポンスの形式を保証 使い方 ...createResponsesAndMiddleware({ response: { schema: getAsIsPatternResponseSchema, status: 200, description: "asIsの業務パターンの詳細を取得します", }, }), 12

Slide 13

Slide 13 text

実装のポイント export const createResponsesAndMiddleware = ({ response, middleware: _middleware = [], }: { response: { schema: T; status: U; description: string }; middleware?: MiddlewareHandler[]; }) => { const { schema, status, description } = response; const responses: { [key in U]: { content: { "application/json": { schema: T } }; description: string } } = { [status]: { content: { "application/json": { schema, }, }, description, }, }; // レスポンススキーマバリデーションミドルウェアを自動で適用 const middleware = [responseSchemaValidationMiddleware(schema, status), ..._middleware]; return { responses, middleware }; }; 13

Slide 14

Slide 14 text

実装のポイント export const responseSchemaValidationMiddleware = ( schema: T, status: U ): MiddlewareHandler => { return async (c, next) => { await next(); // 成功ステータスコード(200-299)のJSONレスポンスのみを検証 if (c.res.headers.get("Content-Type") !== "application/json") return; if (!isSuccessStatusCode(c.res.status)) return; const responseData = await c.res.json(); const result = schema.safeParse(responseData); if (!result.success) { // パース失敗時は500エラーを返し、問題を即座に検出 c.res = c.json({ message: "Response schema validation failed", errors: result.error.flatten().fieldErrors }, 500); return; } // ZodのsafeParseにより余分なプロパティを安全に除外 c.res = c.json(result.data, status); }; }; 14

Slide 15

Slide 15 text

実践例: レスポンススキーマバリデーションミドルウェア 効果 型安全性をコンパイル時+実行時の両方で担保: セキュリティ向上: 機密情報の誤送信を防止 データ整合性: レスポンスの形式を保証 開発体験: 問題を即座に検出・修正可能 これにより、 スキーマ駆動開発のメリットを最大限に活用できています。 15

Slide 16

Slide 16 text

まとめ Honoを選んだ理由 1. TypeScriptネイティブな開発体験 2. スキーマ駆動開発による型安全性 3. TanStack Queryとの高い親和性 4. 学習コストの低さ 結果 型安全な開発体験の実現 開発速度の向上 保守性の高いコードベース 16

Slide 17

Slide 17 text

参考資料 @hono/zod-openapi orval Acsim - AI要件定義プラットフォーム 株式会社ROUTE06 | AI駆動開発プラットフォーム | ROUTE06 About me 17