Google Apps Scriptを使ってWebアプリケーション風の検品ツールを作ってみた

こんにちは! 開発部の手塚(@tzone99)です。普段は社内ERPシステムの開発をしながらその周辺の業務ツールの制作を担当しています。こちらの記事ではGoogle Apps Script(GAS)を使ってアパレル商品の検品結果を登録するツールを作る中でポイントとなった部分を共有します。 使い慣れたGoogleのサービスをGASで連携させてお手軽にサーバーレスなアプリケーションを作りたい、という方の参考になればと思います。

背景

一般的な製造/物流業と同様、ZOZOでも倉庫で保管するアパレル商品にサイズや外観上の不備(ほつれ、汚れ、付属品の不足など)がないかをチェックする検品という作業があります。今回は「通常と異なる手順で検品し、その結果を集計/分析し仕分けに使いたい」という特定の商品群があったため、入力した結果を保存する専用ツールを作成することになりました。

何を作ったか

商品のQRコードを読み取ると検品項目が表示され、検品結果を入力して送信するとSpreadSheetに結果が保存されるツールを試験的に作成しました。

環境構成は以下の通りです。 単純にGitHubのコードをGAS側にデプロイしたい場合はChrome 拡張を使う方法もあります。今回はローカルにインストールしたエディタで開発したかったので clasp を使ってローカルのコードをGAS側にデプロイする方式を選択しました。

なぜGASを選んだか

今回のツールは期間限定で特定の商品群のみに使う想定で、また他のシステムとも疎結合で独立して動かせるものだったため、GASの持つ以下のようなメリットを見込んで採用しました。

  • 社員は全員Googleのサービスが使える(アカウントを持っている)のでアクセス権限の管理や仕様の説明が簡単。
  • サーバー管理を考えずに運用できる。
  • SpreadSheetにデータを保管していればエンジニアでなくても(SQLがわからない人でも)欲しい形式でデータを取得したり統計処理しやすい。

GASをWebアプリケーションとして使う際のポイント

スクリプト実行時、ブラウザにWebページを表示する

以下の記述により、アプリケーションを実行すると src/gas/checking.html がブラウザに表示できます。

//main.gs
function doGet() {
  return HtmlService.createTemplateFromFile('src/gas/checking').evaluate();
}

任意のjsファイル、cssファイルをインポートしたい場合、GASではそれらのファイルをそのまま扱えないためhtmlファイルとして記述する必要があります。例えば今回導入したBootstrap(Bootstrap v4.3.1)をインポートするケースを紹介します。

Bootstrapのcssテンプレートsrc/gas/css/bootstrap.min.cssをインポートする場合はファイルの拡張子をhtml(src/gas/css/bootstrap.min.html)に変更し、ファイルの中身を全て <style> タグで囲います。

<style>
  /*!
 * Bootstrap v4.3.1 (https://getbootstrap.com/)
 * Copyright 2011-2019 The Bootstrap Authors
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */

...中略
</style>

Bootstrapのjsファイル(src/gas/js/lib/bootstrap.min.js)をインポートする場合も同様に拡張子を変更し(src/gas/js/lib/bootstrap.min.html)ファイルの中身を全て <script> タグで囲います。

<script>
  /*!
    * Bootstrap v4.3.1 (https://getbootstrap.com/)
    * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
    * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
    */

  ...中略
</script>

トップページの checking.html に以下のように追記し、各ファイルをインポートします。

<?!= HtmlService.createHtmlOutputFromFile('src/gas/css/bootstrap.min').getContent(); ?>
<?!= HtmlService.createHtmlOutputFromFile('src/gas/js/lib/bootstrap.min').getContent(); ?>

このような方法で任意のjs/cssファイルをGASのアプリケーションへ流用できます。

google.script.runでGASのメソッドを非同期に実行する

htmlからGASのメソッドを実行する方法は主に以下の2通りです。

  • 任意のメソッドを実行するための doGet(), doPost() を定義し、そこに向かってhttpReqestを投げる
  • google.script.runからメソッド指定で呼び出す

GASはお手軽に実行できる一方で、一般的なWebアプリケーションよりパフォーマンスに優れているとは言えません。可能な限りAPIは非同期に実行して体感の待機時間を少なくしたいと考えました。doGet()doPost()をajax通信で非同期実行しようとすると、同一生成元ポリシーの制約により実行が制限されるため素直にgoogle.script.runを使うのが良いと思います。

以下はGASファイル(拡張子がgsのファイル)に定義したinsert()をフロント側(html)からgoogle.script.runで実行する場合の例です。

// 検査結果(inspectionData)をSpreadSheetに挿入する(insert)処理の例
google.script.run.withFailureHandler(onInsertFailure).withSuccessHandler(onInsertSuccess).insert(inspectionData);

withFailureHandlerwithSuccessHandlerを別途以下のように定義することで実行後の処理やエラーハンドリングの制御が可能です。

function onInsertSuccess(result) {
  // insertメソッドが成功した場合の処理
}

function onInsertFailure(e) {
  // insertメソッドが失敗した時の処理
}

SpreadSheetへの読み書き

SpreadSheetに定義したサイズデータから必要な行を抽出する際の実装例を紹介します。以下のように、製品を特定する情報(A列)と検品時の計測部位、想定されるサイズ(製品寸法)が紐づいたサイズデータを予め用意しておきます。

製品のQRコードを読み取った後、QRコードの情報に合致するサイズのみを画面に出力します。エラーハンドリング等、一部省略しています。

function checkSize(qrCode) {
  var result = {};
    const FOLDER_ID = 'xxx'; // サイズデータを格納したGoogle DriveのフォルダID
    const DEFAULT_SHEET_NAME = 'シート1';

    // QRコードに含まれる製品コードを取得
    var productCode = qrCode.substr(0, 9);

    // 製品コードに対応したSpreadSheetをサイズデータフォルダから取得
    var ss = getTargetSpreadSheet(FOLDER_ID, productCode);
    var sheet = ss.getSheetByName(DEFAULT_SHEET_NAME); // サイズデータを含んだシートを取得
    var lastRow = sheet.getLastRow(); // 最終行を取得
    var allSizeDataRaw = sheet.getRange(1, 1, lastRow, 4).getValues(); // シート内の全てのサイズデータを取得

    // 該当製品のサイズデータからQRコードの情報に完全一致する行のみを抽出しreturn
    var sizeMap = allSizeDataRaw.filter(function (row) {
      if (row[0] === qrCode) {
        return row.shift();
      }
    });
    result = {
      status: 200,
      message: 'OK',
      content: sizeMap
    };
  return result;
}

サイズデータはパフォーマンス改善の都合で製品コード単位に分割しました。というのも検品対象となった製品群のサイズデータは約30万行×4列あり、そこからQRコードで読み取った製品のデータのみを抜き出そうとすると約12秒かかっていました。製品コード単位(約300ファイル)に分割してからサイズデータを取得する上記の方法に変えることで約5秒程度までレスポンスが改善できました。APIの実行回数を減らすのがGASのパフォーマンス改善の基本ですが、それだけだとパフォーマンスが不十分なケースもあります。今回のように大量のデータから必要な行だけを読み取るケースでは適切な単位にファイルを分割することも改善策として有効でした。

SpreadSheetへの書き込みは以下のようなコードで実現しています(こちらもエラーハンドリング等一部省略)。

// 検査データをSpreadSheetに保存する
function insert(inspectionData) {
  const FOLDER_ID = 'xxx'; // SpreadSheetが格納されたフォルダID
  const DEFAULT_SHEET_NAME = 'シート1'; // 記録するシート名
  var fileName = 'returns_inspection'; // 保存するSpreadSheetのファイル名
  var ss = getTargetSpreadSheet(FOLDER_ID, fileName);
  var sheet = ss.getSheetByName(DEFAULT_SHEET_NAME);
  var rowData = [inspectionData.attrA, inspectionData.attrB]; // 検査結果を配列rowDataに格納
  sheet.appendRow(rowData); // SpreadSheetに一行追加
}

// フォルダから特定の名前のファイルを取得する
function getTargetSpreadSheet(folderId, fileName) {
  var folder = DriveApp.getFolderById(folderId);
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();
    if (file.getName() == fileName) {
      var existingSS = SpreadsheetApp.openById(file.getId());
      return existingSS;
    }
  }
  return '';
}

GASの使い所はどこか

実際にGASを業務用Webアプリケーション構築で使ってみて、Googleのサービスと簡単に連携できる強みを実感できました。とはいえパフォーマンス面やGAS特有の仕様など、Webアプリケーションの単純な置き換えにはならない面もあり使い所が重要です。今回の検品ツール作成中に検討した範囲で言えば、今後も以下のような条件を満たしていれば積極的にGASを使っていこうと思います。

  • Googleのサービスを普段使っていて業務データがGoogle Driveの中にある。
    • 例えば専用のDBサーバーを立ててデータを移管するのが仰々しいようなケースで有効です。
  • アクセス権を事細かに設定する必要がない、利用者が限定的な場合。
    • 例えば現在はスクリプトの実行権限をこのように指定できますが、これ以上の制御が必要であれば別途実装が必要です。
  • 扱うデータ量が少ない、もしくはパフォーマンスを気にしない場合。
    • 例えば利用範囲が限定的、利用頻度が少ない、バッチ処理等で有効です。

最後に

ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる方を募集中です。 ご興味のある方は、こちらからぜひご応募ください。

カテゴリー