Open11

AWS SDK for Goが絶妙に使いづらい理由はなんだろうか

さき(H.Saki)さき(H.Saki)

背景

AWSに載せてなんやかんややるというコードは、「使用例が豊富」という理由でboto3で実装となることが多かったのです。
その度にGopherの私は「動的型付けのPython使いづらいよう(注: 個人の意見です)」「try-catchじゃなくてerrをreturnするのが一番わかりやすいよなぁ?(注: 個人の意見です)」「使い慣れた言語で実装したら絶対楽だよなあ」と内心思っておりました。

そしてこの度ついにGoで実装していいよという機会をいただいたので、これ幸いにAWS SDK for Goを使い始めたのですが、その感想が「なんか……思ったほど楽になってない……」でした。

  • なんでこういう感想になるのかなあというのを明らかにしたい
  • 好きな言語で快適にAWS SDKを触るノウハウや思考メソッドを見出したい

という目的のもと「AWS SDK for Go」の使い心地を深く掘り下げたいと思います。

さき(H.Saki)さき(H.Saki)

Goの言語仕様がうまく生きていると思った場所

型名が明示的に記述されていれば、エディタのポップアップでフィールドがすぐ確認できる

例えばこのようなコードがあったとして、「requestに何が入っているんだっけ?」となったときに、requestにカーソルを合わせるだけですぐに構造体フィールドが確認できるのは便利でした。

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error)


こういうの

boto3使っていて一番嫌だったのは「変数に含まれているフィールドを調べるために、エディタと公式Docの行ったり来たりになる」スイッチの多さだったので、ここは個人的にはgoodポイントだったのです。
静的型付け言語の強みが生きている箇所だと思います。

さき(H.Saki)さき(H.Saki)

Goの言語仕様がうまくハマっていないと思った場所

レスポンスのフィールドが意外とわかりにくい

初めて覚えた言語が静的型付けだったのもあり、私は「この変数の型が何なのか?」というのが気になる性格です。
そのため、boto3で以下のようなコードを書いていたときに、「この後resに何が含まれているのか、わざわざ公式Docに調べにいかなくても、型がきちんと決まっているGoならエディタの中だけで完結するんだろうなー」と思ってました。

res = client.GetSomething()

しかし、現実には以下のGoのコードがあったとして、resにカーソルを合わせてわかるのは「変数resGetSomethingOutput型だ」というところまでで、「GetSomethingOutput型に何がフィールドとして含まれているか」までは出してくれませんでした。

res, err := client.GetSomething()

AWS SDK for Go全体として、APIのinput/outputは基本的にXxxInput型・XxxOutput型と抽象的にまとめられてしまっているため[1]、型名が分かったところで大した情報にならなかったです。
本来の関心事である「構造体フィールド一覧」や「フィールドの中身」まで調べようとすると結局公式ドキュメントのXxxOutput型のページに飛ぶ羽目になるので、あまりboto3のときと労力が変わらない……と思ってしまいました。

脚注
  1. pkg.go.devのOverviewの箇所を見ればこの傾向は一目瞭然。(例)cloudwatchのSDK ↩︎

さき(H.Saki)さき(H.Saki)

XxxInput型の全容把握が大変

ページ遷移数の観点での比較

例えば、PutLogEventsのAPIを叩くときに、GoのSDKを使っていると以下の3ステップが必要です。

  1. PutLogEventsメソッドのシグネチャから、引数がPutLogEventsInput型であることを把握する
func (c *CloudWatchLogs) PutLogEvents(input *PutLogEventsInput) (*PutLogEventsOutput, error)

出典: pkg.go.dev - cloudwatchlogs#CloudWatchLogs.PutLogEvents

  1. PutLogEventsInput型のフィールドを確認する
type PutLogEventsInput struct {
	LogEvents []*InputLogEvent `locationName:"logEvents" min:"1" type:"list" required:"true"`
	LogGroupName *string `locationName:"logGroupName" min:"1" type:"string" required:"true"`
	LogStreamName *string `locationName:"logStreamName" min:"1" type:"string" required:"true"`
	SequenceToken *string `locationName:"sequenceToken" min:"1" type:"string"`
}

出典: pkg.go.dev - cloudwatchlogs#PutLogEventsInput

  1. PutLogEventsInput型のLogEventsフィールドに使われていたInputLogEvent型のフィールドを確認する
type InputLogEvent struct {
	Message *string `locationName:"message" min:"1" type:"string" required:"true"`
	Timestamp *int64 `locationName:"timestamp" type:"long" required:"true"`
}

出典: pkg.go.dev - cloudwatchlogs#InputLogEvent

このように、cloudwatchlogsパッケージにて独自で定義された型が出てくるたびに、いちいち公式Docのページを辿りながら詳細を把握していく必要があります。
今回使われていた独自型はPutLogEventsInput型とInputLogEvent型の2つだったので、この2つの型のDoc + 元々のPutLogEvents APIのページという3つのページだけで詳細を把握することができました。
これがもっと複雑になるとそれだけステップ数が増えます。

一方boto3の場合には、put_log_eventsメソッドのページだけで入力となる引数の全容を把握することができます。

response = client.put_log_events(
    logGroupName='string',
    logStreamName='string',
    logEvents=[
        {
            'timestamp': 123,
            'message': 'string'
        },
    ],
    sequenceToken='string'
)

出典: boto3公式Doc

フィールド条件での比較

Goではその代わり「このフィールドが必須フィールドなのか」「このフィールドは〇〇の条件下ならいらない」といった要件をフィールドタグや近くのコメントに書いてくれています。

type InputLogEvent struct {
	Timestamp *int64 `locationName:"timestamp" type:"long" required:"true"` // 必須フィールド
}

boto3の場合でその情報を知るにはページスクロールが必要です。
ただ、前述の「何度も行われるページ遷移」に比べると……正直ページスクロールの方がマシでした。

response = client.put_log_events(
    logEvents=[
        {
            'message': 'string'
        },
    ],
)
# ページスクロール
# - timestamp (integer) -- [REQUIRED]
さき(H.Saki)さき(H.Saki)

所感

というわけで、AWS SDKに関していうと「型がわかるメリット」が思っていたよりも享受できなかった上で、さらに「Goの静的型付けによるきちっとさ」が逆に牙を向いてきているような体感があるんです。
私は「変数がどんな型なのか分かっていた方がやりやすいに決まっている」というほど静的型付けどっぷり思想で育ったため、正直「型がついている故に逆に使いづらい」という現象に正直戸惑ってます。

皆さん同じ現象・感想を持っているんでしょうか?それとも私の使い方がまだおぼつかないからこういう体感になるんでしょうか?
それとも「そもそもAWS APIが使いづらい」とかいう説もあります?

Ryuji IwataRyuji Iwata

AWS SDK for Goはv2でもちょっと書きづらいという話をちらほら聞きますね...

以下、Azure SDKで言いますと...

Azure SDKは公開されているAzure REST APIからAzure SDK Designに従ってautorestを使って各言語用のSDK(Web クライアント)を自動生成しているので、言語間の機能差は少ないように思われます。(ちょっと頑張れば、公開中のSDKが対応していない最新のAPIのSDKも手動で生成させることもできます。)

また、GoにはGo用のミドルウェア(go-autorest)が用意されていて、処理の並行実行やデータの型変換などがサポートされています。

ソースコードもGo Wayに寄っていて、素直な方だと思います。

GitHub - Azure/azure-rest-api-specs: The source for REST API specifications for Microsoft Azure.

Go Azure SDK Design Guidelines | Azure SDKs

GitHub - Azure/autorest: OpenAPI (f.k.a Swagger) Specification code generator.

GitHub - Azure/go-autorest: This package implements an HTTP request pipeline suitable for use across multiple go-routines and provides the shared routines relied on by AutoRest generated Go code.

GitHub - Azure/azure-sdk-for-go
: This repository is for active development of the Azure SDK for Go. For consumers of the SDK we recommend visiting our public developer docs at:

さき(H.Saki)さき(H.Saki)

なるほど、頂いた資料いろいろ拝見させていただきましたが、Azureだと直接所望のREST APIを叩く形なのですね。
AWS SDKみたいに独自構造体のオンパレードになるよりも、一般によく知られたRESTに乗っかるこちらの方が学習コストが低くて楽そうだなと思いました。

HTTPリクエスト・レスポンスを扱いやすくするようにgo-autorestも用意されているあたりとても親切ですね、やるなあMicrosoftさん(何様)

budougumi0617budougumi0617

個人の感想ですがAWS SDKはただ「自動生成しやすいコード(体裁)」ってだけで使いづらいのかなと思います。


以下のGoのコードがあったとして、resにカーソルを合わせてわかるのは「変数resがGetSomethingOutput型だ」というところまでで、「GetSomethingOutput型に何がフィールドとして含まれているか」までは出してくれませんでした。
...
本来の関心事である「構造体フィールド一覧」や「フィールドの中身」まで調べようとすると結局公式ドキュメントのXxxOutput型のページに飛ぶ羽目になる

webページ開かないとわからなくて大変だ、という主張だと理解したのですが、VSCodeで定義元にジャンプして直接コードファイルをみるのはダメなんでしょうか?

例で言うと(resじゃなくて申し訳ないですが、) GetSomething にカーソルをあてて「Go to definition(F12)」するとfunc GetSomething() (*GetSomethingOutput, error) が定義されているファイルを開けるので、更に GetSomethingOutput にカーソルを当ててもう一度 F12 を押下すれば構造体のコードファイルが開きますね。


あとは GetSomething にカーソルを当てた状態で「Peek definition(Macだと Alt + F12)」すると定義が開くので、そこで GetSomethingOutput をマウスホバーすると定義がポップアップで見れますね。
(ファイル開かなくて済むけれどマウス操作が挟まる)

スクショは SQS.SendMessage に対して上記操作を実行した例です。ファイル移動せずに構造体定義を確認できます。
Peek definitionしてマウスホバー

https://docs.aws.amazon.com/sdk-for-go/api/service/sqs/#SQS.SendMessage

さき(H.Saki)さき(H.Saki)

なるほど・・・!VSCodeの機能をフルに使いこなせてなかったです。
情報ありがとうございます。

webページ開かないとわからなくて大変だ、という主張だと理解したのですが、

もちろんこのような大変さもありましたが、一番はAPIの全容を理解するために行う「webページを開いたり、構造体の定義を追ったり」する作業のホップ数が多いというのが、私が感じていた不便さの本質かなあと思ってます。
(「XxxInput型の全容把握が大変」の箇所で記述)

tenntenntenntenn

tenntennですが、AWSのライブラリはかなり昔からあり、その頃からかなり使いづらい印象です。

使う側の気持ちで作られておらず、自動生成のしやすさや提供側の都合で用意されているからだと思います。

パッケージの分け方、型の分け方、命名、コンテキストの扱い、などもイマイチだなと思ってます。

さき(H.Saki)さき(H.Saki)

パッケージの分け方、型の分け方、命名、コンテキストの扱い、などもイマイチだなと思ってます。

このコメントを見て、このSDK for Goが使いづらいのは「静的型付けだから」ではなくて、「いまいちな設計のライブラリ独自型に従うことを静的型付けゆえに強制されてしまうから」なのかなと思いました。

コンテキストやパッケージの分け方までは掘り下げてなかったのですが、あまりお手本にしてはいけない例なんですね……。