Development
4 min readAdding an item to a cart requires coordinating price lookups, inventory checks, and offer calculations across multiple services. Handle this coordination poorly, and you end up with a distributed monolith: all the complexity of microservices, none of the benefits. The Cart Operation Service (CartOps) in Broadleaf Commerce solves this through a Provider pattern that keeps orchestration logic decoupled from service dependencies.
For Java architects and developers, understanding Providers is key to working effectively with Broadleaf's microservices ecosystem.
CartOps sits at the intersection of business logic and data access. As an orchestration-level service, it contains no persistence layer of its own. Its job is coordination: calling Resource Tier microservices like Pricing, Inventory, and Catalog to build and validate the shopping cart.
Keeping orchestration separate from resource services prevents the architecture from devolving into a web of point-to-point dependencies.
In a resource-tier service, the service layer talks directly to a repository layer (e.g., JPA). CartOps works differently, being an orchestration service. It must communicate directly with other services, and to manage this complexity while maintaining interface segregation, Broadleaf uses Providers.
A Provider is a service-layer component that formalizes a dependency on another service. It encapsulates all the boilerplate for inter-service communication, replacing what would otherwise be a repository and data layer. Providers execute service-to-service API calls, typically using Spring WebClient.
Providers also handle service-to-service authentication. Rather than scattering OAuth2 token management across your codebase, the Provider encapsulates credential fetching and header injection so that business logic stays clean.
Consider the CatalogProvider interface:
public interface CatalogProvider<P extends CatalogItem> {
P retrieveCatalogItem(CatalogItemRequest catalogItemRequest,
PriceContext priceContext,
ContextInfo contextInfo);
// ...
}
The interface declares intent without specifying implementation. Core logic uses this Provider interface and doesn't know whether the implementation (e.g., ExternalCatalogProvider) makes a REST call to the Catalog Microservice or fetches data from a legacy system. The design enables swapping implementations without touching business logic.
Tracing Add To Cart shows how these pieces connect:
Each step delegates to a Provider interface, so any of these dependencies can be replaced without modifying the coordination logic.
To integrate a different service, you can implement the corresponding Provider interface and register your implementation as a Spring Bean. Since we use @ConditionalOnMissingBean in the framework, this lets your custom Provider supersede the default, like this:
@Bean
InventoryProvider inventoryProvider() {
return new EmptyInventoryProvider();
// Replaces ExternalInventoryProvider if no inventory checks are needed
}
Need to call a third-party inventory system instead of Broadleaf's Inventory Service? Implement InventoryProvider, register your bean, and CartOps picks it up automatically.
Checkout uses a Workflow composed of atomic, self-contained Activities executed in sequence.
The ordering is deliberate:
Front-Load Validation: Validation activities run first. Pricing consistency, payment method eligibility, and similar checks happen before any state changes. Failures surface immediately.
State Mutation Last: Operations that modify state elsewhere (inventory soft reservations, payment authorization) run late in the flow. Fewer state changes mean less to roll back when something fails.
When a checkout activity fails after state-mutating steps have run, the system throws a CheckoutWorkflowActivityException and fires a CheckoutRollbackEvent. The platform's messaging layer (Spring Cloud Stream/Kafka) handles this event asynchronously.
Why async? Synchronous compensating transactions would block the customer's response while unwinding each step. Offloading rollback to an event-driven process keeps checkout latency predictable even during failures.
The tradeoff here is delayed consistency. Inventory may briefly show as reserved until the rollback event processes. Sub-second rollback latency makes this acceptable for most commerce scenarios.
CartOps demonstrates how to build complex business logic on microservices without sacrificing flexibility. Providers keep coordination decoupled from implementation. The Workflow/Activity pattern makes checkout sequencing explicit. Async rollback prioritizes customer experience over strict synchronous consistency.
When evaluating commerce platforms, the real question is whether customizing checkout and cart behavior requires forking core code or just registering a new bean. Broadleaf's Provider pattern makes extension a configuration concern.