2025年1月
BigQueryへの強い依存を解消し、データベース層を抽象化してインターフェース経由でインジェクションできるようにする。その後、PostgreSQLサービスも作成してPostgreSQLでも動作するようにする。
-
BigQueryService (
domain/bigquery/BigQueryService.java)- BigQueryの
com.google.cloud.bigquery.BigQueryに直接依存 - 主な操作:
insertExecution()- 約定データの挿入insertPosition()/upsertPosition()- ポジションの挿入/更新insertTradeHistory()- 取引履歴の挿入queryPosition()- ポジションのクエリqueryAllPositions()- 全ポジションのクエリqueryTradeHistory()- 取引履歴のクエリqueryRecentExecutions()- 最近の約定のクエリregisterUser()- ユーザー登録userExists()- ユーザー存在確認createTablesIfNotExist()- テーブル作成
- BigQueryの
-
BigQueryUserDetailsService (
security/service/BigQueryUserDetailsService.java)- 直接
com.google.cloud.bigquery.BigQueryを使用 - ユーザー認証情報の取得
- 直接
-
BigQueryVolumeCalculationService (
domain/service/BigQueryVolumeCalculationService.java)- 直接
com.google.cloud.bigquery.BigQueryを使用 - 取引量計算のためのクエリ実行
- 直接
-
ExecutionQueueService (
domain/service/ExecutionQueueService.java)@Autowired(required = false) BigQueryService bigQueryService- 約定履歴の初期化と保存
-
PositionManager (
domain/position/PositionManager.java)@Autowired(required = false) BigQueryService bigQueryService- ポジションと取引履歴の保存・取得
-
TradeController (
domain/controller/TradeController.java)@Autowired(required = false) BigQueryService bigQueryService- 取引データの挿入
-
AuthController (
security/controller/AuthController.java)@Autowired(required = false) BigQueryService bigQueryService- ユーザー登録
-
BigQueryWriter (
domain/bigquery/BigQueryWriter.java)private final BigQueryService bigQueryService- 非同期書き込み処理
- BigQueryExecutionEntity - 約定データ
- BigQueryPositionEntity - ポジションデータ
- BigQueryTradeHistoryEntity - 取引履歴データ
これらのエンティティはBigQuery固有の変換ロジック(toBigQueryRow(), fromBigQueryRow())を含んでいる。
データベース層の抽象化は技術的に可能です。以下の理由から:
- 操作の共通性: BigQueryで実行している操作(INSERT, UPDATE, SELECT, UPSERT)は、PostgreSQLでも同様に実行可能
- エンティティの独立性: ドメインエンティティ(Execution, Position, TradeHistory)は既に存在し、データベース固有のエンティティから分離されている
- SpringのDI機能: Springの依存性注入により、実装を切り替えることが容易
以下のインターフェースを定義:
// データベース操作の共通インターフェース
public interface DatabaseService {
// Execution操作
void insertExecution(ExecutionEntity execution);
List<ExecutionEntity> queryRecentExecutions(LocalDateTime fromTime);
// Position操作
void upsertPosition(PositionEntity position);
PositionEntity queryPosition(String username, String symbol);
List<PositionEntity> queryAllPositions(String username);
// TradeHistory操作
void insertTradeHistory(TradeHistoryEntity tradeHistory);
List<TradeHistoryEntity> queryTradeHistory(String username);
List<TradeHistoryEntity> queryTradeHistory(String username, String symbol);
List<TradeHistoryEntity> queryTradeHistory(String username, int limit);
// User操作
void registerUser(String username, String encodedPassword, List<String> roles);
boolean userExists(String username);
UserEntity loadUser(String username);
// テーブル管理
void createTablesIfNotExist();
// 取引量計算(オプション)
Map<String, Long> calculateVolumeBySymbol(LocalDateTime fromTime);
Long calculateTotalVolume(LocalDateTime fromTime);
}BigQueryDatabaseService implements DatabaseService- 既存の
BigQueryServiceをリファクタリング
PostgreSQLDatabaseService implements DatabaseService- Spring Data JPAまたはJDBCを使用
public interface ExecutionEntity {
String getExecId();
String getOrderId();
String getUsername();
String getSymbol();
// ... その他のフィールド
}BigQueryExecutionEntity implements ExecutionEntityPostgreSQLExecutionEntity implements ExecutionEntity
または、単一のエンティティクラスを使用し、マッパーで変換する方法も可能。
application.propertiesで実装を切り替え:
# データベースタイプの選択
app.database.type=bigquery # または postgresql
# BigQuery設定
spring.cloud.gcp.project-id=your-project-id
spring.cloud.gcp.bigquery.dataset-name=your-dataset
# PostgreSQL設定
spring.datasource.url=jdbc:postgresql://localhost:5432/exch_sim
spring.datasource.username=postgres
spring.datasource.password=password@Configuration
public class DatabaseConfig {
@Bean
@ConditionalOnProperty(name = "app.database.type", havingValue = "bigquery")
public DatabaseService bigQueryDatabaseService() {
return new BigQueryDatabaseService();
}
@Bean
@ConditionalOnProperty(name = "app.database.type", havingValue = "postgresql")
public DatabaseService postgreSQLDatabaseService() {
return new PostgreSQLDatabaseService();
}
}-- BigQuery
MERGE `project.dataset.positions` AS target
USING (SELECT ...) AS source
ON target.username = source.username AND target.symbol = source.symbol
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED THEN INSERT ...
-- PostgreSQL
INSERT INTO positions (...) VALUES (...)
ON CONFLICT (username, symbol)
DO UPDATE SET ...;- BigQuery: TIMESTAMP型(マイクロ秒単位)
- PostgreSQL: TIMESTAMP型(マイクロ秒単位、互換性あり)
- BigQuery:
rolesは配列型 - PostgreSQL:
roles TEXT[]または別テーブルで管理
- BigQuery: クエリジョブは非同期で実行される(
job.waitFor()) - PostgreSQL: 通常は同期処理。大量データ処理時は非同期処理を実装可能
- BigQuery: トランザクションサポートが限定的
- PostgreSQL: 完全なACIDトランザクションサポート
- BigQuery: 大規模データ分析に最適化
- PostgreSQL: リアルタイムトランザクション処理に最適化
DatabaseServiceインターフェースを作成- 必要なエンティティインターフェースを定義
BigQueryServiceをBigQueryDatabaseServiceにリファクタリングDatabaseServiceインターフェースを実装- 既存のコードを段階的に移行
- PostgreSQLのテーブルスキーマを設計
PostgreSQLDatabaseServiceを実装- Spring Data JPAエンティティとリポジトリを作成
- テストを実装
- 両方の実装で同じテストを実行
- パフォーマンステスト
- エラーハンドリングの確認
- BigQuery固有の変換ロジック(
toBigQueryRow(),fromBigQueryRow())を抽象化する必要がある - 解決策: マッパーパターンを使用して変換ロジックを分離
- これらは直接BigQueryを使用している
- 解決策:
DatabaseServiceインターフェースに必要なメソッドを追加するか、別のサービス層を作成
- BigQueryとPostgreSQLでデータ型の表現が異なる場合がある
- 解決策: エンティティ層で統一された型を使用し、マッパーで変換
- 既存のBigQueryデータをPostgreSQLに移行する必要がある場合
- 解決策: データ移行スクリプトを作成
データベース層の抽象化は可能です。
以下の理由から実装を推奨します:
- ✅ 技術的実現可能性: すべての操作が抽象化可能
- ✅ 保守性の向上: データベース実装を切り替え可能
- ✅ テスト容易性: モック実装でテストが容易
- ✅ 将来の拡張性: 他のデータベース(MySQL、MongoDB等)への対応も容易
PostgreSQL実装も可能ですが、以下の点に注意が必要です:
- SQL文の違い(特にMERGE文)
- 配列型の扱い
- 非同期処理の実装方法
- パフォーマンス特性の違い
段階的な実装を推奨します。まずインターフェースを定義し、既存のBigQuery実装をリファクタリングしてから、PostgreSQL実装を追加するアプローチが安全です。