OpenAPI × AWS CDK × APIGateway でRest APIを管理する
OpenAPI定義からAPIGatewayをいい感じに作成できたので、伝えたかった記録になっています。
どんな流れで 「OpenAPI ×CDK」 に辿り着いたのか、つらつら書いています。
長い記事になっていますが、本当に伝えたいのは最後のCDKの話だけです!
時間が勿体ない方は最後の方だけ読んでいただけたらと思います。
【想定している層】
- APIGateway そこそこ分かる
- CDK 聞いたことある
- OpenAPI(Swagger) 聞いたことない
【私のレベル】
- APIGateway 2年くらい使っているけど未だにCorsエラーとかで沼る。
- CDK 1ヶ月前くらいから触り始めたけど、既に好き。CloudFormationに戻れない。
- OpenAPI 『スキーマ駆動開発』、とかの記事をみて気になっていて、最近初めて実際に触った。
OpenAPIとは?
「Restful APIの定義を記述するための仕様」です。
分かりやすく言えば、Restful API用の Infrastructure as Code と表現して差し支えないかと思います。(インフラではないが)
YAMLやJSONで定義ファイルを作成すると、それに基づいたAPI仕様書をエクスポートすることができます。
さらにさらに、便利な周辺ツールが充実しており、モックサーバーを動かしたり・定義したAPIを使用するためのプログラムを生成出来たりします。
と、エラそうに解説してますが、2011年からあるものらしいです。
以前は Swagger の名称であり、いろいろあってOpenAPIに変わりました。
いまでも周辺ツール群には “Swagger” の名称が残っています。
OpenAPIの基本情報は、他のサイトを参考にしてください。(間違ったことを書きたくないので…)
拙い説明よりこちらなど見ていただけたら幸いです!
OpenAPIの定義ファイルを見てみよう
実際の定義ファイルは以下のようになります。
今回の記事では内容まで詳しく説明しません。
openapi: 3.0.3
info:
title: Hello API
description: Defines an example “Hello World” API
version: "0.0.1"
paths:
"/":
get:
operationId: sayHello
summary: Say Hello
description: Prints out a greeting
parameters:
- name: name
in: query
required: false
schema:
type: string
default: "World"
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/HelloResponse"
components:
schemas:
HelloResponse:
description: Response body
type: object
properties:
message:
type: string
description: Greeting
example: Hello World!
API仕様書を生成してみよう
先ほどの定義ファイルから、ドキュメントを生成してみます。
ドキュメント生成には redoc
[1]を使いました。
以下を実行します。
redoc-cli bundle openapi.yaml
こんな感じのHTMLが生成されます。
このように、API定義書をポチポチExcelやらスプレッドシートで作る手間が省けます。
ローカルでモックサーバーを動かしてみよう
定義ファイルをもとに、ローカルのモックサーバーを動かすことも可能です。
これにより、「バックエンドがまだできていないけど、フロントからAPI実行したい!」といったニーズを簡単に満たすことができます。
ローカルでモックサーバーを動かすには、Prism
[2]というツールを利用します。
以下を実行します。
npx prism mock openapi.yaml -p 8080
すると、モックサーバーが起動します。
curlコマンドを叩いてみます。
こんな風に、モックサーバーを立ち上げることができました。
他にも、定義ファイルからそのAPIを利用するためのプログラミングコードを生成するツールなどもありますが、今回は割愛します。
とにかく、「OpenAPIは便利だ!」と伝われば良いです。
API GatewayにOpenAPI定義を反映してみる
さて、OpenAPIの説明を書いていたのは、この後の話をするためです。
実はAWS の API GatewayではOpen APIの定義ファイルを読み込んでリソースを作成する機能があります。
これを使ってみます。
ピュアなOpenAPI定義ファイルを読み込ませる
まず、ピュアなOpenAPI定義ファイルを読み込ませます。
今回はマネジメントコンソールから操作しました。
ポチポチしてインポートすると、APIのリソースが作成されました。
ただし、この状態だとAPIGatewayと紐づけるサービスの設定などを何もしていないので、使える状態ではありません。
このままでは意味が無いので、ピュアなOpenAPI定義を使うのはここまでになります。
APIGateway独自のOpenAPI拡張定義を利用する
OpenAPIの定義をAPIGatewayで使う際には、『APIGateway独自の拡張定義』を利用することができます。
例えば「Lambda統合のAPIを作成したい!」となった場合、x-amazon-apigateway-integration
の設定値を利用します。
それでは、拡張定義を利用したOpenAPI定義を作成し、Lambda統合のAPIを作成してみましょう。
以下の手順で進めていきます。
- Lambda統合用のLambda関数を作成
- OpenAPI定義を修正し、統合するLambda関数に 1 で作成したものを指定
- APIGatewayにIAM権限が足りないので設定する
1で適当にLambda関数を作成します。(マネジメントコンソールで作成しました。ここは割愛)
2で修正したOpenAPI定義は以下のようになります。
<Function Arn>に 1 で作成した関数のARNを入力します。
openapi: 3.0.3
info:
title: Hello API
description: Defines an example “Hello World” API
version: "0.0.1"
paths:
"/":
get:
operationId: sayHello
summary: Say Hello
description: Prints out a greeting
parameters:
- name: name
in: query
required: false
schema:
type: string
default: "World"
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/HelloResponse"
+ x-amazon-apigateway-integration:
+ httpMethod: "POST"
+ uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/<Function Arn>/invocations"
+ passthroughBehavior: "when_no_match"
+ type: "aws_proxy"
components:
schemas:
HelloResponse:
description: Response body
type: object
properties:
message:
type: string
description: Greeting
example: Hello World!
こちらを再度APIGatewayでインポートすると、以下のようにLambda統合の設定が完了しました。
しかし、こちらをテスト実行すると以下のエラーが出ます。
Execution failed due to configuration error: Invalid permissions on Lambda function
APIGatewayにIAMロールが割り当てられていないからです。
というわけで、Lambda実行用のロールを付けて再実行します。
まず、適切なIAMロールを作成します。(こちらは説明を割愛)
作成したIAMロールをOpenAPI定義を修正して指定します。
openapi: 3.0.3
info:
title: Hello API
description: Defines an example “Hello World” API
version: "0.0.1"
paths:
"/":
get:
operationId: sayHello
summary: Say Hello
description: Prints out a greeting
parameters:
- name: name
in: query
required: false
schema:
type: string
default: "World"
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/HelloResponse"
x-amazon-apigateway-integration:
httpMethod: "POST"
uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/<Function Arn>/invocations"
passthroughBehavior: "when_no_match"
type: "aws_proxy"
+ credentials : "<IAM Role Arn>"
components:
schemas:
HelloResponse:
description: Response body
type: object
properties:
message:
type: string
description: Greeting
example: Hello World!
これでテスト実行をすると、ようやく成功させることができました。
ここまで、しっかり読んでいただいた方も、流し読みした方も、試しているときの私も、こう思ったはずです。
「めんどくせえ!!」
OpenAPI定義からAPIGatewayのリソースを作成するのは、個人的にイケてないと思いました。
主な理由は3つです。
- OpenAPI定義にAWS用の設定値を記載するのが嫌だ
- AWS用の拡張定義を覚える時間が無駄だ
- そもそも今はCloudFormationなどのIaCでAPIGatewayも定義することが多いので、使うことがない
1について
OpenAPI定義のほうはドキュメント生成やローカルでのモックサーバー起動を主な役割としたいです。そこにAWSのAPIGatewayの設定やIAMの設定が紛れ込ませたくありません。
紛れ込むと、「OpenAPIの記法は知っているがAPIGateway用の拡張記法は知らない人」にとっては、読みづらく煩雑な定義ファイルとなってしまいます。
2について
わざわざ「APIGatewayに反映する」という1つの目的のために拡張定義の設定値を覚える・調べる手間が勿体ないと感じます。
3について
CloudFormation・SAM・Terraformといった IaC でAWSリソースを管理することが多く、「OpenAPI定義からAPIGatewayリソースを作成したい!」というニーズ自体が少ないだろうと感じました。
こんな感じに「つらみ」がありました。
これらの「つらみ」を解消するには、以下が出来れば良さそうです。
- OpenAPI定義はピュアなままで維持する
- APIGatewayなどAWSのリソースはIaCで管理する
- OpenAPI定義をAPIGatewayに上手いこと反映する
というわけで、AWS CDKを使った方法を見ていきます。
実はここからが本題です。
OpenAPI × Infrastructure as Code
CDKの話に入る前に、Cloud FomrationでOpenAPI定義を利用できないか確認してみます。
OpenAPI × CloudFormation
私は実際に試していないですが、AWS::Serverless::Api
のリソースを利用し、 DefinitionBody
の値にOpenAPIの定義ファイルを指定することが出来るようです。(厳密には SAM
のリソースです)
しかし、この場合もOpenAPIの定義側にLambda統合の設定値(x-amazon-apigateway-integration
)などを書く必要が出てくるようです。
こちらの記事[3]を参考にさせていただきました。
この状態だと、先ほど言った以下が実現できません。
- OpenAPI定義はピュアなままで維持する
- APIGatewayなどAWSのリソースはIaCで管理する
『OpenAPI × CloudFormation』はここで終わりにします。
OpenAPI × AWS CDK(標準)
AWS CDKとは、『Cloud Development Kit』の略です[4]。
AWSのインフラストラクチャをコード(TypeScript, Go, Python など)で管理できる優れものです。
現在 CDK for Terraform[5]というものも登場しており、アツい話題だと思っています。
OpenAPI と AWS CDKの組み合わせは上手く行きそうなので、紹介させていただきます。
まず、CDKの標準の機能としてOpen API定義をAPIGatewayに反映する方法はありました。
ただし、こちらの機能でも先ほどの Cloud Formation と同様に、OpenAPIの定義側にAPIGatewayの設定値を記載する必要が出てきます。
OpenAPIの定義から作成したAPIGatewayのリソースは、CDKでの管理が出来なくなってしまいます。
そのため、標準の機能では解決することが出来ません。
OpenAPI × AWS CDK(拡張)
このあたりのつらみを同じように感じている方がおり、解決するためのライブラリが既に開発されていました。
**『CDK Day』**というイベントで紹介されています。
YouTubeのvideoIDが不正です
そして、こちらのライブラリを利用すると、OpenAPIの定義はピュアなままに保ちつつ、CDKのコード側でAWSの設定値を追加することが可能になります。
npm i -D @alma-cdk/openapix
先ほど記載していたピュアなOpenAPIの定義とこちらのライブラリを使って、CDKでデプロイするための方法を見ていきましょう。
まず、先ほどのピュアなOpenAPI定義はこちらです。
x-amazon-apigateway-integration
の記載を削除しました。
openapi: 3.0.3
info:
title: Hello API
description: Defines an example “Hello World” API
version: "0.0.1"
paths:
"/":
get:
operationId: sayHello
summary: Say Hello
description: Prints out a greeting
parameters:
- name: name
in: query
required: false
schema:
type: string
default: "World"
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/HelloResponse"
components:
schemas:
HelloResponse:
description: Response body
type: object
properties:
message:
type: string
description: Greeting
example: Hello World!
そして、CDKのコードを書いていきます。
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as openapix from '@alma-cdk/openapix';
import * as path from 'path';
export class CdkOpenapiStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const greetFn = new NodejsFunction(this, "greet", {
entry: path.join(__dirname, '../lambda/hello-function.ts'),
runtime: Runtime.NODEJS_16_X,
handler: 'handler',
});
new openapix.Api(this, 'HelloApi', {
source: path.join(__dirname, '../openapi/openapi.yaml'),
paths: {
'/': {
get: new openapix.LambdaIntegration(this, greetFn),
},
},
})
}
}
注目すべきは、OpenAPI定義から作成したAPI Gatewayのリソースに、Lambda統合の設定をCDK側のコードで制御できている点です。
このようにすることで、ピュアなOpenAPIの定義は保ったまま、CDKのコード側でAWSの設定を行うことが出来ます。
というわけで、ライブラリのおかげ様で以下が実現できました。
- OpenAPI定義はピュアなままで維持する
- APIGatewayなどAWSのリソースはIaCで管理する
- OpenAPI定義をAPIGatewayに上手いこと反映する
こちらのライブラリがどれほどの完成度なのか、使い込まないことには分からないですが、使いこんでバグが出たらプルリクを投げたり、自分で修正したりして行きたいです。
また、これ以外にも自分でCDKを拡張して今回のライブラリと同じような機能を作ることも出来るはずです。
私はまだ、そこまでのレベルに達していませんが、実務で使う際には自分で拡張するところまで視野に入れていきたいです。
まとめ
- OpenAPI × APIGateway のみでは「つらみ」が多い
- CDKと
@alma-cdk/openapix
のライブラリを使うことで、OpenAPIの定義を上手いことAPIGatewayに反映できる - バグが出ないとは限らないので、実務で使う際には注意が必要
感想
本当は最後のライブラリとCDKを使うと簡単に出来る、ということを書きたかったのですが、長く書いちゃいました。
今回の作業をしている際に、自分が「このポイントがイケてない」と思ったことが、世の中的に同様の認識されていると分かったのが嬉しかったです。
自分で作業しているときに、この記事の流れと同じような順番で最後の解決に辿り着いたので「この流れを誰かにみてほしい!!」という思いもあります。
本当にCDKはお気に入りなので、今後も試していきたいです!!
Discussion