スターターを構築する

このドキュメントでは、アプリまたはサービスがイベントの発生時に Google Workspace Studio に通知し、フローの実行を開始できるようにする開始条件を構築する方法について説明します。API では、スターターは workflowTriggers と呼ばれます。

開始条件はフローを開始しますが、ステップは、フローを構成する一連のタスク内の 1 つのタスクです。開始条件を構築することで、ユーザーはアプリやサービスからのリアルタイム イベントに反応する自動化されたフローを設定できます。

スターターの構築には、アドオン マニフェスト ファイルでスターターを宣言し、Google Apps Script でライフサイクル コールバックを実装するか、Google Workspace Studio API エンドポイントにペイロードを投稿してスターターを起動します。

前提条件と OAuth 認証

Workspace Studio API エンドポイントと通信するには、アプリまたはサービスが OAuth 2.0 を使用して認証を行う必要があります。アプリは、認証時にユーザーに次の専用 OAuth スコープをリクエストする必要があります。

https://www.googleapis.com/auth/workspace.studio.trigger

このスコープにより、アプリは Workspace Studio API を呼び出し、ユーザーがその開始条件用に構成したフローを配信できます。

オフライン アクセス トークンと更新トークン

スターターは、外部サービスでイベントが発生したときに Workspace Studio に非同期で通知します。これは、ユーザーがフローを構成してから数時間、数日、数か月後に発生する可能性があります。そのため、API エンドポイントを呼び出すときに、サービスは有効な OAuth 2.0 アクセス トークンを提供する必要があります。

アドオン イベント オブジェクト(スターター構成時やライフサイクル コールバック リクエスト時など)で Google から提供されるアクセス トークンは有効期間が短く、1 時間のみ有効です。将来的にスターター イベントを非同期で起動するには不十分です。Workspace Studio API を継続的に呼び出すには、サービスでオフラインの更新トークンが必要です。これにより、必要に応じて新しいアクセス トークンを生成できます。

認可の処理方法と更新トークンの取得方法は、アドオンのランタイムによって異なります。

  • HTTP アドオン(代替ランタイム): HTTP アドオンの場合、バックエンド サービスは、オフライン アクセス(access_type=offline)をリクエストして更新トークンを受け取るために、組み込みのアドオン認証とは独立した別の OAuth 2.0 認証フローを実装する必要があります。

    ユーザーが Workspace Studio で開始条件を構成するときに、ログイン カードまたは認証カードを表示して、この接続を承認するようユーザーに求めることができます。承認カードの返却と OAuth フローの処理について詳しくは、Google Workspace アドオンをサードパーティ サービスに接続する(Google Workspace を接続先のサードパーティ サービスとして扱う)をご覧ください。

    バックエンド サービスは、更新トークンを安全に保存し(たとえば、サービスのデータベースで triggerId とともに保存)、イベントが発生するたびに、スターターの notifyUri または triggers.fire API エンドポイントにリクエストを送信する前に、更新トークンを使用して新しいアクセス トークンを取得する必要があります。

  • Google Apps Script アドオン: スケジュール設定された(時間駆動型)トリガーを使用してイベントをポーリングする Google Apps Script ベースのアドオンは、独立した OAuth フローの実装をスキップできます。スケジュール設定されたトリガーは Google Apps Script ランタイム環境内で直接実行されるため、Google Apps Script はマニフェストで宣言されたスコープを使用して OAuth トークンを自動的に管理し、更新します。

マニフェスト ファイルでスターターを定義する

スターターを定義するには、アドオン マニフェスト ファイル(appsscript.json)の addOns.studio.flows.workflowElements ブロックに追加します。この構成は、Apps Script と HTTP ランタイム(代替ランタイム)の両方で必要です。要素を workflowAction(ステップの定義時に使用)ではなく workflowTrigger として構成します。詳しくは、Google Workspace アドオンのマニフェスト構造をご覧ください。

workflowTrigger ブロック内で、以下を指定します。

  • inputs: ユーザーが構成カードで構成する変数(プロジェクト名、リソース フィルタなど)。
  • outputs: フローの下流ステップにスターターから返される変数。
  • onConfigFunction: ユーザー構成インターフェースを表示するコールバック関数の名前。
  • onManageFunction: スターター サブスクリプションの作成と削除を処理するために Google によって呼び出されるコールバック関数の名前。

次のコードサンプルは、イベント スターターのマニフェスト定義の例を示しています。

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "Trigger App",
      "logoUrl": "https://fonts.gstatic.com/s/i/short-term/release/googlesymbols/start/default/24px.svg",
      "useLocaleFromApp": true
    },
    "studio": {
      "flows": {
        "workflowElements": [
          {
            "id": "triggerDemo",
            "state": "ACTIVE",
            "name": "Event Trigger",
            "description": "Fires when a event occurs in the app.",
            "workflowTrigger": {
              "inputs": [
                {
                  "id": "projectId",
                  "description": "The project identifier to watch.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "outputs": [
                {
                  "id": "eventName",
                  "description": "The name of the triggered event.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                },
                {
                  "id": "eventMessage",
                  "description": "Detailed event message description.",
                  "cardinality": "SINGLE",
                  "dataType": {
                    "basicType": "STRING"
                  }
                }
              ],
              "onConfigFunction": "onConfigTrigger",
              "onManageFunction": "onManageTrigger"
            }
          }
        ]
      }
    }
  }
}

スターター サブスクリプションのライフサイクルを処理する

ユーザーが開始条件を含むフローを構成して有効にした場合、またはフローが無効化または削除された場合、Google はマニフェストで宣言された onManageFunction コールバック関数を使用してアドオンを呼び出します。

ライフサイクル イベント オブジェクト

コールバック関数は、アクション コンテキストを含む WorkflowEventObject を受け取ります。たとえば、以下のようなものがあります。

  • トリガーの作成(event.workflow.triggerCreation: フローが公開または有効になったときに発生します。

    • triggerId: このスターター登録インスタンスを識別する一意の UUID 文字列。

    • notifyUri: このスターター登録に関連付けられた一意の REST API エンドポイント URL(https://workspacestudio.googleapis.com/v1/triggers/YOUR_TRIGGER_ID:fire など)。

    • inputs: ユーザーがカードから構成した変数入力。

  • トリガーの削除(event.workflow.triggerDeletion: フローから開始条件が削除されたとき、またはフロー全体が無効化または削除されたときに発生します。

    • triggerId: クリーンアップするサブスクリプション インスタンスの一意の UUID 文字列。

代替ランタイム(HTTP API)のサブスクリプションのライフサイクル

代替ランタイムを使用して構築されたアドオンの場合、サブスクリプション ライフサイクル通知は、onManageFunction コールバック関数で指定されたアクション名を使用して、アドオンの構成済み HTTP エンドポイント URL に HTTP POST リクエストで配信されます。ペイロードは WorkflowEventObject の JSON 表現と一致します。

代替ランタイムの詳細については、HTTP エンドポイントを使用して Google Workspace アドオンを構築するをご覧ください。

Apps Script でライフサイクル コールバックを実装する

次の Apps Script の例は、ユーザー インターフェース カードを構成する方法、onManageTrigger を使用してサブスクリプションのライフサイクル イベントを処理する方法、イベントが発生したときにスターター リクエストを Google に送信する方法を示しています。

Apps Script

/**
 * Generates and returns the user configuration card to collect inputs.
 */
function onConfigTrigger() {
  const projectInput = CardService.newTextInput()
    .setFieldName("projectId")
    .setTitle("Project ID")
    .setHint("Enter the project identifier to watch");

  const section = CardService.newCardSection()
    .setHeader("Configure Event Trigger")
    .addWidget(projectInput);

  const card = CardService.newCardBuilder()
    .addSection(section)
    .build();

  return card;
}

/**
 * Handles subscription lifecycle events sent from Google Workspace Studio.
 *
 * @param {Object} event The Workspace Studio event object.
 */
function onManageTrigger(event) {
  const triggerCreation = event.workflow.triggerCreation;
  const triggerDeletion = event.workflow.triggerDeletion;

  if (triggerCreation) {
    const triggerId = triggerCreation.triggerId;
    const notifyUri = triggerCreation.notifyUri;
    const inputs = triggerCreation.inputs;

    // Extract input values configured by the user.
    const projectId = inputs["projectId"].stringValues[0];

    // TODO: Save triggerId, notifyUri, and projectId in your database/service.
    // Your backend service listens for events related to 'projectId'
    // and calls notifyUri when those events occur.
    console.log("Trigger subscription created: " + triggerId +
                ", Notify URI: " + notifyUri +
                ", Match Project: " + projectId);

  } else if (triggerDeletion) {
    const triggerId = triggerDeletion.triggerId;

    // TODO: Remove references to triggerId from your database and stop
    // sending future event notifications to the associated notifyUri.
    console.log("Trigger subscription deleted: " + triggerId);
  }
}

/**
 * Mock function showing how your backend service fires the trigger.
 * This logic runs on your service when a watched event occurs.
 *
 * @param {string} notifyUri The stored notifyUri associated with the trigger.
 * @param {string} triggerId The stored triggerId.
 * @param {string} userAccessToken The OAuth 2.0 access token for the user
 *     (obtained using your stored refresh token).
 */
function simulateEventFire(notifyUri, triggerId, userAccessToken) {
  // A unique UUID version 4 is recommended as the requestId for idempotency.
  const requestId = Utilities.getUuid();

  const payload = {
    "name": "triggers/" + triggerId,
    "outputs": {
      "eventName": { "stringValues": ["EventOccurred"] },
      "eventMessage": { "stringValues": ["Hello from the service!"] }
    },
    "requestId": requestId
  };

  const options = {
    "method": "POST",
    "contentType": "application/json",
    "headers": {
      "Authorization": "Bearer " + userAccessToken
    },
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };

  const response = UrlFetchApp.fetch(notifyUri, options);
  const responseCode = response.getResponseCode();

  if (responseCode === 200) {
    console.log("Trigger successfully fired!");
  } else if (responseCode === 404) {
    // 404 means the trigger registration is invalid or deleted.
    console.log("Trigger not found. Stop sending events for this trigger.");
    // TODO: Clean up the trigger from your backend database.
  } else if (responseCode === 429 || responseCode >= 500) {
    console.log("Temporary error (" + responseCode + "). Retry using exponential backoff.");
  } else {
    console.log("Failed to fire trigger. HTTP Code: " + responseCode + " - " + response.getContentText());
  }
}

Workspace Studio API を使用する

Workspace Studio API(workspacestudio.googleapis.com)を使用すると、開始条件イベントをプログラムで Google に通知できます。

エンドポイントはベースパス https://workspacestudio.googleapis.com/v1 の下にあります。

開始条件イベントを通知する

triggers.fire メソッドを使用してスターターを起動し、フローの実行を開始します。

  • HTTP メソッド: POST
  • パス: /v1/triggers/{triggerId}:fire{triggerId} はトリガー サブスクリプションの作成時に取得された一意の識別子)
  • OAuth スコープ: https://www.googleapis.com/auth/workspace.studio.trigger

次のコードサンプルは、リクエストで開始条件を配信する方法を示しています。

リクエスト

{
  "name": "triggers/TRIGGER_ID",
  "outputs": {
    "eventName": {
      "stringValues": [
        "EventOccurred"
      ]
    },
    "eventMessage": {
      "stringValues": [
        "Hello from the service!"
      ]
    }
  },
  "log": {
    "textFormatElements": [
      {
        "text": "An event occurred in the app."
      }
    ]
  },
  "requestId": "UNIQUE_REQUEST_ID"
}
  • name(文字列、必須): 開始条件のリソース名。triggers/{triggerId} の形式で指定します。
  • outputs(マップ、省略可): イベントデータを表すスターター出力変数のマップ。各値は、型付きリスト(stringValuesbooleanValuesintegerValues など)をサポートする VariableData オブジェクトです。
  • log(オブジェクト、省略可): Workspace Studio の実行アクティビティ ログに表示される TextFormat マークアップ表現。
  • requestId(文字列、省略可): 再試行時に API のべき等性を確保するための、最大 36 文字の ASCII 文字の一意の識別子(UUID v4 を推奨)。

レスポンス

成功すると、レスポンスは空の JSON オブジェクト {} を返します。

Workspace Studio API の割り当て

workspacestudio.googleapis.com サービスに送信されるトラフィックは、システム過負荷の防止、リソースの公平な使用の促進、Google Workspace 全体のパフォーマンスの保護を目的として制限されます。

次の割り当てが適用されます。

割り当てのタイプ 割り当て
プロジェクトごとの 1 分あたり 1,000 件のスターター リクエスト
ユーザーごとに 1 分あたり 100 件の開始条件リクエスト

割り当てのタイプは次のとおりです。

  • プロジェクトごとに 1 分あたり: 単一のデベロッパーの Google Cloud プロジェクトから発生するスターター イベントの累積数を、スターターを実行しているすべてのユーザーで 1 分あたり 1,000 リクエストに制限します。
  • ユーザーあたりの 1 分あたり: 特定の Cloud プロジェクトで、単一のエンドユーザーのスターター呼び出しの累積数を 1 分あたり 100 件のリクエストに制限します。

時間ベースの割り当てエラーを処理する

これらの割り当てを超過すると、API はレートに基づく割り当てが超過したことを示す HTTP 429 Too Many Requests(または 429 Resource Exhausted)エラーコードを返します。

これらのエラーを解決するには、コードで例外をキャッチし、切り捨て指数バックオフ戦略を使用する必要があります。指数バックオフでは、試行間の遅延を徐々に長くしながら失敗したリクエストを再試行します。また、複数のクライアントが同期して同時に再試行しないように、ランダム化されたジッター(各イテレーションでランダム化された遅延を再計算)も含まれます。

  1. Workspace Studio API にリクエストを送信します。
  2. リクエストが 429 エラーで失敗した場合は、1 second + random_number_milliseconds 待ってから再試行します。
  3. 再度失敗した場合は、2 seconds + random_number_milliseconds 待ってから再試行します。
  4. 再度失敗した場合は、4 seconds + random_number_milliseconds 待ってから再試行します。
  5. このループを継続し、遅延を maximum_backoff しきい値(通常は 32 秒または 64 秒)まで 2 倍にします。
  6. 最大バックオフ時間に達したら、最大再試行回数に達するまでその一定の遅延を使用して再試行し、その後停止してエラーをログに記録します。

ベスト プラクティス

スターターを設計して実装する際は、次のベスト プラクティスを検討してください。

バッチリストではなく単一のイベントを送信する

スターターは、アイテムのバッチまたはリストを含む単一のイベントを生成するのではなく、個別の発生(単一のレコードの更新、新しいメッセージの投稿、タスクの割り当てなど)ごとに個別のイベントを生成するように設計します。

  • 組み込みスターターとの一貫性: Workspace Studio では、組み込みの Google Workspace スターター(Gmail でメールを受信したり、Google Chat でユーザーがスペースに参加したりするなど)は単一のイベントでトリガーされます。単一アイテム イベントの出力は、この動作に沿ったものであり、すべてのスターターでユーザーに一貫性のある予測可能なエクスペリエンスを提供します。
  • シンプルなフロー構成: フローの下流のステップでは、通常、一度に 1 つのアイテムが処理されます。単一アイテム イベントを発行すると、ユーザーは配列の反復処理やリストの解析を行う複雑な手順を追加することなく、変数を直接マッピングできます。
  • ポーリングとバッチ変更を個別に処理する: バックエンド サービスが外部 API をポーリングし、1 回のポーリング間隔で複数の変更されたアイテムを検出した場合は、それらを 1 つのバッチイベントにバンドルするのではなく、アイテムごとに個別の開始条件イベントをトリガーします。
  • イベントレートと割り当てを管理する: 複数の変更されたアイテムに対して個々のイベントをトリガーすると、リクエストが急増する可能性があるため、サービスが Workspace Studio API の割り当て(ユーザーあたり 1 分あたり 100 件のリクエストなど)を超えないようにしてください。ポーリング サイクルで大量のアイテム(100 件を超える変更されたレコードなど)が生成される場合は、429 Too Many Requests エラーを回避するために、イベント ディスパッチのペースを調整するか、スロットリングします。

主な動作とエッジケース

スターターを統合する場合、デベロッパーは特定のエラー動作とランタイム機能を処理する必要があります。

  • テスト実行のサポートなし: Workspace Studio は、スターターのテスト実行をサポートしていません。
  • べき等性と再生防止: 厳密には必須ではありませんが、HTTP または Apps Script のペイロードに一意の requestId(UUID など)を含めることをおすすめします。requestId を指定すると、API が重複する通知を検出して無視できるようになり、1 つのイベントに対してフローが複数回実行されるのを防ぐことで、べき等性が確保されます。
  • フローの無効化と再有効化: スターターを含むフローが Workspace Studio で無効になると、Google は triggerDeletion ライフサイクル イベントを onManageFunction コールバックに送信します。また、関連する FireTrigger メソッドの呼び出しは、404 Not Found エラー戻りコード(Requested entity was not found.)を返します。サービスは、404 エラーに応答して、その開始条件インスタンス ID の今後のアクティビティ通知の配信を停止する必要があります。

    ユーザーが後でフローを再度有効にすると、Google は新しい triggerIdnotifyUri を含む新しい triggerCreation イベントで onManageFunction コールバックを呼び出して、新しい定期購入ライフサイクルを開始します。以前の triggerId は完全に廃止され、再有効化されません。そのため、サービスは古いトリガー インスタンスが再有効化されたかどうかをポーリングまたは確認しないでください。詳細については、スターター サブスクリプションのライフサイクルを処理するをご覧ください。

  • べき等なサブスクリプションの削除: onManageFunction コールバック関数は、Google からの開始条件削除リクエストをべき等に処理する必要があります。同じ triggerId に対して削除フックが複数回呼び出された場合(一時的な接続の切断による再試行など)、関数は成功を返す必要があります。

  • フローの割り当て: Workspace Studio API の割り当てに加えて、ユーザーフローには追加の内部割り当て制御が適用されます。高頻度のループや過剰なイベント数により、安全性のしきい値を超え、フローが自動的に無効になることがあります。