退職に伴い、ばばさん通信の管理機能を作り直しました。
もともとはDjango+MySQLをインスタンス上で動かすシンプルなものでしたが、この機会にServerlessしてみました。
what | how |
---|---|
Hosting | Amplify |
Auth | Cognito(Google federation) |
Datastore | DynamoDB |
Secret | SecretsManager |
Frontend | React(TypeScript)、material-ui |
API | AppSync |
Function | Lambda(Go) |
- DynamoDBはAppSync経由で操作
- Functionは定期実行処理用
- Pocketからデータを回収するクローラの定期実行ジョブ
- Twitterに発信する定期実行ジョブ
- GoではGraphQLライブラリは使わず
- なお開発環境はWSL2 on Windows10
- ReactはVisual Studio Code
- Goは慣れたvimとvim-lsp
結論から言うと、とっても苦労した。
Amplifyが初だというのもありましたが、まだ未成熟な感じの謎エラーがちょくちょくおきていろいろ回避しました。 動くところまでいったけど、理解したかと言われると謎。
同じようなものを作るのであれば、3回目くらいからはさくさくできるかもしれない。
よかったところ
- Amplify側の想定通りに使えば一式揃えるのは楽
- Hosting、Cognito、AppSync、Function一揃いを作るのは楽
- AppSyncのCustom Resolverなど、一部はCloudFormationのJSONを書かねば、っぽくて急に生々しい
- (当時)よくわからなかったので、禁断の直接編集してしまった...
amplify/backend/api/{{apiName}}/build/resolvers/*.req.vtl
とamplify/backend/api/{{apiName}}/build/resolvers/*.res.vtl
をamplify/backend/api/{{apiName}}/resolvers/
にコピーして中身を書き換えて、あとはamplify/backend/api/{{apiName}}/build/stacks/XXXX.json
を参考にamplify/backend/api/{{apiName}}/stacks/CustomResources.json
を書けばいいっぽい
- AWSのサービス間連携の設定はすごく楽
- あっちのIAMとこっちのIAMを紐付けないとならないけど忘れてた、、、みたいなのはない
- ただしKMSとSecretsManagerは例外
amplify
(CLI)のおかげで、Functionのデバッグは楽- ReactからAppSync(GraphQL)を呼び出すのがすごく楽
CloudFormationはじめAWSがある程度わかってないとしんどいと思うけど、わかった上であればうまく使えそう。 いろいろ楽できるのは素晴らしい。
GoでAppSyncのAPIアクセスしGraphQLで操作する方法
APIの認証を @aws_iam
@aws_cognito_user_pools
にした関係もあり、いい感じのライブラリを見つけられなかった。
こんな感じでサードパーティライブラリは使わずにリクエストを署名して操作してます。
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
// ...
)
// ...
query := `
query MyQuery {
filterEntrys(myStatus: "1;1;", limit: 3) {
nextToken
items {
title
url
}
}
}
`
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 10 * time.Second,
DisableCompression: false,
}
client := &http.Client{
Transport: tr,
}
// from graphql/graphql.go
var requestBody bytes.Buffer
requestBodyObj := struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
}{
Query: query,
}
if err := json.NewEncoder(&requestBody).Encode(requestBodyObj); err != nil {
log.Printf("%+v:%v\n", requestBodyObj, err)
return entries, err
}
req, err := http.NewRequest("POST", API_ENDPOINT, bytes.NewReader(requestBody.Bytes()))
config := aws.Config{Region: aws.String("ap-northeast-1")}
sess := session.Must(session.NewSession(&config))
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Content-Type", "application/json; charset=utf-8")
signer := v4.NewSigner(sess.Config.Credentials)
signer.Sign(req, bytes.NewReader(requestBody.Bytes()), "appsync", "ap-northeast-1", time.Now())
resp, err := client.Do(req)
if err != nil {
log.Printf("req: %+v, resp: %+v, err: %+v\n", req, resp, err)
}
if resp.StatusCode >= 400 {
log.Printf("req: %+v, resp: %+v, err: %+v\n", req, resp, err)
}
var appsyncListEntryResponse struct {
Data struct {
ListEntrys struct {
Items []Entry `json:"items"`
} `json:"filterEntrys"`
} `json:"data"`
}
responsebody, _ := ioutil.ReadAll(resp.Body)
if err := json.Unmarshal(responsebody, &appsyncListEntryResponse); err != nil {
log.Printf("%v\n", err)
}
苦労話
- FunctionにPythonを選ぶと
amplify
(CLI)で操作できなくなる- Pythonの場合はPipenvで名前解決するようになってるけど、Amplifyがpushやpullの過程でfunctionのディレクトリまるごとを
amplify/backend
→amplify/#current-backend
→amplify/.temp/#current-backend
と動かす。venvは元のpythonバイナリからの相対位置(階層)が変わるとシンボリックリンクが切れて動かなくなるので、結果として動かなくなる - 回避するためにGoで書き直した
- いま考えると
PIPENV_VENV_IN_PROJECT=false
でよかったかもしれない- Pipenvを設定したのがかなり昔で、この設定の存在を忘れていた
- Pythonの場合はPipenvで名前解決するようになってるけど、Amplifyがpushやpullの過程でfunctionのディレクトリまるごとを
- Amplify ConsoleのCIでpush失敗1
- Google認証する場合、Build時の環境変数として
AMPLIFY_GOOGLE_CLIENT_ID
とAMPLIFY_GOOGLE_CLIENT_SECRET
を渡す必要がある
- Google認証する場合、Build時の環境変数として
- Amplify ConsoleのCIでpush失敗2
- amplifyPushの内部で
amplify init
するときにamplify-nodejs-function-runtime-providerがTypeError: Cannot read property 'join' of null
になる - nodejsなfunctionは使っていないのだけれども...
- 試行錯誤の末になぞのworkaroundを発見したので回避成功
- amplifyPushの内部で
- Amplify pushに時間がかかる
- CloudFormationを経由してるので仕方ないのかもだけど、分単位で時間がかかる
- 環境起因の不具合に対処してるときはテンポが乱れてつらい
- 我慢
まとめ
- Amplify、まだまだバギーな感じ
- 楽しいか楽しくないかで言うと楽しい
- 結局は、アプリのUX設計と、DynamoDBのスキーマ設計がいちばん大変かもしれない