The Order Service is a NestJS (TypeScript) microservice responsible for the complete order lifecycle — from intake and validation through risk/wallet checks to publishing commands for the matching engine and processing execution reports.
Order Intake & Validation : Accept order requests via REST API, validate against instrument rules and schema constraints.
Risk & Session Checks : Call Risk Service for pre-trade checks (leverage, margin, exposure limits).
Wallet Integration : Call Wallet Service to lock margin before accepting orders, unlock on cancel/reject, settle on fill.
Market Order Pricing : Call Market Data Service for last-traded price when sizing market orders.
Lifecycle Persistence : Persist every order state transition to PostgreSQL with a full event-sourced audit trail (order_events table).
Command Publishing : Publish order.command.v1 to Kafka for the matching engine via a transactional outbox.
Execution Processing : Consume engine.event.v1 from the matching engine and update order states. Per-engine-sequence deduplication via Redis.
Idempotency : Deduplicate order submissions via idempotency_key (Redis 24h TTL + DB unique constraint).
gRPC Query Surface : Serve internal queries — GetOrdersByUser, ListOrderByUserIds, CancelAllOrdersForPool — on port 50060 for other services.
The Order Service is the gateway between the client and the matching engine. It ensures orders are valid, funded, and risk-checked before entering the order book.
graph TB
Client[Client App] -->|REST| OrderAPI[Order Service API]
OrderAPI -->|gRPC PreTradeCheck| Risk[Risk Service]
OrderAPI -->|gRPC LockFunds / SettleTrade| Wallet[Wallet Service]
OrderAPI -->|gRPC Instrument lookup| Metadata[Metadata Service]
OrderAPI -->|gRPC LTP for market orders| MarketData[Market Data Service]
OrderAPI -->|order.command.v1| ME[Matching Engine]
ME -->|engine.event.v1| OrderAPI
OrderAPI -->|Stores| PG[(PostgreSQL)]
OrderAPI -->|Idempotency · dedup · cache| Redis[(Redis)]
Other[Other Services] -->|gRPC OrderQueryService :50060| OrderAPI
Field Type Description idUUID (PK) Account ID user_idUUID Owner user account_typeENUM individual, institutional, system_market_makerstatusENUM active, suspended, closedcreated_atTIMESTAMPTZ Creation time
Field Type Description idUUID (PK) Instrument ID symbolVARCHAR(32) UNIQUE Trading symbol (e.g., BTCUSDT-PERP) base_currencyVARCHAR(10) Base asset quote_currencyVARCHAR(10) Quote asset tick_sizeNUMERIC(24) Minimum price increment lot_sizeNUMERIC(24) Minimum quantity increment min_notionalNUMERIC(24) Minimum order notional value max_leverageINT Maximum allowed leverage statusENUM active, halted, delisted
Field Type Description idUUID (PK) Order ID client_order_idTEXT Client-provided tracking ID account_idUUID (FK) References accounts instrument_idUUID (FK) References instruments sideENUM(buy, sell) Order side typeENUM(limit, market, stop_limit, stop_market) Order type time_in_forceENUM(gtc, ioc, fok, gtd) Time-in-force policy priceNUMERIC(24) Limit price (NULL for market) stop_priceNUMERIC(24) Stop trigger price quantityNUMERIC(24) Original quantity filled_qtyNUMERIC(24) Cumulative filled quantity remaining_qtyNUMERIC(24) Remaining to fill avg_fill_priceNUMERIC(24) Volume-weighted average fill price leverageINT Leverage multiplier (1-100) post_onlyBOOLEAN Reject if would take liquidity reduce_onlyBOOLEAN Only reduce existing position statusENUM Order state machine value idempotency_keyTEXT UNIQUE Deduplication key fee_currencyVARCHAR(10) Currency for fees expires_atTIMESTAMPTZ Expiry for GTD orders created_atTIMESTAMPTZ Order creation time updated_atTIMESTAMPTZ Last status change
Field Type Description idUUID (PK) Event ID order_idUUID (FK) References orders event_typeTEXT created, accepted, partially_filled, filled, canceled, rejected, replacedprev_statusTEXT Previous order status new_statusTEXT New order status filled_qtyNUMERIC(24) Fill quantity for this event fill_priceNUMERIC(24) Fill price for this event trade_idUUID Trade ID (for fill events) reasonTEXT Reason for reject/cancel metadataJSONB Additional event context trace_idTEXT OpenTelemetry trace ID created_atTIMESTAMPTZ Event timestamp
Transactional outbox table for reliable Kafka publishing:
Field Type Description idUUID (PK) Outbox entry ID topicTEXT Target Kafka topic keyTEXT Kafka message key payloadJSONB Serialized message publishedBOOLEAN Whether successfully published created_atTIMESTAMPTZ Entry creation time published_atTIMESTAMPTZ When published
Key Pattern Purpose TTL order:idem:{key}Idempotency deduplication 24 hours order:rate:{account_id}Per-account rate limiter Sliding window instrument:{id}Cached instrument definitions 5 min
new --> accepted --> partially_filled --> filled
rejected cancel_pending filled
accepted --> replace_pending --> replaced
filled / canceled --> expired
From To Trigger newacceptedMatching engine acknowledges order newrejectedValidation failure, risk check failure, or insufficient funds acceptedpartially_filledPartial fill from matching engine acceptedfilledComplete fill from matching engine acceptedcancel_pendingCancel request submitted partially_filledfilledRemaining quantity filled partially_filledcancel_pendingCancel remaining cancel_pendingcanceledCancel confirmed by engine acceptedreplace_pendingReplace request submitted replace_pendingreplacedReplace confirmed by engine filled / canceledexpiredGTD expiry reached
All POST / PUT / DELETE endpoints return HTTP 202 Accepted with { orderId, status, message } — order processing is asynchronous via the outbox + matching engine.
Method Endpoint Description POST/v1/ordersCreate new order (202) GET/v1/ordersList orders (paginated / filterable / cursor-based) GET/v1/orders/:idGet order by ID PUT/v1/orders/:idReplace / amend order (202) DELETE/v1/orders/:idCancel order (202)
GET /v1/orders supports: page, limit, status, type, symbol, fromDate, toDate, search, cursor. Results are ordered by updatedAt DESC and the list response is 60s cached in Redis (counts 5 min).
Method Endpoint Description GET/v1/tradesList all trades for the authenticated user GET/v1/trades/:symbolList trades for a specific symbol
Method Endpoint Description GET/healthHealth check (no auth) — /admin/*Operational endpoints (instrument refresh, etc.) — /docs, /docs-jsonSwagger UI + OpenAPI spec
All authenticated endpoints require headers set by the upstream API gateway after JWT validation:
Header Required Purpose x-user-idyes User UUID x-account-idyes Trading account UUID x-sourceno Request source: api (default), web, mobile, broker, market_maker x-idempotency-keyno Deduplication key for write operations
Create Order Request (POST /v1/orders — CreateOrderDto, validated with class-validator):
"symbol" : " BTCUSDT-PERP " ,
"clientOrderId" : " my-order-1 " ,
"idempotencyKey" : " unique-key-123 " ,
All financial fields are decimal strings (never JSON numbers) per the platform’s precision rules.
Response Format (List):
"data" : [ /* orders */ ],
"pagination" : { "page" : 1 , "limit" : 50 , "total" : 142 }
Topic Description order.command.v1Order commands (new, cancel, replace) sent to matching engine
Topic Description engine.event.v1Execution reports from matching engine (fills, accepts, rejects, cancels)
Order Service also serves an inbound gRPC API used by other internal services. Service name: OrderQueryService.
RPC Purpose Called By GetOrdersByUser(user_id, account_id)All orders for one user / account Risk, Settlement ListOrderByUserIds(user_ids[])Batch lookup across users Risk, Reporting CancelAllOrdersForPool(pool_id, allocation_id, trace_id)Bulk-cancel for a market-maker pool Market Maker Service
Service gRPC Package Purpose Risk Service risk.v1PreTradeCheck — leverage / margin / exposure validationWallet Service wallet.v1LockFunds, UnlockFunds, SettleTrade — margin lifecycleMetadata Service metadata.v1Instrument lookup — tick size, lot size, min notional, fee schedule Market Data Service marketdata.v1Last-traded price for sizing market orders
TLS is enabled automatically for non-localhost connections.
{ "name" : " command_type " , "type" : " string " },
{ "name" : " order_id " , "type" : " string " },
{ "name" : " account_id " , "type" : " string " },
{ "name" : " symbol " , "type" : " string " },
{ "name" : " side " , "type" : " string " },
{ "name" : " order_type " , "type" : " string " },
{ "name" : " quantity " , "type" : " string " },
{ "name" : " price " , "type" : [ " null " , " string " ] },
{ "name" : " time_in_force " , "type" : " string " },
{ "name" : " trace_id " , "type" : " string " }
Auth : Extract x-user-id, x-account-id, x-source from headers (set by API Gateway after JWT validation)
Idempotency Check : Check idempotencyKey in Redis (24h TTL) → if hit, return cached response
DTO Validation : NestJS ValidationPipe runs class-validator decorators on CreateOrderDto
Instrument Lookup : Fetch instrument from Metadata Service (gRPC, cached in Redis 5 min)
Business Validation : Check quantity tick/lot size, min notional, price precision, leverage limits
Market Price Lookup (market orders only): MarketDataService.GetMarkPrice for sizing
Risk Check : RiskService.PreTradeCheck via gRPC (leverage, margin, exposure)
Wallet Lock : WalletService.LockFunds via gRPC (reserve margin)
Persist : Insert orders row (status = new) + order_events row + outbox row in a single transaction
Publish : Outbox relay polls and publishes order.command.v1 to Kafka
Response : Return HTTP 202 with the order in new status
When engine.event.v1 arrives:
Deduplicate by engine sequence number (Redis)
Parse event (ORDER_ACCEPTED, TRADE, ORDER_CANCELED, ORDER_REJECTED, BOOK_DELTA — last one ignored)
Load order from DB
Apply state transition via the state-machine service
For fills: call WalletService.SettleTrade() to apply realized P&L and fees
Insert order_events row (audit trail)
Update order status, filled_qty, avg_fill_price
If fully filled: update status to filled
Quantity : Must be positive, divisible by lot_size, does not exceed instrument max
Price : Must be positive (for limit), divisible by tick_size
Notional : price * quantity >= min_notional
Leverage : 1 <= leverage <= instrument.max_leverage
Time-in-Force : Market orders must be ioc or fok; limit orders can be gtc, ioc, fok, gtd
GTD Expiry : expires_at must be in the future (for GTD orders)
Post-Only : Only valid for limit orders
Reduce-Only : Validated against current position
Per-Account : 50 requests/second per account (configurable)
Per-IP : 100 requests/second per IP
Implementation : Redis sliding window counter
order_created_total{side,type} — Orders created by side and type
order_filled_total{side} — Orders filled
order_rejected_total{reason} — Rejections by reason
order_canceled_total — Cancellations
order_latency_ms{stage} — Latency per processing stage
outbox_pending_gauge — Pending outbox messages
Spans: order.create, order.validate, order.risk_check, order.wallet_lock, order.persist, order.publish, order.process_fill
Structured JSON: order_id, account_id, instrument_id, side, type, status, trace_id. Prices and quantities are logged for debugging.
Scenario Handling Risk service unavailable Reject order with RISK_CHECK_UNAVAILABLE Wallet lock fails Reject order with INSUFFICIENT_BALANCE Kafka publish fails Outbox pattern retries; order stays in new until published Engine event processing fails Consumer retries with backoff; idempotent by trade_id DB transaction fails Automatic rollback; client retries with same idempotency key
Metric Target Order acceptance latency (p95) < 100 ms Outbox drain latency (p95) < 500 ms Fill processing latency (p95) < 200 ms Order creation success rate >= 99.5%
Variable Description Default POSTGRES_URLPostgreSQL connection string Required REDIS_URLRedis connection string Required KAFKA_BROKERSKafka broker addresses Required RISK_SERVICE_GRPC_URLRisk service gRPC endpoint Required WALLET_SERVICE_URLWallet service gRPC endpoint Required METADATA_SERVICE_URLMetadata service gRPC endpoint Required MARKETDATA_SERVICE_GRPC_URLMarket Data service gRPC endpoint Required ORDER_GRPC_PORTInbound gRPC server (OrderQueryService) 50060 RATE_LIMIT_PER_ACCOUNTMax requests per second per account 50
Language : TypeScript
Framework : NestJS (Fastify adapter)
ORM : Drizzle ORM
Database : PostgreSQL
Cache : Redis (idempotency, rate limiting, instrument cache, engine-seq dedup, list cache)
Messaging : Confluent Kafka with Avro serialization via Schema Registry
gRPC : @nestjs/microservices with gRPC transport
Validation : class-validator + class-transformer via NestJS ValidationPipe
OpenAPI : @nestjs/swagger (UI at /docs)
Decimal arithmetic : fraction.js
Package Manager : Bun