レック・テクノロジー・コンサルティング株式会社TECH BLOG

【GCP】API Gateway × Cloud Functions × BigQueryでAPIを作ってみる

アプリケーションの機能を外部から利用できる仕組みであるAPI(Application Programming Interface)。最近ではChatGPTをはじめとした生成系AIサービスの利用拡大などに伴い、自作プログラムにAPIを組み込んで利用したり、システムやサービス間をAPIで疎結合化したり、APIでのデータ連携が主流になってきたりと、APIが身近な存在になっているのではないかと思います。

ところで、APIを利用することはよくありますが、作るとなるとどうでしょうか?裏側の仕組みがよくわからないだけに、なんだか難しそうなイメージがありますよね。実は、クラウドサービス(今回はGCP)を利用することで、そんなAPIを手軽に実現することができます。

具体的なAPIの作り方について、以降で詳しく見ていきましょう!

今回作るAPI

今回作成するのは、以下のようなJSON形式でユーザのID、名前、メールアドレスを受け取り、BigQuery上のテーブルに対して操作を行うシンプルなAPIです。

{
    "id": 1,
    "name": "レック太郎",
    "email": "req-taro@example.com"
}

機能としては、

  • ユーザ登録
  • ユーザ削除

の2つを考えます。構成図は以下のようになります。

API構成.png

リクエストをAPIゲートウェイが受け付け、登録・削除に応じて2つのファンクションのどちらかが起動され、BigQueryに対して操作を行うという流れです。

テーブル準備

まず、ユーザデータを管理するためのテーブルをBigQuery上に作成します。今回は簡単に以下のようなusersテーブルを用意しました。

BQ_テーブル.png

バックエンド設定

Cloud Functions

今回作るAPIのバックエンドとなるCloud Functionsのファンクションを作成していきます。

①サービスアカウント設定

最初に、Cloud Functions用のサービスアカウントを作成します。今回はCloud FunctionsからBigQueryにアクセスしたいので、BigQuery管理者の権限を付けておきます。

CF_サービスアカウント作成.png

②ファンクション作成(ユーザ登録)

ファンクションの作成画面に移り、ユーザ登録用ファンクションtest-api-addの設定を行います。

今回はHTTPリクエストを投げる形なので、トリガーはHTTPSです。ランタイムサービスアカウントには先ほど作成したサービスアカウントを指定します。ランタイム環境変数はファンクション起動後にプログラム内で参照できる環境変数で、今回はusersテーブルのテーブルIDを指定しています。

CF_ファンクション作成1.png

次にプログラムの設定です。

プログラムは「ランタイム」タブからPython, Go, Java, Node.js等を選ぶことができます。今回は以下のようなPythonプログラムを用意しました。main.pyがエントリポイントを含むメインプログラムです。

main.py

import os
import json

import functions_framework
from pydantic import BaseModel
from google.cloud import bigquery

class User(BaseModel):
    id: int
    name: str
    email: str

def entrypoint(request):
    # リクエストデータ取得
    request_json = request.get_json()

    # ユーザデータ取得
    user = User(
        id=request_json.get("id"),
        name=request_json.get("name"),
        email=request_json.get("email")
    )

    # ユーザ登録
    add_user(user)

    # レスポンス(正常終了)
    response_json = {
        "code": 200,
        "message": "ユーザ登録に成功しました。"
    }
    response_body = json.dumps(response_json, ensure_ascii=False).encode("utf-8")
    return response_body, 200, {"Content-type": "application/json"}

def add_user(user: User):
    # BigQueryクライアント設定
    bq_client = bigquery.Client()

    TABLE_ID = os.environ.get("TABLE_ID")

    # クエリ作成
    query = f"""
    INSERT INTO `{TABLE_ID}` (id, name, email)
    VALUES (@id, @name, @email);
    """
    job_config = bigquery.QueryJobConfig(
        query_parameters=[
            bigquery.ScalarQueryParameter("id", "INTEGER", user.id),
            bigquery.ScalarQueryParameter("name", "STRING", user.name),
            bigquery.ScalarQueryParameter("email", "STRING", user.email),
        ]
    )

    try:
        result = bq_client.query(query, job_config=job_config, timeout=60).result()
    except Exception as e:
        raise Exception("クエリ実行に失敗しました。") from e

requirements.txt

functions-framework
requests
pydantic
google-cloud-bigquery

エントリポイントはリクエストデータが引数として渡される関数であり、リクエストを処理する本体部分となります。赤線部分の名前が一致していないとエラーになるのでご注意ください。

CF_ファンクション作成2.png

デプロイに成功すると、以下のような画面でファンクションの詳細を確認することができます。一部設定を除いて後から編集も可能で、メモリやCPUを増強したい場合は簡単に変更可能です。

CF_ファンクション作成3.png

③ファンクション作成(ユーザ削除)

ユーザ削除用ファンクションtest-api-delの設定を行います。

②と基本的に同じですが、main.pyのプログラムのみ異なります。こちらのmain.pyは以下の通りです。

main.py

import os
import json
from typing import Optional

import functions_framework
from pydantic import BaseModel
from google.cloud import bigquery

class User(BaseModel):
    id: int
    name: Optional[str]
    email: Optional[str]

def entrypoint(request):
    # リクエストデータ取得
    request_json = request.get_json()

    # ユーザデータ取得
    user = User(
        id=request_json.get("id"),
        name=request_json.get("name"),
        email=request_json.get("email")
    )

    # ユーザ削除
    del_user(user)

    # レスポンス(正常終了)
    response_json = {
        "code": 200,
        "message": "ユーザ削除に成功しました。"
    }
    response_body = json.dumps(response_json, ensure_ascii=False).encode("utf-8")
    return response_body, 200, {"Content-type": "application/json"}

def del_user(user: User):
    # BigQueryクライアント設定
    bq_client = bigquery.Client()

    TABLE_ID = os.environ.get("TABLE_ID")

    # クエリ作成
    query = f"""
    DELETE FROM {TABLE_ID}
    WHERE id = @id
    """
    job_config = bigquery.QueryJobConfig(
        query_parameters=[
            bigquery.ScalarQueryParameter("id", "INTEGER", user.id),
        ]
    )

    # クエリ実行
    try:
        result = bq_client.query(query, job_config=job_config, timeout=60).result()
    except Exception as e:
        raise Exception("クエリ実行に失敗しました。") from e

こちらも同じようにデプロイします。

CF_ファンクション作成4.png

以上でCloud Functionsの設定は完了です。

フロントエンド設定

API Gateway

①サービスアカウント設定

Cloud Functionsと同様、サービスアカウントの作成と設定を行います。Cloud Functionsの呼び出しで「未認証の呼び出しを許可」を選択していた場合、権限付与の設定は必要ありません。

GW_サービスアカウント作成.png

②API仕様作成

APIゲートウェイを作成するにあたり、OpenAPI仕様を作成する必要があります。OpenAPIというのは、APIの仕様を定義するためのフォーマットであり、いわばAPIの設計書です。APIのアクセス先のパスとそれに対応するバックエンド、リクエストとレスポンスの内容などをYAMLまたはJSON形式で記述します。

今回設定するOpenAPI仕様は以下の通りです。

test-api-config.yaml

swagger: '2.0'
info:
  title: test-api
  description: テストAPI
  version: 1.0.0
schemes:
  - https
tags:
  - name: user
    description: ユーザ操作
produces:
  - application/json
paths:
  /api/v1/add:
    post:
      tags:
        - user
      summary: ユーザ情報を登録する
      description: ユーザ情報を登録する
      operationId: addUser
      x-google-backend:
        address: https://asia-northeast1-xxxxxxxxxxxxx.cloudfunctions.net/test-api-add
        path_translation: APPEND_PATH_TO_ADDRESS
        deadline: 60
      parameters:
        - in: body
          name: body
          description: ユーザ情報登録用データ
          required: true
          schema:
            $ref: '#/definitions/User'
      responses:
        '200':
          description: '成功'
          schema:
            $ref: '#/definitions/Response'
  /api/v1/del:
    post:
      tags:
        - user
      summary: ユーザ情報を削除する
      description: ユーザ情報を削除する
      operationId: delUser
      x-google-backend:
        address: https://asia-northeast1-xxxxxxxxxxxxx.cloudfunctions.net/test-api-del
        path_translation: APPEND_PATH_TO_ADDRESS
        deadline: 60
      parameters:
        - in: body
          name: body
          description: ユーザ情報削除用データ
          required: true
          schema:
            $ref: '#/definitions/User'
      responses:
        '200':
          description: '成功'
          schema:
            $ref: '#/definitions/Response'
definitions:
  User:
    type: object
    properties:
      id:
        type: integer
        example: 1
      name:
        type: string
        example: 'レック太郎'
      email:
        type: string
        example: 'req-taro@example.com'
  Response:
    type: object
    properties:
      code:
        type: integer
        example: 200
      message:
        type: string
        example: '成功'

x-google-backendで、先ほど作成したファンクションのURLを指定しています。pathsに指定した各パスが対象の機能をもつバックエンドに対応付けられるため、APIのURLは以下のようになります。

  • ユーザ登録:<APIゲートウェイのURL>/api/v1/add
  • ユーザ削除:<APIゲートウェイのURL>/api/v1/del

なお、OpenAPIはSwagger Editor等のツールを使って記述します。今回のOpenAPI仕様をSwagger Editorで確認した場合、右側のようにドキュメント化された状態のものを見ることができます。

Swagger_Editor.png

③ゲートウェイ作成

API Gatewayのコンソール画面からゲートウェイを作成します。API名や構成を設定し、「ゲートウェイを作成」を押すとゲートウェイの作成が始まります。作成には少し時間がかかります。(5~10分程度)

GW_ゲートウェイ作成.png

※本記事執筆時点で、OpenAPIはバージョン2.0のみサポートされているようです。バージョン3.0以上のOpenAPI仕様はエラーになるためご注意ください。

問題なく作成完了すれば、ゲートウェイのURLが払い出されます。画像の赤枠部分が今回作成したAPI(ゲートウェイ)本体のURLです。

GW_ゲートウェイ情報.png

ちなみに、ゲートウェイのURLはデフォルトの場合https://xxxxxx.an.gateway.devのようになりますが、ロードバランサを利用してカスタムドメインを設定することも可能です。詳しくはAPI ゲートウェイの HTTP(S) 負荷分散スタートガイドをご参照ください。

以上で今回のAPIは完成です!

動作確認

完成したAPIがちゃんと動くかどうか見てみましょう。今回作成したAPIは未認証の呼び出しを許可しているので、対象プロジェクト以外の環境からでもアクセス可能です。試しにローカル端末から、Talend API Testerというツールを使ってリクエストを送ってみます。

API_テスト1.png

ユーザ登録リクエストを送ると、期待通り「ユーザ登録に成功しました。」というレスポンスが返ってきました。実際にusersテーブルを確認してみると...

API_テスト2.png

無事に「レック 太郎」さんのデータが入っていました。同様にユーザ削除を試してみると、「レック 太郎」さんのデータが削除されることが確認できます。

あとがき

今回作成したAPIは簡易的なものなので、認証まわりやリクエストの処理、エラーハンドリングなど突っ込みどころの多いものになっていますが、APIの作り方についてざっくりとイメージを掴んでいただけたのではないでしょうか。

今回ご紹介したように、API GatewayやCloud Functionsといったサービスを使うことで、APIを手軽に作ることができます。サーバレスであるためリソースの管理がいらないことに加えて、利用状況に応じて後から柔軟にリソースの増強ができたり、自前であれこれ用意するのに比べ低コストで運用できる点が魅力的です。

この記事をシェアする

  • Facebook
  • X
  • Pocket
  • Line
  • Hatena
  • Linkedin

資料請求・お問い合わせはこちら

ページトップへ戻る