目次
はじめに
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
を叩くとクエリ結果が返ります。
最小構成のコードを公開しています。実際に試してみたい方はこちらからどうぞ!