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.
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/orders
Create new order (202)
GET
/v1/orders
List orders (paginated / filterable / cursor-based)
GET
/v1/orders/:id
Get order by ID
PUT
/v1/orders/:id
Replace / amend order (202)
DELETE
/v1/orders/:id
Cancel 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).
A global JwtAuthGuard (apps/order-service/src/auth/) verifies Authorization: Bearer <token>
against auth-service’s JWKS on every route except @Public() ones (health, metrics, docs). Identity
(userId, accountId) comes only from the verified principal:
user token: userId/accountId = the token’s sub/account_id. A forged x-user-id/
x-account-id that disagrees with the token → 403 IDENTITY_MISMATCH.
service token with scope orders:on_behalf (market-maker, game-service): may act for another
user by sending x-user-id/x-account-id, which are honored only in this case.
/v1/admin/* and the gRPC OrderQueryService require role admin or service.
Header
Required
Purpose
authorization
yes
Bearer <token> — user or service JWT
x-user-id / x-account-id
only for service on-behalf calls
Target user/account; ignored (and mismatch-checked) for a plain user token
x-source
no
Request source: api (default), web, mobile, broker, market_maker (the latter only accepted from svc:market-maker)
Auth: JwtAuthGuard verifies the Authorization: Bearer token via auth-service JWKS; userId/accountId come from the principal (or from x-user-id/x-account-id only for a service principal with orders:on_behalf), x-source read separately
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
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: settlement is applied asynchronously — position-service computes realized P&L and fees and emits settlement.v1, which wallet-service consumes (no gRPC settle call from order-service)