【AWS】Amplify + AppSync + Lambda + Aurora ServerlessでAPIサーバを作る備忘録

# やりたい事
個人開発で、管理画面的なwebサービスを作りたかった。

- テーブル設計をしたところ、50以上のテーブル構成・・・
- データを更新する上での条件も結構ありそう・・・

と、なかなか複雑な感じになってしまった。

今まで使っていた、firestoreなどのNoSQLだと厳しそうなのと、今後を見越して
いろんな問題に耐えられる構成にしておきたいなというのもあり、いろんなパターンで技術検証というほど細かくやっていないが、
少し作っては動かしてというのをしてみた。
最終的に今回備忘録としてまとめる構成は、採用はしないが、規模がちょっと大きくなってきたりしたら、検討したいと思えるような構成に感じてる。

なので、忘れないように備忘録として自分用にまとめておく。


# 全体構成
考えていた構成はこんな感じ。

- NoSQLは厳しかったので、RDBはマスト条件
- 管理画面からは一発でデータ取れればいいなという事で、GraphQL(AppSyncがそれ)
- コードは出来るだけ書きたくない（時間かけたくない）ので、awsとか既存のサービスを組み合わせる
- etc...

![cloudpipe_archtecture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234760/64a29e09-bcd6-76a1-a839-3c8a1414ec09.jpeg)


# サマリ（良かった事・大変そうな事）
## 良かった事
### サーバサイド側のコードを書くのがリゾルバ部分だけにできそう
立ち上がりを優先したかったので、コードを書かないがベスト。
とはいえ、不整合が起きたり、後々大変な作りはしたくないという思いもあり、
この構成だとリゾルバ部分（Lambda）にビジネスロジックを集約出来そうで、好きな言語で対応できるし、
そこだけコードを書くで良いので、コードの記述量は抑えられそう。

![PolyglotLambda02png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234760/d8f82be5-06c0-d474-2901-9778ec80be90.png)

https://aws.amazon.com/jp/blogs/mobile/appsync-direct-lambda/

### LambdaをAppSyncのデータソースとして指定する事で、VTL言語を使わず、処理をかける
リゾルバの処理は、マッピングテンプレートというところに、VTL言語を使い、処理をかける。
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/resolver-mapping-template-reference-overview.html

こんな感じの、個人的には馴染みのない言語で、簡単な処理はいいが、複雑な処理になるとちょっと楽しくない・・・
それに、あんまり今後も使わなさそうな言語をいちいち覚える時間がもったいない・・・
![_2021-01-19_17.59.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234760/2f49b683-e609-0150-fd40-b18a29ef7a49.png)

で、AppSyncのリゾルバに、Lambdaを使い、VTL言語なしで、作れちゃうよ！というのが、2020年8月？に可能になったことを知り、VTLを使わず、Lambdaでリゾルバの処理はかける！
https://aws.amazon.com/jp/blogs/mobile/appsync-direct-lambda/


## 大変そうな事
### 全てのサービスにあまり知見がなく、都度都度ハマりそう（これは個人的に）
単純に、なんかあった時、毎回0から調べてって感じで大変そう・・・
ただ、それとは反対に、ここら辺の知識増えて良さそう・・・
のジレンマだった

### AppSyncとAurora Serverlessはまだ早いのかな・・・
AppSyncとDynamoDBだったら、スキーマを定義したら、自動で、よしなに色々してくれたりする。
でも、Auroraだと自分でしないといけないことが多々あった。

ここら辺にそれが詳しく書いてある
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/provision-from-schema.html

# GraphQLのリゾルバにLambdaが良さそう
ここが一番メインで、少し細かく残しておかないと・・・と思っている箇所。

まず、わざわざLambdaをかましている理由は、↑でも書いたが、
VTL言語で、リゾルバの処理を書くのではなく、自分の慣れた言語で処理を書きたいというのが理由。

こうすることで、Lambdaが提供する言語ならなんでも使えるし、関数ごとに言語を変えるということも可能になる。

また、Lambdaから直接Auroraではなく、AppSyncとしているのは、Subscriberを使いたい時に、
直接ではなくAppSyncを噛ませておくことで、使いやすくなるという理由。実際に検証の時動作を試してはないが、
他の人の例でも同じような理由で、こうしているっぽい。
https://fixel.co.jp/blog/amplify-appsync-auroraserverless

![cloudpipe_archtecture-Page-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234760/a84a7537-4619-eb6b-606f-2bb9951c75f5.jpeg)


仮に、Golangで、↑にあるようなLambdaのリゾルバを書くとしたらこんな感じになった。

```main.go 
package main

import (
	"flag"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/sony/appsync-client-go"
	"github.com/sony/appsync-client-go/graphql"
	"log"
)

type Response struct {
	Email  string `json:"email"`
	Id     int64  `json:"id"`
	Name   string `json:"name"`
	Phone  string `json:"phone"`
	Answer string `json:"answer"`
}

var (
	region = flag.String("region", "us-xxx-1", "AppSync API region")
	url    = flag.String("url", "https://xxx.appsync-api.us-xxx-1.amazonaws.com/graphql", "AppSync API URL")
	accessKeyID = "xxx"
	secretAccessKey = "xxx"

)

type Request struct {
	Id      int    `json:"id"`
	Message string `json:"message"`
}

func hello(arg Request) (Response, error) {
	query := `
		query MyQuery {
  			getCustomers(id: 2) {
    			email
    			id
    			name
    			phone
  			}
		}`

	credential := credentials.NewStaticCredentials(accessKeyID, secretAccessKey, "")
	conf := aws.NewConfig().WithRegion("us-east-1").WithCredentials(credential)

	sess := session.Must(session.NewSessionWithOptions(session.Options{Config: *conf}))
	signer := v4.NewSigner(sess.Config.Credentials)
	client := appsync.NewClient(appsync.NewGraphQLClient(graphql.NewClient(*url)),
		appsync.WithIAMAuthorization(*signer, *region, *url))
	response, err := client.Post(graphql.PostRequest{
		Query: query,
	})
	if err != nil {
		log.Fatal("error:", err)
		return Response{}, err
	}

	data := new(Response)
	if err := response.DataAs(data); err != nil {
		log.Fatalln("error:", err, response)
		return Response{}, err
	}
	log.Println(*data)
	return *data, nil

}

func main() {
	lambda.Start(hello)
	//hello()
}
```

引数を使いたい時は、オプションでVTLが使えるので、引数部分だけ受け取るのを書いてあげれば、Lambda内で引数を扱うことができる。


# 最後に
もっと良い方法を思いついたので、今回この構成は使わないが、
会社とかある程度の規模のサービスで、管理画面の構成を考えるなら結構良いのではないかなと思っている。

Direct Lambda Resolversが最近対応したのは、本当にでかい気がする。