less is more

心のフルスタックエンジニア👨‍💻バイブスでコードを書いています🤘

aws-sdk-goを触ってみた所感

f:id:bluepixel:20200506182434p:plain

普段はcli, Ruby, Node.jsなどでAWSと戯れていますが、今回初めてaws-sdk-goを使ってみたので所感をしたためておきます。

▼題材はこれです。

bluepixel.hatenablog.com

Rubyで書いた40行ほどのスクリプトをGo実装で焼き直してみました。

rezept/main.go at master · Blue-Pix/rezept · GitHub

ちなみにGoは本当にちょっとしか書いたことがないです。
文法は一通りさらってありますが、細かい作法はよく知りません。
なのでaws-sdk-goではなくGoそのものに対する所感を含まれているかもしれません。

パッケージ

AWSまわりはこのへん

import(
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/awserr"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/service/cloudformation"
)

awsはポインタを返すaws.String()や、認証のconfigとかで使う。
レスポンスもaws.StringValue()とかで扱ってますが、このヘルパーって自分でポインタにすれば別に使わなくてもいいんだよね...?

awserrはエラーコードの判定。詳細知らなくてもよければ特に必要ない。

sessionは認証で、service/cloudformationは各サービスのAPI実装。

認証

環境変数のアクセスキーやプロファイルを順に読み込んでいくのは他のSDKと変わらず。

特に何も指定しない場合はこう。

sess := session.Must(session.NewSession())

session.Must()を使えば以下のようなハンドリングを書かずに済む。

sess, err := session.NewSession()
if err != nil {
  panic(err)
}

プロファイルを使う場合はこういう感じ。

sess := session.Must(
  session.NewSessionWithOptions(
    session.Options{
      Profile: "myProfile",
    },
  ),
)

API

サービスごとにクライアントの作成

service := cloudformation.New(sess)

パラメータは、各APIの名前にInputというサフィックスをつけた構造体で渡す。ListStacksAPIであれば、ListStacksInputになる。

params := &cloudformation.ListStacksInput{
  NextToken: token,
}

実行。

resp, err := service.ListStacks(params)
if err != nil {
  panic(err)
}

レスポンスはポインタになるのでaws.StringValueとかを使って変換する。

for _, stack := range resp.StackSummaries {
  result[aws.StringValue(stack.StackId)] = aws.StringValue(stack.StackName)
}

ページングも同じで次ページのトークンを含める方式。

if resp.NextToken != nil {
  listStacks(sess, resp.NextToken, result)
}

サービス名とAPI名を一定のルールに沿って変換するだけなので特に難しくない。他の言語でAWS実装に親しんでいればなんとなくで予想して書ける。

使えないオプションがある

例えばaws cloudformation list-stacksにはmax-itemsというオプションがあるが、aws-sdk-goの方では使用できない。

list-stacks — AWS CLI 1.18.53 Command Reference

エラーハンドリング

詳細にエラーの種類を把握したい時にはawserr.Errorに変換してごにょごにょやる必要がある。

Handling Errors in the AWS SDK for Go - AWS SDK for Go

このあたりちょっと記述が冗長になってつらい。

resp, err := service.ListImports(params)
if err != nil {
  if aerr, ok := err.(awserr.Error); ok {
    if aerr.Code() == "ValidationError" && strings.Contains(aerr.Message(), "is not imported by any stack") {
      return
    }
  }
  panic(err)
}

参考資料

docs.aws.amazon.com

docs.aws.amazon.com

www.techscore.com

dev.classmethod.jp

その他Goに対する所感

  • いまはGo modulesが主流なんですか?
  • スライスはポインタであってポインタではない...?
  • デフォルト引数サクッと使いたい。Functional Option Pattern...?