iToverDose/Software· 2 MAY 2026 · 00:09

Building a Robust Order API for Trading Systems: Key Design Choices

Learn how to design an Order API that orchestrates trading workflows across multiple services using Kafka, RabbitMQ, and Spring Boot. Explore validation logic, event-driven architectures, and real-world implementation insights.

DEV Community4 min read0 Comments

In modern trading platforms, the Order API isn’t just another microservice—it’s the central nervous system that connects user intent with market execution. When a trader submits a buy or sell order, this component validates every detail, routes the request to the exchange, and broadcasts the outcome across the entire system.

In our ongoing series about building a brokerage platform for Brazil’s B3 exchange, we’ve developed individual components like price synchronization, the matching engine, financial custody, and asset catalogs. Now, we’re focusing on the critical service that ties everything together: the Broker Order API.

What Does the Order API Do?

The trading-broker-order service serves as the single entry point for all trading activity. Its primary responsibility is to manage the complete lifecycle of an order—from initial submission to final execution confirmation. This includes validating user eligibility, checking asset availability, routing orders to the B3 matching engine, and notifying all subsystems of status changes.

To accomplish this, the service integrates with four key components:

  • Asset API (REST/Feign) – Confirms the ticker exists and is tradable
  • Wallet API (REST/Feign) – Verifies the user has sufficient balance
  • B3 Matching Engine (RabbitMQ) – Forwards orders for execution
  • Broker Wallet (Kafka) – Publishes lifecycle events to the ecosystem

End-to-End Order Flow Explained

Here’s how a typical order travels through the system:

  1. A user submits a POST request to /api/v1/orders with details like user ID, ticker, quantity, and side (BUY/SELL)
  2. The Order API:
  • Validates the ticker via Asset API
  • Checks wallet balance via Wallet API
  • Persists the order in MySQL with status PENDING
  • Publishes the order to RabbitMQ for B3 execution
  • Notifies the system via Kafka event order-events-v1
  1. B3’s matching engine processes the order and returns a status (FILLED or REJECTED) via RabbitMQ
  2. The Order API:
  • Updates the order status in MySQL
  • Publishes the final Kafka event
  • The Wallet service reacts by blocking, liquidating, or reversing funds

This design uses two distinct messaging patterns: synchronous REST calls for validation and asynchronous RabbitMQ/Kafka for event propagation.

Technology Stack Behind the Implementation

The Order API leverages modern Java and Spring technologies:

  • Java 21 + Spring Boot 3.3.5 – Core runtime and framework
  • MySQL + Flyway – Persistent storage with schema migration
  • Spring Kafka – Lifecycle event publishing
  • Spring AMQP – RabbitMQ integration for order routing
  • Spring Cloud OpenFeign – REST client for Asset and Wallet APIs
  • SpringDoc OpenAPI – Interactive Swagger documentation

Core Implementation Principles

1. Domain Model: The Order Entity

The Order class models the state of a trading order at any point in its lifecycle:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String userId;
    private String ticker;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal quantity;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal price;

    @Column(precision = 19, scale = 4)
    private BigDecimal executedPrice; // Null until B3 responds

    @Enumerated(EnumType.STRING)
    private OrderSide side; // BUY | SELL

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // PENDING | FILLED | REJECTED | CANCELLED

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    public OrderEventDTO mapToEvent() {
        return OrderEventDTO.builder()
            .orderId(this.getId())
            .userId(this.getUserId())
            .ticker(this.getTicker())
            .quantity(this.getQuantity())
            .price(this.getPrice())
            .executedPrice(this.getExecutedPrice())
            .side(this.getSide().name())
            .status(this.getStatus().name())
            .eventTimestamp(LocalDateTime.now())
            .build();
    }
}

A key design choice is making executedPrice nullable. Since the actual execution price comes from B3 only after order submission, enforcing non-null at creation would break domain integrity.

2. Ticker Validation: Asset Feign Client

Invalid tickers must be rejected early to prevent wasted processing. The AssetClient uses Feign to call the Asset API:

@FeignClient(name = "trading-broker-asset", url = "${app.services.asset-url}")
public interface AssetClient {
    @GetMapping("/api/v1/assets/{ticker}")
    AssetDTO getByTicker(@PathVariable("ticker") String ticker);
}

Validation logic in OrderService:

  • Throws RuntimeException if ticker is inactive or not found
  • Differentiates between business errors (FeignException.NotFound) and technical failures (timeouts, 5xx)
  • Returns user-friendly messages for each failure type

This ensures traders receive immediate feedback on invalid inputs rather than silent rejections later in the flow.

3. Balance Validation: Wallet Feign Client

For buy orders, the system must confirm the user has enough funds. The WalletClient is similarly structured:

@FeignClient(name = "trading-broker-wallet", url = "${app.services.wallet-url}")
public interface WalletClient {
    @GetMapping("/api/v1/wallets/summary")
    WalletSummaryDTO getWalletSummary(@RequestParam("userId") String userId);
}

The validation logic compares requested quantity against available balance, blocking the amount immediately when the order is created.

4. Event-Driven Communication Patterns

The system uses two messaging paradigms:

  • RabbitMQ for real-time order routing to B3’s matching engine
  • Kafka for durable, replayable order lifecycle events

Each state change triggers a Kafka event, allowing downstream services like the wallet to react appropriately—whether by freezing funds, completing settlements, or reversing failed trades.

Lessons from Production-Grade Trading Systems

Building an Order API isn’t just about connecting services—it’s about designing for failure, consistency, and transparency. Every component must gracefully handle network timeouts, invalid inputs, and exchange rejections while maintaining a clear audit trail.

As trading platforms evolve toward real-time settlement and fractional trading, APIs like this will need to support atomic operations across multiple ledgers and regulatory jurisdictions. The architecture we’ve outlined provides a solid foundation for such scalability.

The next step? Integrating risk management rules, compliance checks, and multi-exchange routing—each adding another layer of validation and coordination to this already complex system.

As always, the devil is in the details—and in trading systems, those details are measured in milliseconds and cents.

AI summary

Erfahren Sie, wie Sie eine robuste Order-API für Handelsplattformen mit Java, Spring Boot und asynchroner Kommunikation entwickeln. Optimieren Sie Order-Lebenszyklen und steigern Sie die Effizienz.

Comments

00
LEAVE A COMMENT
ID #87GN6K

0 / 1200 CHARACTERS

Human check

9 + 5 = ?

Will appear after editor review

Moderation · Spam protection active

No approved comments yet. Be first.