-
Notifications
You must be signed in to change notification settings - Fork 12
Portfolio Service
tien.nguyen edited this page Jul 30, 2026
·
1 revision
The Portfolio Service is the internal balance accounting and risk-seed authority of the Emporia Trading Platform. It owns fully funded cash and equity balance tracking per client, manages pre-trade exchange risk seeding, and enforces strict HTTP idempotency contracts for exchange matching engine snapshot deliveries (exchange-core).
-
Directory:
portfolio-service -
HTTP Port:
8088(Internal service boundary; unrouted by public API gateway) -
Database Boundary:
emporia_portfolio(PostgreSQL) -
Primary Roles:
- Maintain client cash and equity asset balances in
portfolio_balance. - Supply client risk seed data for
exchange-corematching engine initialization. - Process durable exchange portfolio snapshot receipts with SHA-256 digest idempotency validation.
- Protect against concurrent balance replacement races via row-level pessimistic locking (
SELECT ... FOR UPDATE).
- Maintain client cash and equity asset balances in
sequenceDiagram
autonumber
participant ExchangeCore as Exchange-Core Simulator
participant PortfolioCtrl as PortfolioController (:8088)
participant ReceiptSvc as PortfolioReceiptService
participant DB as PostgreSQL (emporia_portfolio)
Note over ExchangeCore,PortfolioCtrl: Startup Phase: Risk Seed Retrieval
ExchangeCore->>PortfolioCtrl: GET /internal/v1/portfolios/{clientId}/risk-seed
PortfolioCtrl->>DB: Fetch client cash & asset positions
DB-->>PortfolioCtrl: Balances & Last Transaction ID
PortfolioCtrl-->>ExchangeCore: 200 OK (ValidatedPortfolioSnapshot)
Note over ExchangeCore,PortfolioCtrl: Snapshot Phase: Durable Receipt Delivery
ExchangeCore->>PortfolioCtrl: PUT /internal/v1/portfolio-snapshots/{deliveryId}/{clientId}<br/>Header: Idempotency-Key: {exchangeId}:{deliveryId}:{clientId}
PortfolioCtrl->>ReceiptSvc: process(deliveryId, clientId, idempotencyKey, body)
ReceiptSvc->>DB: Lock client portfolio_state (FOR UPDATE)
ReceiptSvc->>DB: Check idempotencyKey & SHA-256 digest
alt New Receipt
ReceiptSvc->>DB: Replace portfolio_balance rows & Insert portfolio_receipt
ReceiptSvc-->>PortfolioCtrl: Success (200 OK)
else Identical Redelivery
ReceiptSvc-->>PortfolioCtrl: Cached Receipt (204 No Content)
else Conflicting Digest for Same Key
ReceiptSvc-->>PortfolioCtrl: Idempotency Conflict (409 Conflict)
end
| Method | Path | Required Headers | Description |
|---|---|---|---|
GET |
/internal/v1/portfolios/{clientId}/risk-seed |
Authorization: Bearer <token> |
Returns current cash/asset balances and last transaction ID for exchange risk seeding. |
PUT |
/internal/v1/portfolio-snapshots/{deliveryId}/{clientId} |
Idempotency-Key: {exchangeId}:{deliveryId}:{clientId}Content-Type: application/json
|
Idempotently updates client balances from an exchange snapshot. |
To prevent duplicate balance replacements or lost updates when exchange instances redeliver snapshots:
-
Header Requirement: The caller must supply a unique
Idempotency-Keyheader (e.g.XOSR:1004:101). -
Transactional Storage: The raw JSON payload and its computed SHA-256 digest are persisted into the
portfolio_receipttable within the same transaction that updatesportfolio_balance. -
Redelivery Outcomes:
-
New Key: Replaces client available balances and inserts a new receipt (
200 OK). -
Same Key + Identical SHA-256: Recognizes identical redelivery and returns
204 No Contentwithout mutating database balances. -
Same Key + Different SHA-256: Detects payload tampering/conflict and throws
PortfolioIdempotencyConflictException(409 Conflict).
-
New Key: Replaces client available balances and inserts a new receipt (
| Table Name | Entity / Description | Key Columns |
|---|---|---|
portfolio_state |
Primary client account anchor |
client_id (PK), first_transaction_id, created_at, updated_at
|
portfolio_balance |
Asset balances per client |
client_id (FK), asset_id (e.g. USD cash or stock ID), available_balance
|
portfolio_receipt |
Idempotency log |
delivery_id (PK), client_id, idempotency_key, sha256_digest, body
|
-
Pessimistic Row Locking: During receipt delivery,
PortfolioReceiptServicelocks the client'sportfolio_staterow usingSELECT ... FOR UPDATE. - Race Condition Prevention: Concurrent deliveries of the same snapshot event execute sequentially, resulting in exactly one balance application and one clean duplicate response without racing balance updates.
-
Verified by Integration Specs: Validated using real PostgreSQL 16 containers (
TradingOrderPostgresConcurrencySpec&PortfolioReceiptPostgresSpec).
- Trading Terminology Glossary
- Order Lifecycle & Validation
- Order Routing & SOR
- Market Data & Pricing
- Portfolio & Risk Management
- Architecture & Order Flow
- Static Data Service
- Market Data Service
- Order Command Service
- Order Management Service (OMS)
- Execution Service
- Portfolio Service
- Design Patterns
- Microservices Overview
- Exchange-Core Integration
- Testing & Verification
- Deployment & Operations
- Repository: emporia
- Tech Stack: Java 21 | Spring Boot 4.0.7 | React 19 | Kafka | gRPC
- Coverage: 91.95% JaCoCo