複数のバックエンドのリソースをAmazon API Gatewayで統合する

# 前提
システム開発をしていると「A機能はLambdaで作るべきで、B機能はコンテナで作るべきで、でもそれぞれ実はURIは同じにしておく方がREST的にキレイになるんだよね」なことがある。…いや、あまりないか？ともあれ、そんな時にはnginxとかでプロキシを作って振り分けても良いのだけど、せっかくパブリッククラウドを使っているのだからマネージドサービス使ってみたいよね、と思ってやってみた。

Spring BootなWebアプリの動いているコンテナと、Lambda関数がそれぞれあるような構成。

# Lambda関数
この記事のために作り直すのも面倒なので、以下の記事で作ったものを転用する。

[Amazon API Gateway/ALBのバックエンドで動くLambda関数をJava(Eclipse＋maven)で実装する](https://qiita.com/neruneruo/items/8673af2a3ae0f8076627)

# Spring BootなWebアプリの動いているコンテナ
あまりこだわりもないので、適当なパラメータを受けてJSONを返すコンテナを作ってみる。
リソースは```/person```。
パラメータは、クエリかリクエストボディ(JSON)で```id```を読み込んで、それに対応した名前と年齢をJSONで返すような仕様で。

```JAVA:WebController.java
package com.example;
 
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
 
@RestController
public class WebController {

	@RequestMapping("/person")
	@ResponseBody
	public Person response(
			@RequestBody(required = false) Person person,
			@RequestParam(name="id", required = false) String reqParamId) {
		Person response = null;
		String id = null;
		
		if( reqParamId != null ){
			id = reqParamId;
		}else{
			id = Integer.toString(person.getId());
		}
		
		if(id.equals("11111")) {
			response = new Person(Integer.parseInt(id), "Taro", 35);
		}else if(id.equals("22222")) {
			response = new Person(Integer.parseInt(id), "Hanako", 32);
		}
		
		return response;
	}
}
```

```Person.java
package com.example;

public class Person {
	private int Id;
	private String Name;
	private int Age;

	// 適当な getter, setter
	// 中略

	public Person(int id, String name, int age) {
		Id = id;
		Name = name;
		Age = age;
	}
}
```

pom.xml は spring-boot-starter, spring-boot-starter-web があればOK。
ビルドは適当にmvn packageしてから適当なDockerfileでdocker buildして、ECRにpushする。
この辺も以下の過去記事を参考に。

[AWSでSpringBootベースのWebアプリを起動してみる(Docker on EC2編/ECS＋Fargate編)](https://qiita.com/neruneruo/items/ff6747a4129a5b52e6c6)

# API Gatewayの設定
↓こんな感じで定義する。
![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/32f49322-f690-20d9-1f31-d55f98ae57e8.png)

|リソース|メソッド|転送先|
|:--|:--|:--|
| LambdaTest | ANY | Lambda関数 |
| person | GET | Spring BootなWebアプリ |
| person | POST | Lambda関数 |
| person | PUT | Spring BootなWebアプリ |

手でポチポチ設定するのも面倒なので、以下のSwagger+API Gateway拡張をインポートする。
分かりにくいかもしれないけど、インポートはここから可能。
![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1efd4509-3786-e05f-b6ef-c0ee59d83b1e.png)

```YAML
---
swagger: "2.0"
info:
  description: "Created by AWS Lambda"
  version: "2020-04-25T10:56:39Z"
  title: "API GatewayのAPI名"
host: "[API Gatewayのドメイン]"
basePath: "/default"
schemes:
- "https"
paths:
  /LambdaTest:
    x-amazon-apigateway-any-method:
      responses:
        200:
          description: "200 response"
      x-amazon-apigateway-integration:
        uri: "[Lambda関数のARN]"
        responses:
          .*:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"
  /person:
    get:
      produces:
      - "application/json"
      responses:
        200:
          description: "200 response"
          schema:
            $ref: "#/definitions/Empty"
      x-amazon-apigateway-integration:
        uri: "http://[NLBのドメイン]/person"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        connectionType: "VPC_LINK"
        connectionId: "[VPCLinkのID]"
        httpMethod: "GET"
        type: "http_proxy"
    post:
      produces:
      - "application/json"
      responses:
        200:
          description: "200 response"
          schema:
            $ref: "#/definitions/Empty"
      x-amazon-apigateway-integration:
        uri: "[Lambda関数のARN]"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    put:
      produces:
      - "application/json"
      responses:
        200:
          description: "200 response"
          schema:
            $ref: "#/definitions/Empty"
      x-amazon-apigateway-integration:
        uri: "http://[NLBのドメイン]/person"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        connectionType: "VPC_LINK"
        connectionId: "[VPCLinkのID]"
        httpMethod: "GET"
        type: "http_proxy"
definitions:
  Empty:
    type: "object"
    title: "Empty Schema"
```

コンテナについては、内部向けのHTTPサーバを統合する場合は、VPCLinkで繋いだNLBにぶら下げる必要がある。[この辺](https://qiita.com/ohtsuka1317/items/d63fe2a554c468570c64)の記事を参考にしよう。ほぼこの記事とやってることは同じだけど気にしない………。

さて、ここまでやったら、curlで動作確認をしてみると、期待通りの動作になっているのが分かる。

```:Lambda向けのリクエスト①
$ curl --include -X GET https://[API Gatewayのドメイン]/default/LambdaTest
HTTP/2 200
date: Sat, 25 Apr 2020 12:52:11 GMT
content-type: application/json
content-length: 42
x-amzn-requestid: ********
x-amz-apigw-id: ********
x-amzn-trace-id: ********

{"greetings":"Hello from Lambda Ver 1.11"}
```

```:Lambda向けのリクエスト②
$ curl --include -X POST https://[API Gatewayのドメイン]/default/person
HTTP/2 200
date: Sat, 25 Apr 2020 12:54:16 GMT
content-type: application/json
content-length: 42
x-amzn-requestid: ********
x-amz-apigw-id: ********
x-amzn-trace-id: ********

{"greetings":"Hello from Lambda Ver 1.11"}
```

```:Spring&nbsp;BootなWebアプリ向けのリクエスト①
$ curl --include -X GET https://[API Gatewayのドメイン]/default/person?id=11111
HTTP/2 200
date: Sat, 25 Apr 2020 12:55:24 GMT
content-type: application/json
content-length: 35
x-amzn-requestid: ********
x-amzn-remapped-connection: keep-alive
x-amz-apigw-id: ********
x-amzn-remapped-date: Sat, 25 Apr 2020 12:55:24 GMT

{"name":"Taro","id":11111,"age":35}[
```

```:Spring&nbsp;BootなWebアプリ向けのリクエスト②
$ curl --include -X PUT -H 'Content-Type:application/json' -d '{"id":"11111"}' https://[API Gatewayのドメイン]/default/person?id=11111
HTTP/2 200
date: Sat, 25 Apr 2020 12:56:33 GMT
content-type: application/json
content-length: 35
x-amzn-requestid: ********
x-amzn-remapped-connection: keep-alive
x-amz-apigw-id: ********
x-amzn-remapped-date: Sat, 25 Apr 2020 12:56:32 GMT

{"name":"Taro","id":11111,"age":35}
```