要約
- リトライ処理には、
github.com/hashicorp/go-retryablehttp
を使う - ただし、直接利用できないので、StandardClientからTransportを取得して、
NewTransport()
を使ってGoogle APIsで利用可能な状態に変換する - Sheetsサービスには、
WithHTTPClient()
でリトライ可能なクライアントを設定する
本文
Google Sheets APIには、当然ながら呼び出し回数の制限がある。1分毎に呼び出し可能回数が回復するので、適切にリトライを行うことで、処理の失敗を抑えることができる。
リトライ間隔の戦略としては、Exponential backoffが挙げられていた。
有名なパッケージでは、github.com/cenkalti/backoff/v4
を使うことで、Exponential backoffを使ったリトライ処理を書くことができる。
今回は、github.com/hashicorp/go-retryablehttp
(以下、retryablehttp)を利用する。理由としては以下。
-
http.Client
を設定するだけで、リトライが行われるので簡単 - HTTPのステータスコードが理解されてリトライが行われる
-
Retry-After
ヘッダーをサポートしている
retryablehttpを使う際の注意点としては、StandardClient()
で生成したhttp.Client
をそのままSheets APIのサービスに利用できない。
option.WithHTTPClient(retryClient.StandardClient())
のように渡してしまうと、シークレットが正しく設定されないためAPIを利用することができなくなるためである。
対策としては、StandardClientからTransportを取り出し、google.golang.org/api/transport/http
パッケージのNewTransport()
を使って、Google APIsで利用可能な状態にする。
サンプルコード
package main
import (
"context"
"fmt"
"net/http"
"github.com/cockroachdb/errors"
"github.com/hashicorp/go-retryablehttp"
"github.com/samber/mo"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
googlehttp "google.golang.org/api/transport/http"
)
const (
spreadsheetID = ""
sheetID = 0
)
type Client struct {
srv *sheets.Service
}
func main() {
ctx := context.Background()
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = 5
standardClient := retryClient.StandardClient()
transport, err := googlehttp.NewTransport(context.Background(), standardClient.Transport)
if err != nil {
panic(err)
}
service, err := sheets.NewService(ctx, option.WithHTTPClient(&http.Client{
Transport: transport,
}))
if err != nil {
panic(err)
}
c := Client{srv: service}
res := c.getSheetTitle(ctx, spreadsheetID, sheetID)
if res.IsError() {
panic(res.Error())
}
fmt.Println(res.Get())
}
// getSheetTitle シート名を取得
func (c Client) getSheetTitle(ctx context.Context, spreadsheetID string, sheetID int64) mo.Result[string] {
s, err := c.srv.Spreadsheets.Get(spreadsheetID).Context(ctx).Do()
if err != nil {
return mo.Err[string](errors.Wrap(err, "get spreadsheet"))
}
for _, v := range s.Sheets {
if v.Properties.SheetId == sheetID {
return mo.Ok(v.Properties.Title)
}
}
return mo.Err[string](fmt.Errorf("sheet not found"))
}