【Snowflake】ネイティブアプリケーションによるAPIの民主化(External Network Access)

【Snowflake】ネイティブアプリケーションによるAPIの民主化(External Network Access) | Tableau-id Press -タブロイド-
snowflake-logo-1200x630-960x504-1 (1)

現在、技研商事インターナショナル(GSI)さんとの協業プロジェクトとして、GSIさん提供のAPI「r4b (report for biz)」をSnowflake上のGUIで操作できるアプリケーションの試作を進めています。
本記事ではこのプロジェクトを進めている背景とそれを支えるSnowflakeの新機能(External Network Access)についてまとめて共有しようと思います。

API導入をもっと簡単で直感的なものに

世の中にはたくさんのAPIが存在しており、手元にあるデータと組み合わせれば新しい価値を見出せるケースも多々あるでしょう。
ところがいざ手元のデータと組み合わせようとすると、下記のような懸念から導入の見送りを余儀なくされてしまうケースがあると考えています。
APIのリクエスト作成やレスポンスの解釈はある程度専門的な知識が必要になる
仕様書にある数多のパラメータの中から本当に必要なパラメータを選定する作業は骨が折れる

特にノーコーダー・ローコーダーの方にとっては、導入の大きな妨げになることは想像に難くありません。
そこで本プロジェクトではこの問題を克服すべく、Snowflake上のGUI操作のみで簡単にAPI連携できるようなアプリケーションを開発しています。

今回使用しているAPIについて

GSIさん提供のAPI「r4b」で使用できる下記機能に着目してプロトタイプを開発しています。
・商圏(例えば、ある地図上の点から自動車で5分のエリア)ポリゴンの計算
※本APIには商圏に基づいた統計情報を取得する等の機能も備わっています。詳細は公式ページをご覧ください。
※本APIは有償サービスのため、ご利用の際にはGSIさんへの問い合わせが必要になります。

下図の例は、実際にAPIから取得した「渋谷駅から自転車で15分」の商圏ポリゴンを可視化したものになります。

開発中のネイティブアプリケーション

ユーザはこのアプリを使うことで、自分の環境にあるテーブルの全レコードに対して、APIから取得した商圏ポリゴンを紐づけることができます。
アプリをインストールすると、下図の画面から操作を開始することになります。

ユーザが行う操作を順に説明していきます。

1. 外部APIへの接続設定

Snowflakeの内側から外側のネットワークへアクセスするためには、特別なネットワークルールを作成する必要があります。
本アプリではこの設定を全て下図のダイアログ形式で完了することができます。(実装方法については後述)

左が外部アクセスの許可、右がそのアクセス中に使用する認証情報(今回の場合はGSIさんから払い出されたAPIアカウント)です。

このダイアログを介して保存される認証情報はSnowflakeのSecretオブジェクトとして保存されるため、今回作成したネットワーク設定内のみの使用に制限することができます。

2. 読み込むテーブルの選択

商圏ポリゴンを紐づけたいテーブル(中心座標を指す経度・緯度カラムが必要)を選択します。

3. APIへの問い合わせを実行

ここまでの操作を終えると、APIへの問い合わせに使うパラメータの設定画面が表示されます。
ここでは、「自転車で15分」の商圏について問い合わせを行います。
問い合わせに使う経度・緯度カラムは、先ほど読み込ませたテーブルのカラムのリストから選択します。

「クエリ実行」ボタンを押すと、全レコードに対してAPIリクエストが行われます。

4. ポリゴン生成結果

生成が完了すると、読み込ませたテーブルに商圏ポリゴンのデータや使用したパラメータを合わせたテーブルが作成されます。

以上のように、API問い合わせをSnowflake外の操作なしに行うことができます。

カプセル化されたAPIのやり取り

本アプリによってユーザはどのような恩恵を得られるでしょうか?
アプリの裏側で実際に発行されているAPIのやり取りについて紹介します。

商圏ポリゴンを得るためには、下記JSON形式のパラメータをリクエストする必要があります。
それぞれ、GSIさんから払い出されたAPIのアカウント情報、中心座標、移動手段(今回は自転車)、トラベルタイム(今回は15分)を指しています。

{
  "userID": "ユーザID",
  "password": "パスワード",
  "mapCenter": {
    "x": 139.7023894,
    "y": 35.6598003
  },
  "mapAreaType": [202],
  "mapAreaSize": [15]
}

本アプリによって、パラメータの名称や「mapAreaType」などに指定される意味のある定数について、ユーザは気にする必要がなくなりました

一方でレスポンスは下記のようなJSON形式になります。レスポンスのステータスとポリゴンの頂点座標リストが返ってきます。

{
  "statusCode": "100",
  "statusDescription": "成功",
  "coordinate": {
    "points": [
      {
        "lat": 35.664952184809209,
        "lng": 139.7417653638407
      },
      {
        "lat": 35.66495220419425,
        "lng": 139.74287527166297
      },
      {
        "lat": 35.664952223561826,
        "lng": 139.74398417956826
      },
      .......

ユーザはStatusコードに基づく分岐処理(エラー対応等)やポリゴンデータの書式を考慮する必要がなくなりました

以上のように、API導入の敷居をぐんと下げることができます。

 

ネイティブアプリ × External Network Access(技術者向け)

2024年2月にGeneral Availability(一般公開, 以降GA)となったExternal Network Accessによって、External Access Integrationを作成さえすれば特定の外部ドメインに対するアクセスが可能になりました。これによって本アプリも実現可能になっています。
ちなみに、2024年7月にGAとなったExternal network access in Streamlit in Snowflakeを使うと、SiSの単位でExternal Access Integrationを設定できるので、自分のPCで実行するのとほぼ変わらないコードをデプロイすることができます。
ただし、ネイティブアプリ内のExternal network access in Streamlit in Snowflakeについては2024年8月現在でも未対応です

また、ネイティブアプリの場合、External Access Integrationをコンシューマに作成してもらう必要があるという点で、自アカウント内のSiSの外部アクセス実装手順とは大きく異なります。対応策としては2点考えることができます。

一つは、コンシューマに作成してもらったExternal Access IntegrationやSecretの識別子をアプリで受け取り、外部アクセスを行うプロシージャの定義文にそれらの識別子を設定する方法です。この方法だとコンシューマにワークシートで作業してもらう必要があり、それぞれのオブジェクトに対してある程度の知識が必要になります。実装方法についてはこちらの記事が詳しいです。

二つ目は、referenceオブジェクトを介してExternal Access Integrationの作成をコンシューマ側にリクエストする方法です。本アプリではこちらの方法を採用しています。本方式について説明していきます。

External Access IntegrationとSecretのreference設定

日本語のReferenceオブジェクトに関する公式ドキュメントには明記されていないのですが、referenceではExternal Access IntegrationおよびSecretも受け取ることができます。(要英語版ドキュメント参照)

この場合、ネイティブアプリのmanifest.ymlファイルに下記のような記述が必要です。

references:
  - consumer_secret:
    label: "APIのアカウント"
    description: "ID/PW形式"
    privileges:
      - READ
    object_type: SECRET
    register_callback: core.REGISTER_SINGLE_REFERENCE
    configuration_callback: core.GET_CONFIGURATION_FOR_REFERENCE
  - consumer_external_access:
    label: "APIへのアクセスに必要な外部アクセス設定"
    description: "External Access Integration"
    privileges:
      - USAGE
    object_type: EXTERNAL ACCESS INTEGRATION
    register_callback: core.REGISTER_SINGLE_REFERENCE
    configuration_callback: core.GET_CONFIGURATION_FOR_REFERENCE

register_callbackの設定はreferenceに必須なので説明は省きますが、External Access IntegrationまたはSecretを受け取る場合、configuration_callbackという項目の設定が必須になります。

configuration_callbackに指定されたプロシージャは、SiSでSwnoflakeのpermissionモジュールを使って「permissions.request_reference(“reference名”)」を実行した際に提供されるダイアログから設定できる項目を定義したJSONを返す必要があります。
今回の実装では、どちらも同じプロシージャ名を指定し、referenceの名前でCASE分岐する下記コードで対応しています。


CREATE OR REPLACE procedure core.GET_CONFIGURATION_FOR_REFERENCE(ref_name STRING)
  RETURNS STRING
  LANGUAGE SQL
  AS
  $$
  BEGIN
    CASE (ref_name)
      WHEN 'CONSUMER_EXTERNAL_ACCESS' THEN
        RETURN '{
                  "type": "CONFIGURATION",
                  "payload":{
                    "host_ports":["対象ドメイン"],
                    "allowed_secrets" : "LIST",
                    "secret_references":["CONSUMER_SECRET"]
                  }
                }';
      WHEN 'CONSUMER_SECRET' THEN
        RETURN '{
                  "type": "CONFIGURATION",
                  "payload":{
                    "type" : "PASSWORD"
                  }
                }';
  END CASE;
  RETURN '';
  END;
  $$;

External Access Integration内で使用するシークレットが無い場合は「secret_references」の記述は不要です。
また、本アプリの場合は認証情報がID/PWなので、payloadのtypeが「PASSWORD」になっていますが、OAuth2等の認証方式の場合はまた別の定義が必要になります。詳しくは公式ドキュメントをご覧ください。

SiSでreferenceを登録

setup_script内で、空のリファレンスをプロシージャに対して設定することはできないので、SiSのコード内でreferenceを確認した後に所定のプロシージャに対してALTERで設定しています。
ここからは、下記モジュールがimportされている前提で話を進めます。

import snowflake.permissions as permissions

リクエストはExternal Access Integrationに対してのみ行えば、内包されるSecretについてもリクエストが発行されます。今回の場合は下記のように行います。

permissions.request_reference("consumer_external_access")

リファレンスの作成を確認した後、下記のようなALTER文を発行することで、外部アクセスを有効化することができます。

alter function core.fetch_something(float, float, integer, float)
   set
     EXTERNAL_ACCESS_INTEGRATIONS = (reference('consumer_external_access'))
     SECRETS = ('my_account' = reference('consumer_secret'))

おわりに

SnowflakeのExternal Network Access機能を使えば、グラフィカルかつSnowflake内で閉じた操作で外部APIと連携できるので、これを機にAPI利用の裾野を広げていけたら嬉しい限りです。