Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スキーマ駆動開発による品質とスピードの両立 - 私達は何故、スキーマを書くのか

スキーマ駆動開発による品質とスピードの両立 - 私達は何故、スキーマを書くのか

Postman API Night Tokyo 2024 Spring
https://postman.connpass.com/event/309418/

#APINight

武田 憲太郎

April 17, 2024
Tweet

More Decks by 武田 憲太郎

Other Decks in Programming

Transcript

  1. ⾃⼰紹介 2024/4/18 #APINight @KentarouTakeda / 武⽥ 憲太郎 Webアプリケーションエンジニア • サーバサイド:

    PHP - 2002年頃〜 • 型の扱いが⽐較的緩かった⾔語 • フロントエンド: TypeScript - 2013年頃〜 • 発表当初より厳密な型システムを持っていた⾔語 APIへの型付けや仕様と実装の乖離に、 課題感を持ち続けている。 スキーマ駆動開発による品質とスピードの両⽴ 1
  2. APIファーストが解決した課題ともたらした困難 2024/4/18 #APINight • 仕様書の無い開発 • 仕様書のある開発 • 「仕様書」は依存に値するか? •

    仕様書の冗⻑化に過ぎない実装 • 隠蔽されない知識 スキーマ駆動開発による品質とスピードの両⽴ 3
  3. 仕様書の無い開発 2024/4/18 #APINight • サーバサイドが出⼒するプロパティ名と フロントエンドのそれとが連動 • フロントエンドのコンポーネントが サーバサイドの実装に依存 //

    サーバサイド: UserController.php public function show(User $user): array { return response()->json([ 'name' => $user->name, 'email' => $user->email, 'birthdate' => $user->birthdate ->toIso8601String(), ]); } // フロントエンド: UserComponent.tsx useEffect(() => { axios.get("/api/users/" + id) .then(response => setUser({ name: response.data.name, email: response.data.email, birthdate: new Date(response.data.birthdate), }) }); スキーマ駆動開発による品質とスピードの両⽴ 4
  4. 「仕様書」は依存に値するか? 2024/4/18 #APINight • ⾃然⾔語 • 表現⼒の限界 • 解釈の余地 •

    管理 • リビジョン • ブランチ • マージ • 強制⼒ • 仕様と実装の乖離 スキーマ駆動開発による品質とスピードの両⽴ 7
  5. 仕様書の冗⻑化に過ぎない実装 2024/4/18 #APINight <label for="email">メールアドレス</label> <input type="email" name="email" id="email" maxlength="128">

    <label for="password">パスワード</label> <input type="password" name="password" id="password" maxlength="32"> <label for="name">名前</label> <input type="text" name="name" id="name" maxlength="20"> <label for="birthdate">生年月日</label> <input type="date" name="birthdate" id="birthdate"> <!-- 省略 --> スキーマ駆動開発による品質とスピードの両⽴ 8
  6. 仕様書の冗⻑化に過ぎない実装 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 9 axios.post("/api/users", { email: form.email, password:

    form.password, name: form.name, birthdate: form.birthdate, // 省略 }).then(response => { // 省略: レスポンスを描画 });
  7. 仕様書の冗⻑化に過ぎない実装 2024/4/18 #APINight class CreateUserRequest extends FormRequest { public function

    rules(): array { return [ 'email' => [ 'required', 'email', 'max:128' ], 'password' => [ 'required', 'string', 'max:32' ], 'name' => [ 'required', 'string', 'max:20' ], 'birthdate' => [ 'nullable', 'date' ], 'organizations' => [ 'required', 'array' ], 'organizations.*' => [ 'string' ], ]; } } スキーマ駆動開発による品質とスピードの両⽴ 10
  8. 隠蔽されない知識 2024/4/18 #APINight API仕様書に準拠した実装 • ⽬的: IDを指定しユーザー情 報を取得する。 • ⼿段:

    仕様通りに URLを組み ⽴てGETリクエストを送る。 ネットワークの知識が フロントに漏れ出している。 抽象化された実装 • ⽬的: IDを指定しユーザー情 報を取得する。 • ⼿段: IDを指定しユーザー情 報を取得する。 適切な隠蔽が ⽬的と⼿段を⼀致させる。 // GET /api/users/{id} const users = await axios .get('/api/users/' + id); // UserApiクラスのメソッド呼び出し const user = await userApi .getUserById(id); スキーマ駆動開発による品質とスピードの両⽴ 12
  9. スキーマ駆動開発による品質とスピードの両⽴ 2024/4/18 #APINight • ⾼品質で安定したものに依存する • フロントエンド開発 • サーバサイドでのスキーマ管理と統合 •

    TDDとスキーマ駆動開発 • スキーマの破壊的変更 • エラー原因の提供 • リリース後の品質向上 スキーマ駆動開発による品質とスピードの両⽴ 13
  10. ⾼品質で安定したものに依存する 2024/4/18 #APINight プリミティブな値は Json Schemaで表現可能 • 数値 { "type":

    "number" } • 整数 { "type": "integer" } • ⾃然数 { "type": "integer", "minimum": 1 } • non-empty-array<string> { "type": "array": "items": { "type": "string" }, minItems: 1 } プリミティブなドメインは OpenAPI Specificationで表現可能 • ⽂字列・UI上はマスクを推奨・8⽂字以上 { "type": "string", "format": "password", "minLength": 8 } • ⽇時・ISO8601形式の⽂字列 { "type": "string", "format": "date-time" } • URL⽂字列 { "type": "string", "format": "uri" } 「解釈の余地」が問題を招く → インターフェース記述⾔語による厳密化 スキーマ駆動開発による品質とスピードの両⽴ 14
  11. ⾼品質で安定したものに依存する 2024/4/18 #APINight インターフェース記述⾔語の導⼊により: • 間違いが機械的に指摘される。 • 補完(エディタ・IDE) • Lint

    / 静的解析 • Spectral • IBM OpenAPI Validator • 実⾏時検査 • 履歴が残る。 • Git ⼈間の判断は間違える → マシンリーダブルな⾔語で判断を⾃動化 スキーマ駆動開発による品質とスピードの両⽴ 15
  12. ⾼品質で安定したものに依存する 2024/4/18 #APINight OpenAPIドキュメントそれ⾃体に強制⼒は無い。 実⽤的に使うには、これと同じ強制が必要。 class User implements Authenticatable {

    } // Fatal error: // Class UserModel contains 6 abstract methods and must therefore be declared ... スキーマ駆動開発による品質とスピードの両⽴ 17
  13. ⾼品質で安定したものに依存する 2024/4/18 #APINight • コード⾃動⽣成: OpenAPI Generator • 仕様に準拠(=正しく依存)したライブラリが⾃動⽣成される •

    「ビルドが通れば問題なし」と判断できる • バリデーション⾃動化 - OpenAPI PSR-7 Message Validator • ドキュメント: Redocly • ⼿動テスト: Swagger UI • APIテスト: runn • プラットフォーム: Integrate Postman with OpenAPI • ドキュメント /⼿動テスト / APIテスト / バリデーション⾃動化 • デリバリー / 監視 スキーマ駆動開発による品質とスピードの両⽴ 18
  14. フロントエンド開発 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 20 OpenAPI Generatorによる⾃動⽣成ライブラリは: • tagがクライアントライブラリのクラスに変換される。 •

    operationIdが各クラスのメソッドに変換される。 • schemaが各⾔語の型定義やクラスに変換される。 • enumが各⾔語のenumや定数に変換される。 • リクエストレスポンスへの型付けが⾏われる。 • descriptionやsummaryが各⾔語のDocBlockに反映される。 • deprecatedなどのメタデータもDocBlockに反映される。 • date-timeが各⾔語の⽇時型と透過的に変換される。 以上の機能を持つ。
  15. フロントエンド開発 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 21 • 型安全 • エディタ補完・ホバーヒント •

    静的解析・コンパイルチェック • 抽象化 • URLやHTTPメソッドを隠蔽したAPIクライアント // UserApiクラスのメソッド呼び出し const user = await userApi .getUserById(id); // メソッド補完
  16. サーバサイドでのスキーマ管理と統合 2024/4/18 #APINight • デメリット • サーバサイド(APIサーバ)のコードに責務が集中する • メリット •

    API仕様はサーバサイドの担当者が書くことが多い • URL(ルーティング)はサーバサイドに実装される • 型(バリデーション)はサーバサイドに実装される • 仕様と実装とを⼀致させやすい • 他の⽅法にいつでも移⾏できる スキーマ駆動開発による品質とスピードの両⽴ 22
  17. サーバサイドでのスキーマ管理と統合 2024/4/18 #APINight • 仕様をアノテーションで宣⾔。すぐ下に実装。 • レスポンスの型定義(仕様)と⽣成(実装)とを紐づけ。 ここでの定義はOpenAPIドキュメントとしていつでも出⼒可能 #[OA¥Schema( title:

    'User', properties: [ new OA¥Property(property: 'name', type: 'string'), new OA¥Property(property: 'email', type: 'string', format: 'email'), new OA¥Property(property: 'birthdate', type: 'string', format: 'date-time'), ], required: ['name', 'email', 'birthdate'], )] class UserResource extends JsonResource { public function toArray(Request $request): array { return [ 'name' => $this->resource->name, 'email' => $this->resource->email, 'birthdate' => $this->resource->birthdate->toIso8601String(), ]; } } スキーマ駆動開発による品質とスピードの両⽴ 23 仕様 実装
  18. TDDとスキーマ駆動開発 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 25 • Red: • 失敗するテストを書く •

    テストは失敗する • Green: • テストに通るコードを書く • テストは成功する • Refactor: • テスト成功を維持しつつリファクタ リング • Schema: • スキーマを先に書く • リクエストは失敗する • Code: • 仕様準拠したコードを書く • リクエストは成功する • Refactor: • リクエスト成功を維持しつつリファ クタリング リクエストレスポンスのテストを1本書けば Red / Green / Refactor が回る。
  19. スキーマの破壊的変更 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 27 破壊的変更の発⽣条件 • リクエストの型を狭める • レスポンスの型を広げる

    • 型を変更する SOLID原則: リスコフの置換原則への違反 • 事前条件を、派⽣型で強めることはできない。 • 事後条件を、派⽣型で弱めることはできない。 破壊的変更の条件を予め知っておくことで 発⽣の際の対応も容易に
  20. エラー原因の提供 2024/4/18 #APINight •400 Bad Request フロントエンドに不⾜なく理由を 知らせる(レスポンス) •500 Internal

    Server Error サーバ再度が容易に理由を知れる ようにする(ログ・レスポンス) { "title": "Response Validation Failed", "status": 500, // エラー位置 "pointer": [ "data", "status" ], // エラー理由 "detail": "Value cannot be null", // オリジナルのレスポンス "originalResponse": { "data": { "id": 42, "status": null, "name": "yokawasa", "content": "APINight" } } } スキーマ駆動開発による品質とスピードの両⽴ 28
  21. エラー原因の提供 2024/4/18 #APINight スキーマ駆動開発による品質とスピードの両⽴ 29 本番リリース後も仕様外レスポンスを追跡 • クラッシュさせずログに残しアラートと連携 • リリース直後など⼗分に安定していないサービス

    • 既存サービスへ後からスキーマ駆動開発を導⼊するケース • 開発時と同じようにエラーとする • 堅牢性よりも正当性を優先しなければならないサービス • バリデーションそのものを外す • ⻑期の運⽤を経て⼗分に安定したサービス • スループットが重要なサービス