LoginSignup
9
4

CDKのAPIGateway+Lambda構成をOpenAPIの定義から作成する

Last updated at Posted at 2022-12-20

どんな内容か

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で定義すると以下のようになります。

api/docs/openapi.yaml
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をエクスポートすることもできるのでそれでもよいと思います。マネジメントコンソールでは以下のメニューからエクスポートできます。

cdkapi1.png

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を使って以下のように書きました。権限設定も合わせて必要だったので追加しています。

lib/cdk-openapi-express-stack.ts
// 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関数に設定されます。

cdkapi2.png

まとめ

CDKのSpecRestAPIを使うことでOpenAPI定義とAPIGatewayの乖離を防ぐことができました。定義ファイルを正とできることで、クライアントアプリ側の連携も安定しそうです。また、今回は記載していませんがLambda統合だけでなく、Authorizerなどの設定も追加することが可能です。欠点としては、私の方法ではリソース全体に設定しているため、リソース個別で設定や呼び出すLambdaが異なるような場合には大変になりそうな気がしました。

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4