Feedforce Developer Blog

フィードフォース開発者ブログ

Google MyBusiness APIのGoクライアントを生成する

こんにちは、 id:kogainotdan です。

現在私は新規事業開発のチームで、飲食店支援のサービスの可能性を探索しています。

飲食店、ひいてはローカルビジネスの運営において、集客施策として重要とされるのがGoogleマイビジネスの活用です。

ナレッジパネルやローカル検索において表示されている情報は、ユーザーとしても見たことがある方が多いと思います。

喫緊の話題としては、緊急事態宣言の前後における営業時間や営業形態の変更の正確な反映が、見込み顧客の取りこぼしを防ぐと共に店舗自体への信頼感の醸成にも役立つものと考えられます。

そんなGoogleマイビジネスですが、Googleの他のサービス同様にAPIが存在します。

ただし、まだGeneral Availableではない様子で、フォームによる利用申請が必要となるのですが、 Googleマイビジネスの管理画面で出来る操作の多くをサポートしており、ローカルビジネス関連のサービスを開発する際に重要なパーツとなり得ると思われます。

さて、そんなGoogle MyBusiness APIですが、公式に提供されているクライアントライブラリがJava, PHP, C#の3種となっています。

他のGoogleのサービスと同様にDiscoveryドキュメントが提供されていますので、この3種以外の任意の言語のクライアントライブラリを生成することは可能と思われます。

そこで本稿では、Google MyBusiness APIのGolangクライアントライブラリの生成までのステップを記録したいと思います。

Google API Discovery Service

公式ガイドの導入ドキュメントに沿ってAPIを有効化し、OAuth 2.0 の認証情報を取得すると、APIリクエストが発行出来るようになります。 (認証情報の取得は他のサービスと同様なので割愛します)

なお、APIの利用申請はorganization毎に1つだけ許可されるようですのでご注意ください。

さて、前述のようにGoogle MyBusiness APIのクライアントライブラリは、Java, PHP, C#の3種のみ提供されています。

この3種のいずれかでアプリケーションを開発する手も無くはないですが、すでに稼働している別の言語のコードがある場合など、APIの利用のためだけに別の言語のランタイムを導入するのは大仰に過ぎるというケースもあるかと思います。 (白状すると、私はこの3種のどれも書いたことがありません)

ところで上記のクライアントライブラリの紹介ページには、以下のようなことも書かれています。

Google My Business API Discovery ドキュメントは、この API の個々のバージョンのインターフェースについて解説しています。このドキュメントは Google API Discovery Service とともにご利用ください。

Swagger/Open API SpecificationやGraphQLなど、Web APIのクライアントは形式化された仕様から生成するのが一般的になっています。

Google API Discovery Serviceのサイトに飛ぶと、

Use the Google API Discovery Service to build client libraries

The Discovery API provides a list of Google APIs and a machine-readable "Discovery Document" for each API.

とあり、これもそういった仕様の一つであることが伺えます。

Google関連のAPIクライアントライブラリ

ここでGoogleが提供する各種サービスのAPIクライアントライブラリが集積されたレポジトリを見てみましょう。

何らかの形でこのDiscoveryドキュメントが利用されていることが見て取れます。 Goほど分かりやすい形ではないですが、Ruby.NETでも、同様に利用されていることが伺われます。

Google MyBusiness APIのドキュメントに示されている通り、このレポジトリにはGoogle MyBusiness APIのクライアントライブラリは置かれていません。

ただしSupport for Bussiness Messaging private API? #597

If you want to try out the generator and see if it works for you can.

You would need to clone this repo and generate from within it as the code it generates will rely upon some internal packages here.

という回答が付いています。

internal packageを利用する必要があるものの、Discoveryドキュメントさえあれば、個々の開発者がクライアントライブラリを生成する道はありそうです。

Google MyBusiness API クライアントライブラリの生成

上述のIssueから参照されているAdd instructions to generate services yourself #283にあるように、同じレポジトリにコードジェネレータも置いてあるようです。

実態としてはGoのmain packageのようですので、forkしてGoogle MyBusiness APIのDiscoveryドキュメントを食わせて上げれば良さそうです。 公式に公開されたツールというわけではないようなので、ドキュメントなどは見当たりませんが、--helpオプションで実行してあげるとサポートされているオプションが確認出来ます。

  -api string
        The API ID to generate, like 'tasks:v1'. A value of '*' means all. (default "*")
  -api_json_file string
        If non-empty, the path to a local file on disk containing the API to generate. Exclusive with setting --api.
  -api_pkg_base string
        Go package prefix to use for all generated APIs. (default "google.golang.org/api")
  -base_url string
        (optional) Override the default service API URL. If empty, the service's root URL will be used.
  -build
        Compile generated packages.
  -cache
        Use cache of discovered Google API discovery documents. (default true)
  -copyright_year string
        Year for copyright. (default "2021")
  -discoveryurl string
        URL to root discovery document (default "https://www.googleapis.com/discovery/v1/apis")
  -gendir string
        Directory to use to write out generated Go files (default "/Users/kogai/.gvm/pkgsets/go1.14/global/src/google.golang.org/api")
  -gensupport_pkg string
        Go package path of the 'api/internal/gensupport' support package. (default "google.golang.org/api/internal/gensupport")
  -googleapi_pkg string
        Go package path of the 'api/googleapi' support package. (default "google.golang.org/api/googleapi")
  -header_path string
        If non-empty, prepend the contents of this file to generated services.
  -htransport_pkg string
        Go package path of the 'api/transport/http' support package. (default "google.golang.org/api/transport/http")
  -install
        Install generated packages.
  -internaloption_pkg string
        Go package path of the 'api/option/internaloption' support package. (default "google.golang.org/api/option/internaloption")
  -option_pkg string
        Go package path of the 'api/option' support package. (default "google.golang.org/api/option")
  -output string
        (optional) Path to source output file. If not specified, the API name and version are used to construct an output path (e.g. tasks/v1).
  -publiconly
        Only build public, released APIs. Only applicable for Google employees. (default true)

Add instructions to generate services yourself #283で指摘されているapi_jsonなどのオプションも確認出来ます。

GitHub Actionsを使ったShopify テーマの自動デプロイ環境構築という記事でも触れたのですが、私の管理しているmonorepoレポジトリはBazelでビルドターゲットを管理していますので、 Google MyBusiness APIのクライアントライブラリも、ビルド単位の一つとして扱っています。

# WORKSPACE

# Discoveryドキュメントを取得する
http_file(
    name = "mybusiness_api_go_schema",
    downloaded_file_path = "mybusiness.json",
    sha256 = "084e68d0fc746fe9d0ba105f6878b8eb208181f5ee1288e79c35a11684ec4a13",
    urls = ["https://developers.google.com/my-business/samples/mybusiness_google_rest_v4p7.json"],
)

# forkしてくる代わりにcloneしておく
new_git_repository(
    name = "google_api_go_client",
    # non-bazelレポジトリなので、カスタムのビルドファイルを作っておく
    build_file = "//:google_api_go_client.bazel",
    commit = "074c16e73361434fc3d1f6ef62585d57b70a9d1b",
    remote = "https://github.com/googleapis/google-api-go-client.git",
    shallow_since = "1608641643 +0000",
)
# google_api_go_client.bazel
# ビルドに必要なのはinternal packageだけ
exports_files(
    [] + glob(["internal/**/*.go"]),
)
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
load("@build_bazel_rules_nodejs//:index.bzl", "generated_file_test")

filegroup(
    name = "gensupport",
    srcs = [
        "@google_api_go_client//:internal/gensupport/buffer.go",
        "@google_api_go_client//:internal/gensupport/doc.go",
        "@google_api_go_client//:internal/gensupport/json.go",
        "@google_api_go_client//:internal/gensupport/jsonfloat.go",
        "@google_api_go_client//:internal/gensupport/media.go",
        "@google_api_go_client//:internal/gensupport/params.go",
        "@google_api_go_client//:internal/gensupport/resumable.go",
        "@google_api_go_client//:internal/gensupport/retryable_linux.go",
        "@google_api_go_client//:internal/gensupport/send.go",
        "@google_api_go_client//:internal/gensupport/version.go",
    ],
)

# gazelle:ignore
go_binary(
    name = "codegen",
    args = ["-api_json_file=$(location @mybusiness_api_go_schema//file:mybusiness.json)"],
    data = [
        ":gensupport",
        "@mybusiness_api_go_schema//file:mybusiness.json",
    ],
    embed = [
        "@org_golang_google_api//google-api-go-generator",
    ],
    visibility = ["//visibility:public"],
)

generated_file_test(
    name = "json",
    src = "//packages/google-mybusiness-api-go/mybusiness/v4:mybusiness-api.json",
    generated = "@mybusiness_api_go_schema//file:mybusiness.json",
)

当初は単純にMakefileにcurlの実行などを書いていたのですが、Discoveryドキュメントを都度取得・クライアントライブラリを生成しているとCIが重くなるので、で適宜キャッシュさせるためにもBazelが効いています。

mybusiness/v4:
    npx bazelisk run -- //google-mybusiness-api-go:codegen \
        -gendir="$(CURDIR)" \
                # internal以下に置かれたpackageではなく、手元に移したpackageを使う
        -gensupport_pkg="github.com/your-organization/your-repository/packages/google-mybusiness-api-go/mybusiness/gensupport"
    cp -r $(BZL_BIN)/$(PKG)/codegen_/codegen.runfiles/google_api_go_client_internal/internal/gensupport $(CURDIR)/mybusiness

最後のgenerated_file_testはクライアントライブラリの生成には直接関係しないのですが、同じレポジトリでTypeScriptのコードも管理している関係でJavaScript関係のBazel Ruleが導入されています。 ついでだったので、Discoveryドキュメントのスナップショットテストのようなこともしています。

まとめ

Google MyBusiness APIのGoクライアントライブラリの生成について書いてみました。 何かの参考になれば幸いです。