どんな内容か
CDKにてAPIGateway+Lambdaの構成を作る際に、RestApiではなくOpenAPI定義からAPIGatewayを作ることができるSpecRestApiを使ってみたという内容になります。
OpenAPIとは
OpenAPIとは、RESTful APIを記述するための標準化されたフォーマットのことで、OpenAPI Specification(OAS)やOpenAPI Generatorなど取り巻く技術の総称としても呼ばれています。
もともとswaggerとして有名だったのですが、OASとして標準化されてからはOpenAPIと呼ばれることが多く、swaggerはOpenAPIのツール的な位置付けになっています。
例えば、https://jsonplaceholder.typicode.com/ というRESTfulAPIを公開しているサービスがあるのですが、その中の /posts , /posts/{id} をOpenAPIで定義すると以下のようになります。
openapi: 3.0.0
info:
version: 1.0.0
title: JSON Placeholder API
description: See https://jsonplaceholder.typicode.com/
paths:
/posts:
get:
description: Returns all posts
tags: ["Posts"]
operationId: "getPosts"
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/PostsList"
/posts/{id}:
get:
description: Returns a post by id
tags: ["Posts"]
operationId: "getPost"
parameters:
- name: id
in: path
required: true
description: The user id.
schema:
type: integer
format: int64
responses:
"200":
description: Successful response
content:
"application/json":
schema:
$ref: "#/components/schemas/Post"
"404":
description: Post not found
components:
schemas:
PostsList:
"type": "array"
"items":
$ref: "#/components/schemas/Post"
Post:
"type": "object"
"required":
- "id"
- "userId"
- "title"
- "body"
"properties":
id:
type: "integer"
userId:
type: "integer"
title:
type: "string"
body:
type: "string"
今回はこちらのOpenAPI仕様のyamlファイルを使ってAPIGatewayを作っていきたいと思います。
CDKを使ったAPIGateway+Lambda構成の一般的な作成方法
APIGateway+Lambdaを構成するやり方を色々調べてみると、RestApi を利用するケースがよくみられており、前述のOpenAPI通りにCDKで記述すると以下のようになります。(CDKはTypeScript、v2.38.0です)
// Lambda関数の作成
const fn = new lambda.Function(this, 'handler', {
// Lambda関数の設定
});
// RestApiを使ったAPIGatewayの作成
const apigw = new apigateway.RestApi(this, "RestApi", {
restApiName: "restApi",
});
const integration = new apigateway.LambdaIntegration(fn);
// /posts
const postsResource = apigw.root.addResource("posts");
postsResource.addMethod("GET", integration);
// /posts/{id}
const postIdResource = postsResource.addResource("{id}");
postIdResource.addMethod("GET", integration);
RestApiの良い点としては、リソース毎に定義するため、AuthorizerやRequestValidatorなどの設定を柔軟に定義できることですが、反面、OpenAPI定義ファイルに合わせてCDKのコードを合わせて作成しないといけないため、リソースが増えると記述量が比例して増えるので辛かったり、仕様変更時にOpenAPI定義とCDKの二重メンテが発生してしまうことがあります。
CDKを正として事後でOpenAPI定義ファイルを生成したい場合は、APIGatewayからOpenAPIをエクスポートすることもできるのでそれでもよいと思います。マネジメントコンソールでは以下のメニューからエクスポートできます。
SpecRestApiを使ったAPIGateway+Lambda作成方法
OpenAPIを利用したAPIGateway作成方法として、公式CDKにてSpecRestApiが提供されています。こちらのCDKコード例は以下のようになります。
const apigw = new apigateway.SpecRestApi(this, "RestApi", {
apiDefinition: apigateway.ApiDefinition.fromAsset("./api/docs/openapi.yaml"),
restApiName: "restApi",
})
ただ、このままだとLambdaと連携しないApigatewayができるだけなので、RestApiでいうintegration的な処理が必要になります。色々なやり方があるかもしれませんが、私はOpenAPI拡張の x-amazon-apigateway-integrationLambdaを使って以下のように書きました。権限設定も合わせて必要だったので追加しています。
// SpecRestApiを使ったAPIGatewayの作成
const swaggerYaml = yaml.parse(
fs.readFileSync("./api/docs/openapi.yaml").toString()
);
for (const path in swaggerYaml.paths) {
for (const method in swaggerYaml.paths[path]) {
swaggerYaml.paths[path][method]["x-amazon-apigateway-integration"] = {
uri: `arn:${cdk.Aws.PARTITION}:apigateway:${cdk.Aws.REGION}:lambda:path/2015-03-31/functions/${fn.functionArn}/invocations`,
passthroughBehavior: "when_no_match",
httpMethod: "POST",
type: "aws_proxy",
};
}
}
const apigw = new apigateway.SpecRestApi(this, "RestApi", {
apiDefinition: apigateway.ApiDefinition.fromInline(swaggerYaml),
});
fn.addPermission("LambdaPermisson", {
principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
action: "lambda:InvokeFunction",
sourceArn: apigw.arnForExecuteApi(),
});
この内容で cdk deploy
するとこのようにApiGatewayのリソースが定義されて、統合タイプとしてはLambda関数に設定されます。
まとめ
CDKのSpecRestAPIを使うことでOpenAPI定義とAPIGatewayの乖離を防ぐことができました。定義ファイルを正とできることで、クライアントアプリ側の連携も安定しそうです。また、今回は記載していませんがLambda統合だけでなく、Authorizerなどの設定も追加することが可能です。欠点としては、私の方法ではリソース全体に設定しているため、リソース個別で設定や呼び出すLambdaが異なるような場合には大変になりそうな気がしました。