👼
2021-09-04

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

Dart

BigQuery

FirestoreなどのNoSQLを使用しているとBigQueryの結果をレポートとして表示したいケースがあると思います.
Dartでバックエンドを構築している場合の,BigQueryのクエリを叩く方法を紹介します.

実際のコードを公開しています.お試しはこちらからどうぞ!
https://github.com/kawa1214/dart-bigquery

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

GCPのIAMのサービスアカウントから

  • BigQuery ジョブユーザ
  • BigQuery データ閲覧者

のロールを持ったサービスアカウントを作成します.

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

サービスアカウントの情報を環境変数として定義

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