はじめに
こんにちは、会津大学学部二年のHAMADAです。今年も始まってもう少しで1ヶ月ですね。 最強寒波も来るらしいですが、体調にきをつけて今年も頑張りたいと思います。 さて今回は AWS CDK をGoで書いてみた話です!!
目的と動機
最近は、Alche株式会社でインターンをさせていただいています!その中でAWSを学んだり、使ったりする機会が多いのでそのアウトプットとして記事を書きます。GraphQL自体は興味はあったものの使ったことがなかったので、ある程度慣れてきたAWSを使って勉強できる!という気持ちもあり、掘りさげて取り組もうと思いました。
使用技術
Go
ここのところよく使う言語。最近関数型に興味がありますが、Goに甘えてしまう。
-
Amazon Web Service 本当に色々できる(知らないこといっぱい)今回使ったのは以下
やったこと
- とりあえず、CDKプロジェクトを構築する
- スタックを作成
- DynamoDBを作成
- AppSyncを作成し、クエリを投げられるようにする。
CDKプロジェクトを構築
CDKのinstall
AWS CDKのコマンドラインツールに cdk
というものがあります。これ自体はnpmで提供されています。以下のコマンドを実行します
$ npm install -g aws-cdk ... $cdk --version 2.60.0 (build 2d40d77)
プロジェクトの作成
以下のコマンドを実行すると、GoのCDKプロジェクトが作成できます。
$ mkdir aws-cdk-go $ cd aws-cdk-go $ cdk init --language=go
プロジェクト内には、次のファイルが作成されます。
- README.md - .gitignore - aws-cdk-go.go - aws-cdk-go_test.go - cdk.json - go.mod
不要な依存関係を削除するためにgo mod tidy
を実行しておきます。
スタックを作成
aws-cdk-exp.go
には自動でコードが生成されますが、一度消して簡単なコードを書き直します。
package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" "github.com/aws/jsii-runtime-go" ) func main(){ app := awscdk.NewApp(nil) //stackの作成 //stack := としているがこれは後で使うからで、このままではerrorを吐く stack := awscdk.NewStack(app, jsii.String("AwsCdkGo"), &awscdk.StackProps{}) app.Synth(nil) }
空のスタックをawscdk.NewStack
で作成しています。
ここで一度デプロイしてみます。
go mod tidy
が必要であれば実行しておきます。
以下のようにコマンドを実行します。
$ cdk bootstrap //最初のデプロイの前だけでOK $ cdk deploy
bootstrapはAWS特定の環境 (アカウントとリージョン)AWS CloudFormation にテンプレートをデプロイすることです。これをしないとdeployで必要だという旨のエラーが出る。
こんな感じでWebコンソール上に作成することができました。
DynamoDBを作成
DynamoDBのテーブルを作成します。基本的には、先ほどのスタックのコードに追加していきます。
package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" "github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb" "github.com/aws/aws-cdk-go/awscdk/v2/awsiam" "github.com/aws/jsii-runtime-go" ) func main(){ app := awscdk.NewApp(nil) //stackの作成コード //DynamoDBのテーブル作成 //table := としているが後で使うからで、このままではerrorを吐く table := awsdynamodb.NewTable(stack, jsii.String("demo-table"), &awsdynamodb.TableProps{ TableName: jsii.String("booktable"), PartitionKey: &awsdynamodb.Attribute{ Name: jsii.String("id"), Type: awsdynamodb.AttributeType_STRING, }, BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST, Stream: awsdynamodb.StreamViewType_NEW_IMAGE, }) //テーブルの権限を作成 tablerole := awsiam.NewRole(stack, jsii.String("dynamodb-role"), &awsiam.RoleProps{ AssumedBy: awsiam.NewServicePrincipal(jsii.String("appsync.amazonaws.com"), &awsiam.ServicePrincipalOpts{}), }) tablerole.AddManagedPolicy(awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonDynamoDBFullAccess"))) app.Synth(nil) }
awsdynamodb.NewTable
で新しくテーブルを作成できます。
引数は順にstack、作成するリソース名、プロパティとなっています。
TableNameは、作成するテーブル名。
PartitionKeyは、データがどのパーティションに保存されるか決めるもの。
BillingModeは、DynamoDBの課金方式を決めています。
Streamは、DBに変更があったときに変更情報を保存しておくもの。
ここで一度デプロイすると、
きちんとtableが作成されています。
AppSyncを作成しクエリを投げる
この手順通りにmain関数にコードを追記していきます。
"github.com/aws/aws-cdk-go/awscdk/v2/awsappsync"
をimportします。
まず、appsyncを作成します。
//mainの中に追記していく //api := としているが後で使うからで、このままではerrorを吐く api := awsappsync.NewCfnGraphQLApi(stack, jsii.String("booksApi"), &awsappsync.CfnGraphQLApiProps{ Name: jsii.String("books-api"), AuthenticationType: jsii.String("API_KEY"), }) awsappsync.NewCfnApiKey(stack, jsii.String("BooksApiKey"), &awsappsync.CfnApiKeyProps{ ApiId: api.AttrApiId(), })
awsappsync.NewCfnGraphQLApi
で新しくGraphQLApiを作成します。
引数はDynamoDBの時と同様です。
Nameは、api名。
AuthenticationTypeは、権限の認証方法。
awsappsync.NewCfnApiKey
で API KEYを作成します
引数は他と同様です。
ApiIdは、APIに割り当てられた識別子。
次に、作成したDynamoDBをAppsyncのデータソースに割り当てます。
//mainの中に追記していく //Ds := としているが後で使うからで、このままではerrorを吐く Ds := awsappsync.NewCfnDataSource(stack, jsii.String("DataStore"), &awsappsync.CfnDataSourceProps{ ApiId: api.AttrApiId(), Name: jsii.String("BookDataSource"), Type: jsii.String("AMAZON_DYNAMODB"), DynamoDbConfig: awsappsync.CfnDataSource_DynamoDBConfigProperty{ TableName: table.TableName(), AwsRegion: stack.Region(), }, ServiceRoleArn: tablerole.RoleArn(), })
awsappsync.NewCfnDataSource
で新しくデータソースを作成します。
引数は他と同様です。
Typeは、データソースが何かを指定できます。今回はDynamoDBですが、Lambda等でもOK。
DynamoDbConfigは、DynamoDBへの接続を定義しています。
ServiceRoleArnは、Roleを参照しています。
その次は、スキーマとマッピングテンプレートを別ディレクトリで定義し、それをとってきます。
//mainの中に追記していく //schemaを、ファイルからとってくる //def := としているが後で使うからで、このままではerrorを吐く def, err := os.ReadFile(filepath.Join(".", "resource", "schema.graphql")) if err != nil { fmt.Println("failed to load graphql definition " + err.Error()) } //schemaを定義 //schema := としているが後で使うからで、このままではerrorを吐く schema := awsappsync.NewCfnGraphQLSchema(stack, jsii.String("GraphSchema"), &awsappsync.CfnGraphQLSchemaProps{ ApiId: api.AttrApiId(), Definition: jsii.String(string(def)), }) getitem, err := os.ReadFile(filepath.Join(".", "resource", "getitem.vtl")) if err != nil { fmt.Println("failed to load getitem.vtl " + err.Error()) } putitem, err := os.ReadFile(filepath.Join(".", "resource", "putitem.vtl")) if err != nil { fmt.Println("failed to load putitem.vtl " + err.Error()) }
./resouce/schema.graphql
に定義しておいた、スキーマをとってきてawsappsyncに渡す。
awsappsync.NewCfnGraphQLSchema
で新しくスキーマを作成します。
引数は他と同様です。
Definitionは、スキーマの中身を定義。
schema.graphqlの中身
schema { query: Query mutation: Mutation } type Query { getPost(id: ID): Post } type Mutation { addPost( id: ID! author: String! title: String! content: String! url: String! ): Post! } type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! }
getitem.vtlの中身
{ "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) } }
putitem.vtlの中身
{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id) }, "attributeValues" : { "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author), "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title), "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content), "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url), "ups" : { "N" : 1 }, "downs" : { "N" : 0 }, "version" : { "N" : 1 } } }
このチュートリアルのスキーマ、マッピングテンプレートを流用しました。
最後に、QueryとMutationを投げるリゾルバーを作成。
//mainのなかに追記 //Queryを投げられるようにする awsappsync.NewCfnResolver(stack, jsii.String("GetResolver"), &awsappsync.CfnResolverProps{ ApiId: api.AttrApiId(), TypeName: jsii.String("Query"), FieldName: jsii.String("getPost"), DataSourceName: Ds.Name(), RequestMappingTemplate: jsii.String(string(getitem)), ResponseMappingTemplate: jsii.String(`$util.toJson($ctx.result)`), }).AddDependency(schema) //Mutationを投げられるようにする awsappsync.NewCfnResolver(stack, jsii.String("AddResolver"), &awsappsync.CfnResolverProps{ ApiId: api.AttrApiId(), TypeName: jsii.String("Mutation"), FieldName: jsii.String("addPost"), DataSourceName: Ds.Name(), RequestMappingTemplate: jsii.String(string(putitem)), ResponseMappingTemplate: jsii.String(`$util.toJson($ctx.result)`), }).AddDependency(schema)
awsappsync.NewCfnResolver
は新しくリゾルバーを作成します。
TypeNameは、Query,Mutationなどを指定できます。
FieldNameは、スキーマに含まれているフィールドを指定します。
DataSourceNameは、今回は先ほど定義したデータソースの名前をとってきます。
RequestMappingTemplateは、putiem、getitemから指定しています。
ResponseMappingTemplateは、context.resultをJson形式にして返しています。
Webコンソールで実行できるようになりました。
まず、mutationで情報を追加。
その後、queryで情報を取得。
無事にDynamoDBとAppSyncを接続し実行するためのCDKを作成できました!!!
感想と展望
AWS CDK V2をGoで書く場合に参考になる公式ドキュメントが少なく、割と苦労しながらの実装になりました。vscodeでエスパーしながら進めていたので、vscodeの補完能力には感謝しかありません。GraphQLに関しては、まだまだ知識が足りていないのでGraphQL単体でも学びたいと思っています。他の人のコードを読んだり、探したりすることが今までよりも多く学びが深まったことは大きな収穫だと思います。何かの参考になれば幸いです。ここまでお付き合い頂きありがとうございました!
所属サークル
私のTwitter
私のgithub
Alche株式会社