Skip to content

Design Patterns

tien.nguyen edited this page Jul 30, 2026 · 1 revision

Software Design Patterns in Emporia

The Emporia Trading Platform incorporates enterprise architectural and object-oriented design patterns to ensure low latency, high throughput, maintainability, and strict concurrency safety.


🏛️ Architectural Patterns

1. API Gateway Pattern

  • Implementation: gateway module using Spring Cloud Gateway.
  • Role: Single entry point for external client traffic (:8082). Handles OIDC authentication header injection, CORS configuration, path-based routing (/api/static/**, /api/orders/**, /api/market-data/**), and rate limiting.

2. Event-Driven Architecture (EDA)

  • Implementation: trading-contracts, order-command-service, order-management-service, execution-service.
  • Role: Asynchronous, non-blocking communication using Apache Kafka topics (emporia.order.commands.v1, emporia.order.results.v1, emporia.orders.v1, emporia.execution.commands.v1). Decouples command issuance from state persistence and execution routing.

3. Command Query Responsibility Segregation (CQRS)

  • Implementation:
    • Command Path: order-command-service receives POST /api/orders commands, validates them, and produces immutable Kafka commands.
    • Query Path: order-management-service maintains read-optimized database models (TradingOrder) queried via GET /api/orders/{id} and GET /api/orders.

4. Database-per-Service Pattern

  • Implementation: Separate PostgreSQL databases (emporia_authorisation, emporia_static_data, emporia_client_config, emporia_order_data, emporia_portfolio).
  • Role: Ensures strict domain boundary isolation, preventing tight database coupling and allowing independent service scaling.

5. Choreography-based Saga Pattern

  • Implementation: Distributed order lifecycle processing across Kafka topic channels without a centralized orchestrator.
  • Role: OrderCommandService $\rightarrow$ OrderManagementService $\rightarrow$ ExecutionService $\rightarrow$ OrderManagementService $\rightarrow$ PortfolioService. Each service reacts to events and publishes state transitions.

🧩 Behavioral & Structural Software Patterns

Pattern Location / Class Architectural Role & Purpose
Strategy Pattern execution-service (OrderRoutingStrategy, SmartRoutingStrategy, VwapRoutingStrategy) Dynamically selects order routing logic based on order type, size, and market execution conditions.
State Machine Pattern order-management-service (TradingOrder) Enforces valid status transitions (PENDING_NEW $\rightarrow$ LIVE $\rightarrow$ PARTIALLY_FILLED $\rightarrow$ FILLED / CANCELLED / REJECTED) and mathematical quantity invariants.
Observer Pattern market-data-service (QuoteAggregator, MarketDataSseController) Broadcasts streaming tick updates and aggregated depth updates to connected client subscribers via HTTP SSE and gRPC streams.
Adapter Pattern execution-service (ExchangeCoreAdapter) Bridges Emporia's Kafka order events to the in-memory LMAX Disruptor exchange-core matching engine protocol.
Facade Pattern order-command-service (StaticDataClient) Provides a simplified fluent RestClient interface to query instrument snapshots from static-data-service.
Decorator Pattern market-data-service (ResilientMarketDataProvider) Wraps external IEX / FIX market data sources with fallback logging, retry mechanisms, and rate limit guards.

🔄 Synchronized State Machine Transitions

The TradingOrder entity in order-management-service implements thread-safe state machine transitions:

public synchronized void applyFill(BigDecimal fillQuantity, BigDecimal fillPrice) {
    require(status == OrderStatus.LIVE || status == OrderStatus.PARTIALLY_FILLED
                    || status == OrderStatus.CANCELLED,
            "Only active or cancelled orders can receive fills");
    
    // Recalculate traded quantity, remaining quantity, and volume-weighted average price
    BigDecimal newTradedQuantity = tradedQuantity.add(fillQuantity);
    tradedQuantity = newTradedQuantity;
    remainingQuantity = quantity.subtract(newTradedQuantity);
    averageTradePrice = newTradeValue.divide(newTradedQuantity, 6, RoundingMode.HALF_UP);
    
    if (remainingQuantity.signum() == 0) {
        status = OrderStatus.FILLED;
    } else if (status != OrderStatus.CANCELLED) {
        status = OrderStatus.PARTIALLY_FILLED;
    }
    validateInvariants();
}
  • Thread Safety: All state-modifying methods (applyFill, requestCancel, confirmCancel, reject) use synchronized monitors.
  • Invariant Enforcement: validateInvariants() guarantees that tradedQuantity + remainingQuantity == quantity at all times.
  • Late Fill Safety: Accepts late fills received from execution venues even after order cancellation.

Clone this wiki locally