|
| 1 | +[English](./README.md) / 日本語 |
| 2 | + |
| 3 | +# cdk-rest-api-with-spec |
| 4 | + |
| 5 | +[Amazon API Gateway (API Gateway)](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html)のREST APIとその[OpenAPI](https://spec.openapis.org/oas/latest.html)定義を`cdk-rest-api-with-spec`で一度に記述。 |
| 6 | + |
| 7 | +## 誰のためのライブラリ? |
| 8 | + |
| 9 | +[AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/v2/guide/home.html)のパーツを使ってREST APIとそのOpenAPI定義を一度に書いてしまいたい方には役に立つかもしれません。 |
| 10 | +詳しくは[「背景」](#背景)をご覧ください。 |
| 11 | + |
| 12 | +## 事前準備 |
| 13 | + |
| 14 | +[Node.js](https://nodejs.org/en/) v12かそれ以降をインストールしてください。 |
| 15 | +このライブラリはNode.js v16.xで開発しました。 |
| 16 | + |
| 17 | +このライブラリはCDK**バージョン2** (CDK v2)向けに実装されており、CDKバージョン1には対応していません。 |
| 18 | + |
| 19 | +## インストール方法 |
| 20 | + |
| 21 | +このレポジトリを依存関係(`dependencies`)に追加してください。 |
| 22 | + |
| 23 | +```sh |
| 24 | +npm install https://github.com/codemonger-io/cdk-rest-api-with-spec.git#v0.1.0 |
| 25 | +``` |
| 26 | + |
| 27 | +このライブラリはCDK v2プロジェクトで使用することを想定しており、以下のモジュールは`dependencies`ではなく`peerDependencies`に含んでいます。 |
| 28 | +- [`aws-cdk-lib`](https://www.npmjs.com/package/aws-cdk-lib) |
| 29 | +- [`constructs`](https://www.npmjs.com/package/constructs) |
| 30 | + |
| 31 | +CDK v2プロジェクトで使っている限り、これらを別途インストールする必要はないはずです。 |
| 32 | + |
| 33 | +## 始める |
| 34 | + |
| 35 | +[`aws_apigateway.RestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.RestApi.html)のコンストラクタの代わりに[`RestApiWithSpec.createRestApi`](./api-docs/markdown/cdk-rest-api-with-spec.restapiwithspec.createrestapi.md)を使ってください。 |
| 36 | +この関数は[`aws_apigateway.RestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.RestApi.html)をOpenAPI定義を記述する機能で拡張したオブジェクトを返します。 |
| 37 | + |
| 38 | +```ts |
| 39 | +const api = RestApiWithSpec.createRestApi(this, 'example-api', { |
| 40 | + description: 'Example of RestApiWithSpec', |
| 41 | + openApiInfo: { |
| 42 | + version: '0.0.1', |
| 43 | + }, |
| 44 | + openApiOutputPath: 'openapi.json', |
| 45 | + // ... other options |
| 46 | +}); |
| 47 | +``` |
| 48 | + |
| 49 | +より詳しくは[使用例](#使用例)と[APIドキュメンテーション](#apiドキュメンテーション)をご参照ください。 |
| 50 | +実際に動くサンプルも[`example`](./example/README.ja.md)フォルダにあります。 |
| 51 | + |
| 52 | +## 背景 |
| 53 | + |
| 54 | +最近、API Gatewayで作成したREST APIのOpenAPI定義を書くべきだなぁという思いが強くなっていました。 |
| 55 | +私の知る限り、API Gatewayで作ったREST APIのOpenAPI定義を得るには2つの選択肢があります。 |
| 56 | +1. [既存のREST APIからOpenAPI定義をエクスポートする](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-export-api.html) |
| 57 | +2. [OpenAPI定義をインポートしてREST APIを作成する](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html) |
| 58 | + |
| 59 | +### 1. OpenAPI定義をエクスポートする |
| 60 | + |
| 61 | +追加のドキュメンテーションなしでは、API GatewayからエクスポートされたOpenAPI定義には制約が全然かかっておらず使い物になりません。 |
| 62 | +REST APIのすべての構成要素に[個別にドキュメンテーションリソースを追加](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-documenting-api.html)しなければなりません。 |
| 63 | +REST APIの構成要素を作るのと同時にドキュメント作成もできるとよい気がします。 |
| 64 | + |
| 65 | +### 2. OpenAPI定義をインポートする |
| 66 | + |
| 67 | +私はCDKのパーツを使ってAPI GatewayのREST APIを記述するのに慣れています。 |
| 68 | +以前、素の[CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html)テンプレートでREST APIを書いていましたが繰り返しが多すぎて嫌気がさしました。 |
| 69 | +CDKはその苦痛を取り除いてくれました。 |
| 70 | +素のOpenAPI定義を書くとその苦痛が戻ってくることになるのではないかと思います(試していませんが・・・)。 |
| 71 | + |
| 72 | +### 3番目の選択肢 |
| 73 | + |
| 74 | +ということで、CDKのパーツを使いつつ**REST APIとそのOpenAPI定義を一度に書く**ことのできる3番目の選択肢を求めています。 |
| 75 | +そしてこのライブラリが解決策になることを願っています。 |
| 76 | + |
| 77 | +## SpecRestApiとの違い |
| 78 | + |
| 79 | +CDKは[`aws_apigateway.SpecRestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.SpecRestApi.html)という似たような名前を持つConstructを提供しています。 |
| 80 | +[`aws_apigateway.SpecRestApi`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.SpecRestApi.html)の目的は既存のOpenAPI定義をインポートしてREST APIを作成することである一方、このライブラリの目的はその逆、REST APIを構築することでOpenAPI定義を作成することです。 |
| 81 | + |
| 82 | +## 使用例 |
| 83 | + |
| 84 | +### メソッドを記述する |
| 85 | + |
| 86 | +[`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md)メソッドの3番目の引数の`summary`と`description`プロパティを指定することができます。 |
| 87 | + |
| 88 | +```ts |
| 89 | +api.root.addMethod( |
| 90 | + 'GET', |
| 91 | + new apigateway.MockIntegration({ |
| 92 | + // ... integration settings |
| 93 | + }), |
| 94 | + { |
| 95 | + operationName: 'getRoot', |
| 96 | + summary: 'Get root', // NEW! |
| 97 | + description: 'Returns the root object', // NEW! |
| 98 | + methodResponses: [ |
| 99 | + { |
| 100 | + statusCode: '200', |
| 101 | + description: 'successful operation', |
| 102 | + }, |
| 103 | + ], |
| 104 | + } |
| 105 | +); |
| 106 | +``` |
| 107 | + |
| 108 | +`operationName`, `summary`, `description`プロパティはそれぞれOpenAPI定義における[Operation Object](https://spec.openapis.org/oas/latest.html#operation-object)の`operationId`, `summary`, `description`プロパティに対応しています。 |
| 109 | + |
| 110 | +`methodResponses`プロパティの各要素には`description`プロパティを設定することができ、これはOpenAPI定義における[Response Object](https://spec.openapis.org/oas/latest.html#response-object)の`description`プロパティに対応しています。 |
| 111 | + |
| 112 | +### リクエストパラメータを記述する |
| 113 | + |
| 114 | +[`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md)メソッドの3番目の引数の[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティでリクエストパラメータを記述することできます。 |
| 115 | + |
| 116 | +```ts |
| 117 | +findByStatus.addMethod( |
| 118 | + 'GET', |
| 119 | + new apigateway.MockIntegration({ |
| 120 | + // ... integration settings |
| 121 | + }), |
| 122 | + { |
| 123 | + operationName: 'findPetsByStatus', |
| 124 | + requestParameterSchemas: { // NEW! |
| 125 | + 'method.request.querystring.status': { |
| 126 | + description: 'Status values that need to be considered for filter', |
| 127 | + required: false, |
| 128 | + explode: true, |
| 129 | + schema: { |
| 130 | + type: 'string', |
| 131 | + enum: ['available', 'pending', 'sold'], |
| 132 | + default: 'available', |
| 133 | + }, |
| 134 | + }, |
| 135 | + }, |
| 136 | + }, |
| 137 | +); |
| 138 | +``` |
| 139 | + |
| 140 | +[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティはKey-Valueペアのマップで[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティと同じキーを受け付けますが、キーを`boolean`値ではなくOpenAPI定義における[Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object)を表すオブジェクト(`name`と`in`プロパティを除く)にマップします。 |
| 141 | +[Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object)の`name`と`in`プロパティはキーから導出されます。 |
| 142 | +ということで上記の[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)は以下の[Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object)になります。 |
| 143 | + |
| 144 | +```ts |
| 145 | +[ |
| 146 | + { |
| 147 | + name: 'status', |
| 148 | + in: 'query', |
| 149 | + description: 'Status values that need to be considered for filter', |
| 150 | + required: false, |
| 151 | + explode: true, |
| 152 | + schema: { |
| 153 | + type: 'string', |
| 154 | + enum: ['available', 'pending', 'sold'], |
| 155 | + default: 'available', |
| 156 | + }, |
| 157 | + }, |
| 158 | +] |
| 159 | +``` |
| 160 | + |
| 161 | +[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティを指定した場合、[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティを指定する必要はありません。 |
| 162 | +ベースとなる[`aws_apigateway.IResource.addMethod`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IResource.html#addwbrmethodhttpmethod-target-options)に渡される[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティは[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティから`requestParameters[key] = requestParameterSchemas[key].required`となるように生成されます。 |
| 163 | + |
| 164 | +[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティを省略し[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティを指定した場合、[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティから最低限の[Parameter Object](https://spec.openapis.org/oas/latest.html#parameter-object)を作成します。 |
| 165 | +以下のオブジェクトを[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)プロパティに指定したとすると、 |
| 166 | + |
| 167 | +```ts |
| 168 | +{ |
| 169 | + 'method.request.querystring.status': false, |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +以下のようになります。 |
| 174 | + |
| 175 | +```ts |
| 176 | +[ |
| 177 | + { |
| 178 | + name: 'status', |
| 179 | + in: 'query', |
| 180 | + required: false, |
| 181 | + schema: { |
| 182 | + type: 'string', |
| 183 | + }, |
| 184 | + }, |
| 185 | +] |
| 186 | +``` |
| 187 | + |
| 188 | +[`requestParameters`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.MethodOptions.html#requestparameters)と[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティの両方を指定した場合、[`requestParameterSchemas`](./api-docs/markdown/cdk-rest-api-with-spec.methodoptionswithspec.requestparameterschemas.md)プロパティが優先されます。 |
| 189 | + |
| 190 | +### モデル(スキーマ)を定義する |
| 191 | + |
| 192 | +[`IRestApiWithSpec.addModel`](./api-docs/markdown/cdk-rest-api-with-spec.irestapiwithspec.addmodel.md)メソッドはOpenAPI定義における[Components Object](https://spec.openapis.org/oas/latest.html#components-object)の`schemas`プロパティに[Schema Object](https://spec.openapis.org/oas/latest.html#schema-object)を追加します。 |
| 193 | +以下は例です。 |
| 194 | + |
| 195 | +```ts |
| 196 | +const petModel = api.addModel('PetModel', { |
| 197 | + description: 'A pet', |
| 198 | + contentType: 'application/json', |
| 199 | + schema: { |
| 200 | + schema: apigateway.JsonSchemaVersion.DRAFT4, |
| 201 | + title: 'pet', |
| 202 | + description: 'A pet', |
| 203 | + type: apigateway.JsonSchemaType.OBJECT, |
| 204 | + properties: { |
| 205 | + id: { |
| 206 | + description: 'ID of the pet', |
| 207 | + type: apigateway.JsonSchemaType.INTEGER, |
| 208 | + format: 'int64', |
| 209 | + example: 123, |
| 210 | + }, |
| 211 | + name: { |
| 212 | + description: 'Name of the pet', |
| 213 | + type: apigateway.JsonSchemaType.STRING, |
| 214 | + example: 'Monaka', |
| 215 | + }, |
| 216 | + }, |
| 217 | + }, |
| 218 | +}); |
| 219 | +``` |
| 220 | + |
| 221 | +[`IRestApiWithSpec.addModel`](./api-docs/markdown/cdk-rest-api-with-spec.irestapiwithspec.addmodel.md)メソッドの2番目の引数の`schema`プロパティ([`JsonSchemaEx`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.md))はOpenAPI定義において等価な[Schema Object](https://spec.openapis.org/oas/latest.html#schema-object)に翻訳されます。 |
| 222 | + |
| 223 | + [`IResourceWithSpec.addMethod`](./api-docs/markdown/cdk-rest-api-with-spec.iresourcewithspec.addmethod.md)メソッドの3番目の引数の`methodResponses`プロパティにおける`responseModels`プロパティから参照した[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)はOpenAPI定義でその[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)に対応するスキーマへの参照に置き換えられます。 |
| 224 | + |
| 225 | +以下の例における`methodResponses`プロパティは、 |
| 226 | + |
| 227 | +```ts |
| 228 | +petId.addMethod( |
| 229 | + 'GET', |
| 230 | + new apigateway.MockIntegration({ |
| 231 | + // ... integration settings |
| 232 | + }), |
| 233 | + { |
| 234 | + operationName: 'getPetById', |
| 235 | + methodResponses: [ |
| 236 | + { |
| 237 | + statusCode: '200', |
| 238 | + description: 'successful operation', |
| 239 | + responseModels: { |
| 240 | + 'application/json': petModel, |
| 241 | + }, |
| 242 | + }, |
| 243 | + ], |
| 244 | + }, |
| 245 | +); |
| 246 | +``` |
| 247 | + |
| 248 | +OpenAPI定義において以下のような[Responses Object](https://spec.openapis.org/oas/latest.html#responses-object)になります。 |
| 249 | + |
| 250 | +```ts |
| 251 | +{ |
| 252 | + '200': { |
| 253 | + description: 'successful operation', |
| 254 | + content: { |
| 255 | + 'application/json': { |
| 256 | + schema: { |
| 257 | + '$ref': '#/components/schemas/exampleapiPetModel43E308F7' |
| 258 | + }, |
| 259 | + }, |
| 260 | + }, |
| 261 | + }, |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)に付与されるCloudFormationのリソースIDが[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)の参照パスを表現するのに使われます。 |
| 266 | + |
| 267 | +[`aws_apigateway.JsonSchema`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.JsonSchema.html)の拡張である[`JsonSchemaEx`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.md)には[`modelRef`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.modelref.md)という追加のプロパティがあります。 |
| 268 | +[`modelRef`](./api-docs/markdown/cdk-rest-api-with-spec.jsonschemaex.modelref.md)プロパティを使うとスキーマから別の[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)を参照することができます。 |
| 269 | +以下は配列要素の型を指定するのに別の[`aws_apigateway.IModel`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IModel.html)を参照する例です。 |
| 270 | + |
| 271 | +```ts |
| 272 | +const petArrayModel = api.addModel('PetArrayModel', { |
| 273 | + description: 'An array of pets', |
| 274 | + contentType: 'application/json', |
| 275 | + schema: { |
| 276 | + schema: apigateway.JsonSchemaVersion.DRAFT4, |
| 277 | + title: 'petArray', |
| 278 | + description: 'An array of pets', |
| 279 | + type: apigateway.JsonSchemaType.ARRAY, |
| 280 | + items: { |
| 281 | + modelRef: petModel, |
| 282 | + }, |
| 283 | + }, |
| 284 | +}); |
| 285 | +``` |
| 286 | + |
| 287 | +### Authorizerを記述する |
| 288 | + |
| 289 | +[`augmentAuthorizer`](./api-docs/markdown/cdk-rest-api-with-spec.augmentauthorizer.md)関数を使って既存の[aws_apigateway.IAuthorizer](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.IAuthorizer.html)をOpenAPI定義のためのプロパティで拡張することができます。 |
| 290 | + |
| 291 | +```ts |
| 292 | +const authorizer = augmentAuthorizer( |
| 293 | + new apigateway.TokenAuthorizer( |
| 294 | + this, |
| 295 | + 'ExampleAuthorizer', |
| 296 | + { |
| 297 | + handler: new nodejs.NodejsFunction(this, 'authorizer', { |
| 298 | + description: 'Example authorizer', |
| 299 | + runtime: lambda.Runtime.NODEJS_16_X, |
| 300 | + }), |
| 301 | + }, |
| 302 | + ), |
| 303 | + { |
| 304 | + type: 'apiKey', |
| 305 | + in: 'header', |
| 306 | + name: 'Authorization', |
| 307 | + }, |
| 308 | +); |
| 309 | +``` |
| 310 | + |
| 311 | +[`augmentAuthorizer`](./api-docs/markdown/cdk-rest-api-with-spec.augmentauthorizer.md)関数の2番目の引数はOpenAPI定義においてAuthorizerを記述する[Security Scheme Object](https://spec.openapis.org/oas/latest.html#security-scheme-object)です。 |
| 312 | + |
| 313 | +## APIドキュメンテーション |
| 314 | + |
| 315 | +最新のAPIドキュメンテーションは[`api-docs/markdown/index.md`](./api-docs/markdown/index.md)にあります(日本語版はありません)。 |
| 316 | + |
| 317 | +## 開発 |
| 318 | + |
| 319 | +### 依存関係を解決する |
| 320 | + |
| 321 | +```sh |
| 322 | +npm install |
| 323 | +``` |
| 324 | + |
| 325 | +### ライブラリをビルドする |
| 326 | + |
| 327 | +```sh |
| 328 | +npm run build |
| 329 | +``` |
| 330 | + |
| 331 | +`dist`フォルダ内で以下のファイルが作成または更新されます。 |
| 332 | +- `index.js` |
| 333 | +- `index.js.map` |
| 334 | +- `index.d.ts` |
| 335 | + |
| 336 | +`dist`フォルダは存在しなければ作成されます。 |
| 337 | + |
| 338 | +このライブラリのAPIに変更があると[`api-docs/cdk-rest-api-with-spec.api.md`](./api-docs/cdk-rest-api-with-spec.api.md)ファイルも更新されます。 |
| 339 | + |
| 340 | +### ドキュメンテーションを生成する |
| 341 | + |
| 342 | +```sh |
| 343 | +npm run doc |
| 344 | +``` |
| 345 | + |
| 346 | +[`api-docs/markdown`](./api-docs/markdown)フォルダのコンテンツを置き換えます。 |
0 commit comments