Google Sheets + Google Apps Script でローカル開発・本番デプロイ

こんにちは。Insight Edgeカクテル大好きDeveloperのntです。カシスのソーダ・ベリージュース・オレンジジュース割が好きで、美味しいならと思って全部混ぜてみたところなんとも言えない気持ちになりました。今回は お酒 Google Apps Script(以下GAS)を使って機能開発する案件に携わったので、ちょっとしたコツをご紹介します。お酒を飲みながらは開発していません。

目次


本書の意義

GASをGoogle Sheets(旧Googleスプレッドシート)と組み合わせてツール開発するケースが多いと思います。 個人的なGASの経験はWebエディタで1ファイルくらいのちょっとしたツールを書いたくらいで、ある程度の規模になると効率的に開発できるようにしたいですね。GAS開発で共通に使えるやり方なので、GAS開発に携わる人は一読です。

  • 計算処理の実装はGASとGoogle Sheetsをどう使い分ければよいかの指針がわかる
  • ローカルで開発したコードを簡単にGASにdeployできる
  • 本番環境のGASにGitHubのブランチからデプロイできる
  • 環境設定をどこに配置すればよいかの参考がわかる

本書の対象者

  • GAS開発でコードをどうデプロイすれば良いか知らない人、安定したデプロイを行いたい人

本書で触れないこと

  • ビジネスや業務課題・実装の詳細
  • Google SheetsやGASの仕様
  • JavaScriptなどプログラミング言語の詳細

イントロ

ビジネス・業務課題の確認

とある事業の担当者と会話し、As-Isをヒアリングしたところ、事業の運用者は、日々、経験で予測し最適値を決めているということがわかりました。To-Beをすり合わせした結果、以下を目指したいということです。

  • システムは、時系列予測・数理最適化・可視化をする。可視化のイメージサンプルはExcelで作ってある
  • 運用者は、予測結果と最適値を確認したい
  • 元データは外部システム(API連携)と、BigQueryにある

そこで、システムが自動で外部データを取得、予測し、最適値を探索できるようにすることで、業務課題の解決を目指します。

システム課題整理

システムを作るにあたり、基本方針を検討します。

まず、利用者は事業会社のユーザで、技術の専門家ではありません。予測アルゴリズムを不透明かつ複雑な仕組みにしてしまうと、価値を感じてもらえないリスクがあります。そこで、データ/アルゴリズムを可能な限りユーザの見えるところに配置します。その結果、ユーザが自力で確認できるので、システムの説明責任を果たします。

ところで、要件確認はExcelのサンプルで行ったのですが、ユーザはExcelであれば計算の流れを確認できるようなので、今回作るシステムも表計算をベースとするのが良いと考えました。ExcelならGoogle Sheetsへ移行作業もしやすく、かつ、ユーザは自力で詳細を確認できるのでお互いハッピーでしょう。 Google Sheetsなどの表計算でやりきれないETL処理もあるので、それに関してはクラウド型のGASで記述・実行すれば開発・実行できるようになります。

使い分けの指針は以下のとおりです。

  • シンプルな計算処理はGoogle Sheetsで実装する。複雑なことをするとユーザだけでなく開発者もわからなくなって保守しづらくなるのでやめましょう。
  • 外部データ連携やデータ加工などシンプルに行かない処理はGASで実装する。今回の要件ではバッチ処理で良いため、データの保存もGoogle Sheets上に保存してしまえば良いので、結合もシンプルです。

システム構成図

さて、方向性は固まったものの、システムを作るにあたり大きく以下のような課題がありました。いずれもGASで開発したことがないゆえですが、業務課題の解決のために1つずつ解決していこうと思います。 太字は今回の記事で紹介する対象です。

  • GASで複数ユースケース規模の開発方法
  • 開発環境と本番環境の分離方法
  • 環境変数・シークレット情報の管理方法
  • 本番環境へのデプロイ方法
  • BigQueryとの認証・連携方法
  • スケジューラを用いて特定時刻になったらバッチ処理の実行方法
  • 最適値探索の方法

本編

前置きが長くなりましたが、システム化の課題について1つずつ見ていきます。

GASで複数ユースケース規模の開発方法

今回、大きく4つの機能を実現することになりましたが(具体的には外部システム(API連携)やBigQueryから元データの取り込み、最適値の探索、自動実行制御)、ちょっとしたツールよりは大きめの実装になります。さすがにWebエディタだけで開発するのはしんどいです。ローカルIDEで書いて、ソースコードのディレクトリ構造をつくり、Gitでソース管理したいですね。

ここでは Clasp というツールを使い、ローカルで書いて、クラウドにpushして動作確認ができるのでやってみました。前提として、Google Sheetsと、それに紐づくGASが作成済みとします。

claspをインストールします。

npm install -g @google/clasp

次にCLIツールがクラウドリソースにアクセスできるようにログインします。

clasp login

ログインができたら、GASのスクリプトを手元にクローンします。そうすると、GASのWebエディタで .gs 拡張子のファイルが、手元では .js に変換されてダウンロードされます!

clasp clone "XXXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # <GASのScript ID>

ソースコードを書いたら、pushコマンドを実行してアップロードし、GASのWebエディタを開きます。すると、今度は手元の .js ファイルが、 .gs としてアップロードされています!

clasp push

なお、ファイルを保存したら自動で変更を検知してpushしてくれるwatchオプションが便利です。デプロイが楽でいいですね。

clasp push --watch

さて、開発を進めていくとコードがそれなりに大きくなり、リファクタリングし、ディレクトリを切りたくなります。どうなるでしょうか?例えばローカルで ./repo/BigQueryService.js として、 clasp push すると「repo/BigQueryService.js」というファイルができます。なんとファイル名にスラッシュが含まれます。。Webエディタ上では少し見にくいですが、意図はわかるので良いとしましょう。

また、GASはファイルの並び順で実行されるので、並び順によって挙動が変わります。グローバルに定義や関数実行しないように、注意して記述する必要があります。 clasp側で並び順の変更もできますが、使う必要のないように開発すると良いでしょう。

ここでのポイントは、「便利なのでGASなら全部claspでデプロイすれば良い」だと思いますよね。違います。「claspでのデプロイは開発環境だけにしておくと良い」です。理由は、環境切り替えの手間や、編集中の意図しないコードを本番にアップロードしたりとトラブルリスクがあるので、claspを本番で使うのはやめましょう。なお、失敗談としては .claspignore のルールを間違えて巨大な node_modules ディレクトリをアップロードしようとしました。いつまで経ってもアップロードが終わりませんでした。簡単に変更できるのは良いですが、それが本番環境だと怖いですね。

開発環境と本番環境の分離方法

素早く形にして、本番に反映し、ユーザに使ってもらいながら開発することになります。そうなると、環境を複数用意しておく必要があり、つまり環境分離を考える必要があります。

前提として、GASはGoogle Sheetsにコンテナバインドされています。スタンドアロン型ではないです。 Google Sheetsを2つ用意するのは当然として、Google SheetsにGASが紐付いてしまっているので、GASも2つ用意することになります。 ファイル名に開発とか本番であることがわかるように、Google SheetsとGASそれぞれのファイル名に識別子をつけておきましょう。例として「XXX_dev」「XXX_prod」のようにsuffixをつけると良いです。

環境変数・シークレット情報の管理方法

イケてる変数管理方法があるかと期待したのですが、GASにシークレットストアはなかったです。また、GASのプロパティサービス(スクリプトプロパティ、ユーザプロパティ、ドキュメントプロパティの3種類)はあるものの、読み書きのクォーターがPJ全体で適用されてしまいます。

他のPJの影響によってサービスが落ちてほしくないです。無償版だと50k/日のクォータ割当なので、不安ですね。1実行あたりの読み書きするプロパティの数×実行数×環境数×PJ数でリソースを消費していくのであれば、消費状況を管理できないまま不足しますし、リソース状況の監視もできればしたくないです。よって、プロパティサービスは基本的に使わないほうが良さそうです。

暫定的なやり方になりますが、考えられる方法はプロパティをソースコードで変数として静的に管理することです。そして、スクリプト実行時に、スクリプト自身の環境名(ファイル名やプロパティを参照)から多数の設定変数を選択・参照するのが良さそうです。

本番環境へのデプロイ方法

上記でも述べましたが、本番環境はきちんと確実にデプロイしたいですね。繰り返しますが、本番ではclasp pushを使いません。その代わりに、安全にデプロイできるChrome拡張を使います。

前提として、デプロイに使う開発者のブラウザはChromeとし、ソースコードはGitHubにあるとします。まず、Google Apps Script GitHub アシスタント をインストールします。

次に、GASのWebエディタを開くと、拡張機能がアドオンされているので、そこから設定をしていきます。 拡張機能がGitHubにアクセスするためのAccess Tokenの発行を求められるので、GitHubで生成します。そして、拡張機能でGitHubユーザ名とGitHubのAccess Tokenを入力してログインします。

ここで、 .js でソースコード管理している場合は、設定アイコンから Sync Type.js にします。そうしないとGitHub上のファイルを認識せず、デプロイできません。

最後に、プルダウンからGitリポジトリとブランチ名を選択し、「↓」アイコンをクリックします。現行のWebエディタと最新のGitHub間のソースコード差分が表示されるので、変更を確定します。するとWebエディタが自動でリロードされ、最新のソースコードに置き換わります。変更内容が明確なので、確信を持ってデプロイできて安心ですね。

MVP開発し終わった感想

ここまでのシステム課題に対処しながらGAS特有のシート操作などを記述し開発しました。実装詳細には触れませんが、簡単に言うと一般的なソフトウェア開発同様、迷子にならないようにディレクトリ・ファイル設計をしておきました。ユースケースごとにファイルを分け、詳細も別ファイルの関数に切り出して定義しておきました。その結果、1週間ぶりのコード修正や新規機能追加であってもスムーズに開発できました。

動作確認はありきたりな方法で行いました。Google Sheetsと結合しているのでローカルだけで確認...は難しいですね。Webエディタで関数ごとに実行して動作確認しました。claspを使えばローカルから関数の発火は可能らしいですが、試していません。

GAS開発でデバッグ実行はつらく、生産性は高くないです。Webエディタではブレークポイントを仕掛けてデバッグ実行し、コールスタックと変数状態が見える程度であり、デバッグコンソールがないのは致命的です。また、ローカルIDEでデバッグできるとより理想的です。

今後の展望/できなかったこと

細かくなるので省こうと思いましたが、同じ問題にぶつかる人がいると思い、メモ書きを記載します。良い対処法があれば教えてください。

  • Google Sheets本体のマスタ管理・デプロイ
    • 標準機能でバージョン管理はできるが、単体ファイル向けであり、環境を分けると別ファイルとなって困難
    • 標準機能で既存ファイルにXLSXインポートする案では、既存ファイルに入れると数式が壊れる
    • Driveファイルコピーして新規ファイルとして扱うとGASとの紐付けが無くなり、GAS再設定が不便となるのでやれていない
  • TypeScript / Unit Test
    • 上記らを対応するために、 import/export シンタックスを記述することになる
    • ローカルのnodeでは動作するが、GASではが実行環境差分(ES Moduleエラー)によりデプロイできなくなる
    • 時間切れでギブアップ
  • CI/CD
    • セットアップの費用対効果・必要性がなかったため
  • ローカルでGAS環境を再現した開発
    • 結合しても環境差分があり、有用ではなさそう

まとめ

本書ではユーザ目線の観点に基づいて、計算処理をGASとGoogle Sheetsで使い分けて実装しました。また、環境設定は暫定解としてGASの設定ファイルとしてもたせれば良いとわかりました。最後に、ローカルで開発したコードを簡単にGASにdeployし、本番環境にGitHubからデプロイできるようになりました。

ご紹介した内容は開発の途中からでも適用できるので、いまからでも迅速かつ安定感のある開発をしていきましょう。