Skip to content

DartからBigQueryのクエリを叩く方法

2021/09/04

Dart

目次

目次

  1. はじめに
  2. BigQuery にアクセスする ServiceAccount を作成
  3. BigQuery を Dart から呼ぶ
    1. BigQuery を実際に叩く

はじめに

Firestore などの NoSQL を使用していると BigQuery の結果を利用したいケースがあります。

Dart でバックエンドを構築している場合の,BigQuery のクエリを叩く方法を紹介します。

実際のコードを公開しています。お試しはこちらからどうぞ!

https://github.com/kawa1214/dart-bigquery

BigQuery にアクセスする ServiceAccount を作成

GCP の IAM のサービスアカウントから以下のロールを持ったサービスアカウントを作成します。

作成したサービスアカウントのキーから新しい鍵を作成し,JSON 形式で保存します。

service-account-json-1

service-account-json-2

サービスアカウントの情報を環境変数として定義 Docker で環境変数に追加します。

.env

PROJECT_ID=
PRIVATE_KEY_ID=
PRIVATE_KEY=
CLIENT_EMAIL=
CLIENT_ID=

DockerFile

# Official Dart image: https://hub.docker.com/_/dart
# Specify the Dart SDK base image version using dart:<version> (ex: dart:2.12)
FROM dart:stable AS build

# Resolve app dependencies.
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

# Copy app source code and AOT compile it.
COPY . .
# Ensure packages are still up-to-date if anything has changed
RUN dart pub get --offline
RUN dart compile exe bin/server.dart -o bin/server

# Build minimal serving image from AOT-compiled `/server` and required system
# libraries and configuration files stored in `/runtime/` from the build stage.
FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/bin/

# Start server.
EXPOSE 8080
CMD ["/app/bin/server"]

dockr-compose.yaml

version: "3.4"
services:
  server:
    build:
      context: .
      dockerfile: Dockerfile
    image: server
    restart: always
    container_name: server
    ports:
      - "8080:8080"
    env_file:
      - .env

lib/constants/environment_variables.dart に環境変数の Key を定義します。

const projectIdMapKey = 'PROJECT_ID';
const privateKeyIdMapKey = 'PRIVATE_KEY_ID';
const privateKeyMapKey = 'PRIVATE_KEY';
const clientEmailMapKey = 'CLIENT_EMAIL';
const clientIdMapKey = 'CLIENT_ID';

dart 側からは final env = Platform.environment; で呼び出すことができます。

import '../constants/environment_variables.dart' as environment_variables;
final env = Platform.environment;
env[environment_variables.projectIdMapKey] // これでプロジェクトIDを参照することが出来ます

BigQuery を Dart から呼ぶ

BigQueryAPI を wrapper しているパッケージを追加します。

dependencies:
  googleapis: ^4.0.0 // APIのwrapper
  googleapis_auth: ^1.1.0 // サービスアカウントの認証で使います

サービスアカウントの認証はgoogleapis_authを使用します。

final env = Platform.environment;

final credentials = googleapis_auth.ServiceAccountCredentials.fromJson({
  'type': 'service_account',
  'project_id': env[environment_variables.projectIdMapKey],
  'private_key_id': env[environment_variables.privateKeyIdMapKey],
  'private_key':
   '-----BEGIN PRIVATE KEY-----\n${env[environment_variables.privateKeyMapKey]}\n-----END PRIVATE KEY-----\n',
  'client_email': env[environment_variables.clientEmailMapKey],
  'client_id': env[environment_variables.clientIdMapKey],
});

final authClient = await googleapis_auth.clientViaServiceAccount(
  credentials,
  [googleapis.BigqueryApi.bigqueryScope],
);

googleapis を用いて BigQuery の API を叩きます。

デフォルトでは useLegacySql が false になっているため,標準 SQL を使う場合は false にします。

タイムアウトはデフォルトで 10 秒なので,使用ケースに合わせて適切な値に設定します。

final queryResponse = await googleapis.BigqueryApi(authClient).jobs.query(
  googleapis.QueryRequest(
  useLegacySql: false,
  query: query,
  timeoutMs: 20000,
),
 projectId,
);

BigQuery を叩くためのクライアントクラスは,最終的に以下のようになります。

import 'dart:io';

import 'package:googleapis/bigquery/v2.dart' as googleapis;
import 'package:googleapis_auth/auth_io.dart' as googleapis_auth;
import 'package:googleapis_auth/googleapis_auth.dart' as googleapis_auth;

import '../constants/environment_variables.dart' as environment_variables;

class BigQueryClient {
  BigQueryClient(this.projectId, this.authClient);

  final String projectId;
  final googleapis_auth.AutoRefreshingAuthClient authClient;

  Future<googleapis.QueryResponse> query(String query) async {
    try {
      final queryResponse = await googleapis.BigqueryApi(authClient).jobs.query(
            googleapis.QueryRequest(
              useLegacySql: false,
              query: query,
              timeoutMs: 50000,
            ),
            projectId,
          );
      return queryResponse;
    } catch (e, s) {
      stderr.writeAll({
        'Error': e,
        'StackTrace': s,
      }.entries);
      rethrow;
    }
  }

  static Future<BigQueryClient> getInstance() async {
    final env = Platform.environment;

    final credentials = googleapis_auth.ServiceAccountCredentials.fromJson({
      'type': 'service_account',
      'project_id': env[environment_variables.projectIdMapKey],
      'private_key_id': env[environment_variables.privateKeyIdMapKey],
      'private_key':
          '-----BEGIN PRIVATE KEY-----\n${env[environment_variables.privateKeyMapKey]}\n-----END PRIVATE KEY-----\n',
      'client_email': env[environment_variables.clientEmailMapKey],
      'client_id': env[environment_variables.clientIdMapKey],
    });

    final authClient = await googleapis_auth.clientViaServiceAccount(
      credentials,
      [googleapis.BigqueryApi.bigqueryScope],
    );
    return BigQueryClient(
      env[environment_variables.projectIdMapKey]!,
      authClient,
    );
  }
}

実際に BigQueryClient を呼び出す処理は次のようになります。

エラーがある場合 レスポンスの行が 0 の場合 でエラーハンドリングをしています。

import 'dart:io';

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;

import 'package:dart_bigquery/clients/big_query_client.dart';

void main() async {
  final handler = Pipeline().addHandler(_queryRequest);

  final server = await shelf_io.serve(handler, InternetAddress.anyIPv4, 8080);
  server.autoCompress = true;

  print('Serving at http://${server.address.host}:${server.port}');
}

Future<Response> _queryRequest(Request request) async {
  final client = await BigQueryClient.getInstance();

  final res = await client.query('''
    select * from table
    ''');

  final errors = res.errors ?? [];
  if (errors.isNotEmpty) {
    throw Exception(errors);
  }

  final tableRows = res.rows;
  if (tableRows == null) {
    throw Exception('Incorrect query');
  }

  final data = tableRows.map((e) => e.f!.map((e) => e.toJson())).toList();

  return Response.ok(data);
}

BigQuery を実際に叩く

docker-compose up -d

で立ち上げます。

http://0.0.0.0:8080を叩くとクエリ結果が返ります。

最小構成のコードを公開しています。実際に試してみたい方はこちらからどうぞ!

https://github.com/kawa1214/dart-bigquery