株式会社スマレジの開発部でスマレジのサーバサイドを作っています

GAS+スマレジ プラットフォームAPI連携の構成例

こんにちは!株式会社スマレジ 、開発部のmasaです。

大阪本社は新型コロナウィルスの感染拡大第三波の影響を受けて、 来週から開発部も原則出勤日なしの完全リモートワークを実施することになりました。(仕事の都合上、出社する日もありますが、必要最低限です)

コロナだけでなく、インフルエンザなども流行し出す季節なので、皆さんも健康にはお気をつけください。

さて、本日はすでに何度かしている、GAS+スマレジ プラットフォームアプリで実装するときのアーキテクチャのお話です。 内容的には、これまでの実装のまとめ的なお話になります。

2020年版 GASで作るスマレジ プラットフォームAPIアプリ アーキテクチャ

f:id:masa2019:20201206230905p:plain
プラットフォームアプリ+GASアプリの構成例
※ 上記の図は、Lucid Chartで作成しています。Lucid Chartについては下記リンクと、過去ブログをご参考ください。

www.lucidchart.com

masa2019.hatenablog.com

サービスとクライアント

GASはStand Alone Script(Googleの他のサービスに付随しない、独立したGAS)とContainter Bound Script(Googleのサービスに付随して使用されるGAS. Stand Alone Scriptと違い、そのサービスに関連するGASライブラリ類が予め導入されている)の2種類があります。

developers.google.com

developers.google.com

このうち、プラットフォームAPIを呼び出して(=スマレジ の連携する部分)、その内容をDBに登録したり、設定画面やgoogleサービスへのアクセスのハンドリングなどを実施する部分を「サービス」と呼んでいます。(いわゆるバックエンドです)また、サービスはgoogleのサービスと連携するための内部的なWebAPIを持ちます。

ここでは、DBにCloud SQLを使用していますが、データ量が少なかったり、一般公開する予定がないのであればスプレッドシートに変えてもいいし、既存のDBに繋いでも良いです。(ただし、スプレッドシートに出力する場合は、シートのアクセス権を管理者のみにするなどの注意が必要です)

また、サービスとは逆に、googleのサービス(スプレッドシートやスライド、ドキュメント)上のイベントで発火し、サービス側のWebAPIを呼ぶことでスマレジと連携する部分を「クライアント」と呼んでいます。

クライアントには、「ライブラリ」としてサービスのGASを読み込ませておきます。やり方は下記を参照ください。

www.pnkts.net

Cloud Functionの役割

構成図を見ると、 * プラットフォームアプリの購入通知Webhookを受信するGASを別のStand Alone Scriptで用意している * バックエンドからDBを直接呼ぶのではなく、Cloud Functionを経由している

上記2点が奇妙に思われると思います。これは前回のブログで紹介したように、Google側で特定の権限でGASをWebAPIで実行すると、jdbcが未定義になる不具合が存在するため、それを回避するためです。詳しくは過去のブログをご参照ください↓

masa2019.hatenablog.com

また、アプリ契約受信部分の実装例を記載しておきます。

/**
 * サインアップ処理
 * @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の「ライブラリ」から最新のバージョンを選ぶ必要があります。この作業をエンドユーザが実施できる場合は、アップデートごとに利用者に通知し、各自で作業してもらえれば良いでしょうが、大抵の場合はその作業を実施するのは難しいですし、何よりクライアントのコードや実行環境を不特定多数に公開することになるので、好ましい状態とは言えません。

そこで、「マスター」を予め作成しておき、サービスやクライアントの更新があれば、下記のように対応します。

  1. googleサービスのID(https://docs.google.com/spreadsheets/d/[この部分]/edit)を予めDBに保存しておき、今ユーザが使っているサービスを特定できるようにしておく
  2. マスターを更新する。
  3. マスターを複製し、1の登録情報を複製したサービスのIDに更新する

マスターが複製されると、マスターに付随しているContainer Bound Scriptも一緒に複製されるので、ライブラリのバージョンやクライアントのソースの更新も併せて適応できます。

サービス側のバッチ実行は、トリガーを利用

定期的に実行される集計バッチや、特定のタイミングでGASを実行したい場合は、「トリガー」機能を利用します。

masa2019.hatenablog.com

Googleのサービスで対応できない入出力を使いたい時は、Web Appとして利用する。

GASはHTMLを返し、リクエストを受け付けることもできるので、既存のGoogleのサービスで対応できないI/Oを定義したいときは、 HTMLして、Webアプリとして対応することもできます。

tonari-it.com

というわけで今回は今まで開発してきたGAS + スマレジ プラットフォームAPIの連携アプリの作り方について、ざっくりまとめてみました。