JK
>読み込み中 // 読み込み中
ビューを初期化中
複式簿記方式を採用し、INR換算と厳格な監査証跡を提供する株式インセンティブ管理バックエンド。
株式報酬は厳密なトランザクション整合性と完全な監査追跡が不可欠であり、市場価格APIの障害時にも安定稼働する耐障害性が求められます。
| 技術 | バージョン | 目的 | 採用理由 |
|---|---|---|---|
| Go | 1.25.1 | プログラミング言語 | 強力な標準ライブラリ、並行処理プリミティブ、高速なコンパイル |
| Gin | 1.11.0 | HTTP フレームワーク、ルーティング、ミドルウェア | 高性能、ミドルウェア対応、活発なコミュニティ |
| PostgreSQL | 17 | リレーショナルデータベース | 高度な SQL 機能(ビュー、LATERAL、生成列、ENUM) |
| golang-migrate | v4.19.0 | データベースマイグレーション | シンプルなファイルベースのマイグレーションランナー |
| logrus | 1.9.3 | 構造化ログ | 構造化フィールドとレベル付きログ |
| go-sqlmock | v1.5.2 | テスト用データベースモック | 実際の DB なしで SQL をシミュレート |
| lib/pq | v1.10.9 | PostgreSQL ドライバー | 実績があり、ネイティブな PostgreSQL サポート |
| Docker | — | コンテナ化 | 再現可能なビルドと分離された環境 |
| メソッド | ルート | 目的 |
|---|---|---|
| GET | /health | ヘルスチェック — {status: OK} を返す |
| POST | /api/v1/reward | リワード作成 — 冪等、トランザクション対応、手数料計算 |
| POST | /api/v1/adjustments/:id | 調整の適用 — 行ロック、タイプ別の台帳エントリ |
| GET | /api/v1/today-stocks/:userId | today_rewards ビューからの本日のリワード |
| GET | /api/v1/historical-inr/:userId | 過去の INR 評価額 — ページネーション、イベント乗算 |
| GET | /api/v1/stats/:userId | 統計サマリー — 本日のリワードとポートフォリオ総額 |
| GET | /api/v1/portfolio/:userId | ポートフォリオ詳細 — ページネーション、上場廃止フィルタ |
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| username | VARCHAR(50) | NOT NULL、UNIQUE |
| VARCHAR(100) | NOT NULL、UNIQUE | |
| created_at | TIMESTAMP | デフォルト: NOW() |
ユーザーアカウント。テストユーザー 1 件がシードされます。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| user_id | INT | FK → users(id) ON DELETE CASCADE |
| stock_symbol | VARCHAR(20) | NOT NULL |
| quantity | NUMERIC(18,6) | NOT NULL、負数は取消 |
| idempotency_key | VARCHAR(64) | UNIQUE、NULL 可 |
| reward_date | DATE | GENERATED ALWAYS AS (created_at::DATE) STORED |
(user_id, stock_symbol, reward_date) の一意制約と大文字小文字を区別しないバリアントを持つ中核のリワードイベント。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| reward_id | INT | FK → rewards(id) ON DELETE CASCADE |
| entry_type | ledger_entry_type | ENUM: stock_units、inr_outflow、fees、brokerage_fee、stt_fee、gst_fee |
| stock_symbol | VARCHAR(20) | NULL 可 |
| quantity | NUMERIC(18,6) | NULL 可 |
| amount | NUMERIC(18,4) | NULL 可 |
複式簿記形式の台帳。各リワードで 2〜5 件のエントリが生成されます。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| stock_symbol | VARCHAR(20) | UNIQUE |
| price | NUMERIC(18,4) | 現在価格 |
| updated_at | TIMESTAMP | PriceService が更新 |
銘柄ごとの最新価格。バックグラウンドの PriceService が更新します。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| stock_symbol | VARCHAR(20) | NOT NULL |
| price | NUMERIC(18,4) | NOT NULL |
| date | DATE | NOT NULL、stock_symbol との複合 UNIQUE |
過去評価額用の日次価格スナップショット。PriceService がアップサートします。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| stock_symbol | VARCHAR(20) | NOT NULL |
| event_type | stock_event_type | ENUM: split、bonus、merge、delist |
| ratio_num | NUMERIC(18,6) | 乗数の分子 |
| ratio_den | NUMERIC(18,6) | 乗数の分母 |
| new_symbol | VARCHAR(20) | 合併用 |
| effective_date | TIMESTAMP | NOT NULL |
コーポレートアクション。ratio_num/ratio_den が乗数を定義します(例: 2:1 分割)。
| カラム | 型 | 備考 |
|---|---|---|
| idPK | SERIAL | 自動生成 |
| reward_id | INT | FK → rewards(id) ON DELETE CASCADE |
| adjustment_type | VARCHAR(64) | reward_reversal、fee_refund、manual_correction |
| delta_quantity | NUMERIC(18,6) | NOT NULL |
| delta_amount | NUMERIC(18,4) | NOT NULL |
| reason | VARCHAR(255) | NULL 可 |
行レベルロックを用いた既存リワードの変更。
1 対多: users→rewards(CASCADE)、rewards→ledger(CASCADE)、rewards→adjustments(CASCADE)。暗黙の相互参照: rewards.stock_symbol ↔ stock_prices.stock_symbol(UPPER() 経由)、rewards.stock_symbol ↔ stock_events.stock_symbol(イベント乗数)。rewards と stock_prices/stock_events の間に正式な FK はなく、ビューレベルで強制されます。
判断: 複雑なポートフォリオ/過去計算をアプリケーションコードではなく PostgreSQL ビューとして実装
理由: アプリケーションの複雑さを軽減し、PostgreSQL クエリオプティマイザを活用して計算の単一情報源を実現
利点: 全エンドポイントで一貫した計算が行われ、DB が JOIN と集計を効率的に処理
トレードオフ: 大規模データセットではビューが遅くなる可能性があり、アプリコードよりデバッグが難しく、PostgreSQL に依存
判断: 累積コーポレートイベント乗数の計算に EXP(SUM(LN(ratio))) を使用
理由: 多数のイベントが複合した際の浮動小数点オーバーフロー/アンダーフローを回避
利点: 数値的に安定し、任意の数のイベントを処理可能
トレードオフ: 直接乗算より読みにくく、正の比率が必要
判断: ビジネスロジックをサービス層に分離せず、ハンドラーに直接配置
理由: 小規模プロジェクトではシンプルで、保守するファイルと抽象化が少ない
利点: 定型コードが少なく、リクエストフローを追跡しやすい
トレードオフ: ロジックの再利用が難しく、HTTP とデータアクセスの結合が強く、ビジネスロジックの独立テストが困難
判断: 実際の市場データ API ではなく RandomPriceFetcher を使用
理由: 開発/テスト環境であり、API キー管理が不要
利点: 外部依存がゼロで、ほぼ決定的な挙動
トレードオフ: 本番利用には不向きで、ポートフォリオ評価額に意味がなく、実運用には置き換えが必要
判断: 外側のトランザクションを中断せずに冪等性を処理するため PostgreSQL SAVEPOINT を使用
理由: 外部の冪等性サービスなしでリトライ安全性を確保
利点: PostgreSQL の制約内で動作する高度なトランザクション内冪等性
トレードオフ: 複雑なエラーハンドリングと SAVEPOINT のオーバーヘッド
判断: 全エンドポイントが認証なしで公開
理由: 開発/デモ用プロジェクトで、認証は後から追加予定(例: API ゲートウェイ経由)
利点: 実装がシンプルでテストが容易
トレードオフ: 本番には不向きで、任意のクライアントがリワード作成や調整適用を行える