GAS+スマレジ プラットフォームAPI連携の構成例
こんにちは!株式会社スマレジ 、開発部のmasaです。
大阪本社は新型コロナウィルスの感染拡大第三波の影響を受けて、 来週から開発部も原則出勤日なしの完全リモートワークを実施することになりました。(仕事の都合上、出社する日もありますが、必要最低限です)
コロナだけでなく、インフルエンザなども流行し出す季節なので、皆さんも健康にはお気をつけください。
さて、本日はすでに何度かしている、GAS+スマレジ プラットフォームアプリで実装するときのアーキテクチャのお話です。 内容的には、これまでの実装のまとめ的なお話になります。
2020年版 GASで作るスマレジ プラットフォームAPIアプリ アーキテクチャ例
※ 上記の図は、Lucid Chartで作成しています。Lucid Chartについては下記リンクと、過去ブログをご参考ください。
サービスとクライアント
GASはStand Alone Script(Googleの他のサービスに付随しない、独立したGAS)とContainter Bound Script(Googleのサービスに付随して使用されるGAS. Stand Alone Scriptと違い、そのサービスに関連するGASライブラリ類が予め導入されている)の2種類があります。
このうち、プラットフォームAPIを呼び出して(=スマレジ の連携する部分)、その内容をDBに登録したり、設定画面やgoogleサービスへのアクセスのハンドリングなどを実施する部分を「サービス」と呼んでいます。(いわゆるバックエンドです)また、サービスはgoogleのサービスと連携するための内部的なWebAPIを持ちます。
ここでは、DBにCloud SQLを使用していますが、データ量が少なかったり、一般公開する予定がないのであればスプレッドシートに変えてもいいし、既存のDBに繋いでも良いです。(ただし、スプレッドシートに出力する場合は、シートのアクセス権を管理者のみにするなどの注意が必要です)
また、サービスとは逆に、googleのサービス(スプレッドシートやスライド、ドキュメント)上のイベントで発火し、サービス側のWebAPIを呼ぶことでスマレジと連携する部分を「クライアント」と呼んでいます。
クライアントには、「ライブラリ」としてサービスのGASを読み込ませておきます。やり方は下記を参照ください。
Cloud Functionの役割
構成図を見ると、 * プラットフォームアプリの購入通知Webhookを受信するGASを別のStand Alone Scriptで用意している * バックエンドからDBを直接呼ぶのではなく、Cloud Functionを経由している
上記2点が奇妙に思われると思います。これは前回のブログで紹介したように、Google側で特定の権限でGASをWebAPIで実行すると、jdbcが未定義になる不具合が存在するため、それを回避するためです。詳しくは過去のブログをご参照ください↓
また、アプリ契約受信部分の実装例を記載しておきます。
/** * サインアップ処理 * @param e postデータ */ function doPost(e) { console.log(e.postData.contents); const requestStr = e.postData.contents; const requestJson = JSON.parse(requestStr); const exist = executeSQL("select * from contract_info where contract_id = ?", [requestJson.contractId]); let contractId = ""; while (exist.next()) { contractId = exist.getString('contract_id'); } if(contractId.length > 0) { throw new Error('既にサービスが存在しています: ' + spreadSheetId); } else { const results = executeSQL("insert into contract_info(contract_id) values(?)", [requestJson.contractId], true); } } /** * SQL実行汎用メソッド */ function executeSQL(sql, params = [], isUpdate = false) { let ipAddress = PropertiesService.getScriptProperties().getProperty("CLOUD_SQL_IP_ADDRESS"); let dbName = PropertiesService.getScriptProperties().getProperty("CLOUD_SQL_DB_NAME"); let userName = PropertiesService.getScriptProperties().getProperty("CLOUD_SQL_USER_NAME"); let password = PropertiesService.getScriptProperties().getProperty("CLOUD_SQL_USER_PASSWORD"); let connection = Jdbc.getConnection("jdbc:mysql://" + ipAddress + ":3306/" + dbName, userName, password); let statement = ""; if (params.length > 0) { statement = connection.prepareStatement(sql); for (let i = 0; i < params.length; i++) { statement.setString(i + 1, params[i]); } if (isUpdate) { statement.executeUpdate(); return true; } else { return statement.executeQuery(); } } else { statement = connection.createStatement(); if (isUpdate) { statement.executeUpdate(sql); return true; } else { return statement.executeQuery(sql) } } statement.close(); connection.close(); }
バージョン管理とアップデートの適応方法
GASは「公開」タブから公開することでAPIとしてコールしたり、スケジュール実行することができるようになります。 ただ、GASには特定のバージョンで最新のソース(=HEAD)を公開しても、反映されない仕様(不具合?)があるので、 原則、ソースを変えた場合は最新のバージョンを作成する必要があります。
ただし、サービス側のバージョンを上げたとしても、クライアントから呼ばれるバージョンを変更する場合は、GASの「ライブラリ」から最新のバージョンを選ぶ必要があります。この作業をエンドユーザが実施できる場合は、アップデートごとに利用者に通知し、各自で作業してもらえれば良いでしょうが、大抵の場合はその作業を実施するのは難しいですし、何よりクライアントのコードや実行環境を不特定多数に公開することになるので、好ましい状態とは言えません。
そこで、「マスター」を予め作成しておき、サービスやクライアントの更新があれば、下記のように対応します。
- googleサービスのID(https://docs.google.com/spreadsheets/d/[この部分]/edit)を予めDBに保存しておき、今ユーザが使っているサービスを特定できるようにしておく
- マスターを更新する。
- マスターを複製し、1の登録情報を複製したサービスのIDに更新する
マスターが複製されると、マスターに付随しているContainer Bound Scriptも一緒に複製されるので、ライブラリのバージョンやクライアントのソースの更新も併せて適応できます。
サービス側のバッチ実行は、トリガーを利用
定期的に実行される集計バッチや、特定のタイミングでGASを実行したい場合は、「トリガー」機能を利用します。
Googleのサービスで対応できない入出力を使いたい時は、Web Appとして利用する。
GASはHTMLを返し、リクエストを受け付けることもできるので、既存のGoogleのサービスで対応できないI/Oを定義したいときは、 HTMLして、Webアプリとして対応することもできます。
というわけで今回は今まで開発してきたGAS + スマレジ プラットフォームAPIの連携アプリの作り方について、ざっくりまとめてみました。