AWS AmplifyでReact(TypeScript)、GraphQL、Lambda(Go)なアプリを作ってみた

退職に伴い、ばばさん通信の管理機能を作り直しました。

もともとは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.vtlamplify/backend/api/{{apiName}}/build/resolvers/*.res.vtlamplify/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/backendamplify/#current-backendamplify/.temp/#current-backendと動かす。venvは元のpythonバイナリからの相対位置(階層)が変わるとシンボリックリンクが切れて動かなくなるので、結果として動かなくなる
    • 回避するためにGoで書き直した
    • いま考えるとPIPENV_VENV_IN_PROJECT=falseでよかったかもしれない
      • Pipenvを設定したのがかなり昔で、この設定の存在を忘れていた
  • Amplify ConsoleのCIでpush失敗1
    • Google認証する場合、Build時の環境変数としてAMPLIFY_GOOGLE_CLIENT_IDAMPLIFY_GOOGLE_CLIENT_SECRETを渡す必要がある
  • Amplify ConsoleのCIでpush失敗2
  • Amplify pushに時間がかかる
    • CloudFormationを経由してるので仕方ないのかもだけど、分単位で時間がかかる
    • 環境起因の不具合に対処してるときはテンポが乱れてつらい
    • 我慢

まとめ

  • Amplify、まだまだバギーな感じ
  • 楽しいか楽しくないかで言うと楽しい
  • 結局は、アプリのUX設計と、DynamoDBのスキーマ設計がいちばん大変かもしれない
Cloud  AWS  Amplify  Go  GraphQL 

See also