Order Processing Workflow
A production-quality example demonstrating how functional patterns solve real business problems.
Overview
The Order Processing Workflow is a comprehensive e-commerce example that processes customer orders through multiple stages: validation, inventory reservation, payment processing, shipment creation, and notification. It showcases how Higher-Kinded-J patterns handle complexity without sacrificing readability.
┌─────────────────────────────────────────────────────────────────────────┐
│ ORDER PROCESSING PIPELINE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Request ──▶ Validate ──▶ Customer ──▶ Inventory ──▶ Discount │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Address? Exists? In Stock? Valid Code? │
│ Postcode? Eligible? Reserved? Loyalty Tier? │
│ │
│ ──▶ Payment ──▶ Shipment ──▶ Notification ──▶ Result │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Approved? Created? Sent? │
│ Funds? Carrier? (non-critical) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Key Patterns Demonstrated
Typed Error Hierarchies
Domain errors modelled as a sealed interface hierarchy for exhaustive pattern matching:
public sealed interface OrderError {
record ValidationError(List<FieldError> fieldErrors, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record CustomerError(String customerId, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record InventoryError(List<String> unavailableProducts,
ErrorEnvelope<OrderErrorContext> envelope) implements OrderError {}
record DiscountError(Optional<String> promoCode, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record PaymentError(Optional<String> transactionId, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record ShippingError(boolean recoverable, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record NotificationError(ErrorEnvelope<OrderErrorContext> envelope) implements OrderError {}
record SystemError(Optional<Throwable> cause, ErrorEnvelope<OrderErrorContext> envelope)
implements OrderError {}
record FieldError(String field, String message, @Nullable Object rejectedValue) {}
}
A hierarchy like this tends to grow the same code/message/timestamp/context components on every variant. @GenerateErrorEnvelope generates that envelope and types the context; the book's running example there is exactly an OrderError.
For → toState → ForState Comprehension
The workflow uses For to gather initial values, then bridges to ForState via toState() for named field access through the remaining steps:
public EitherPath<OrderError, OrderResult> process(OrderRequest request) {
var orderId = OrderId.generate();
var customerId = new CustomerId(request.customerId());
MonadError<EitherKind.Witness<OrderError>, OrderError> monad = Instances.monadError(either());
Kind<EitherKind.Witness<OrderError>, OrderResult> result =
// Phase 1 (Gather): accumulate address, customer, order via For
For.from(monad, lift(validateShippingAddress(request.shippingAddress())))
.from(addr -> lift(lookupAndValidateCustomer(customerId)))
.from(t -> lift(buildValidatedOrder(orderId, request, t._2(), t._1())))
// Bridge: construct named state from gathered values
.toState((address, customer, order) ->
ProcessingState.initial(address, customer, order))
// Phase 2 (Enrich): named field access via ForState + lenses
.fromThen(s -> lift(reserveInventory(s.order().orderId(), s.order().lines())),
ProcessingStateLenses.reservation())
.fromThen(s -> lift(applyDiscounts(s.order(), s.customer())), ProcessingStateLenses.discount())
.fromThen(s -> lift(processPayment(s.order(), s.discount())), ProcessingStateLenses.payment())
.fromThen(s -> lift(createShipment(s.order(), s.address())), ProcessingStateLenses.shipment())
.fromThen(s -> lift(sendNotifications(s.order(), s.customer(), s.discount())),
ProcessingStateLenses.notification())
.yield(OrderWorkflow::toOrderResult);
return Path.either(EITHER.narrow(result));
}
After toState(), every value is accessed by name (s.order(), s.customer(), s.discount()) rather than by tuple position (t._3(), t._2(), t._5()).
Resilience Patterns
Retry and timeout combinators chain onto the path itself. The committing half of the workflow is never retried, because a payment that already went through is not safe to repeat:
// Retry only the idempotent pre-flight reads; one timeout bounds the whole retry loop.
EitherPath<OrderError, Unit> preflight =
EitherPath.withTimeout(
() -> toEitherPath(
Path.io(() -> runPreflight(request)).withRetry(retryPolicy),
"ConfigurableOrderWorkflow.preflight"),
preflightTimeout,
() -> OrderError.SystemError.timeout(
"ConfigurableOrderWorkflow.preflight", preflightTimeout));
Focus DSL Integration
Immutable state updates using generated lenses:
// Replace a validated order's payment method
ValidatedOrder updated = ValidatedOrderFocus.paymentMethod().set(newMethod, order);
// Add one to every line's quantity
ValidatedOrder adjusted = ValidatedOrderFocus.lines()
.via(ValidatedOrderLineFocus.quantity())
.modifyAll(qty -> qty + 1, order);
Source Files
| File | Description | Run Command |
|---|---|---|
| OrderWorkflowDemo.java | Main demo runner | ./gradlew :hkj-examples:run -PmainClass=org.higherkindedj.example.order.runner.OrderWorkflowDemo |
| EnhancedOrderWorkflowDemo.java | Demo with concurrency | ./gradlew :hkj-examples:run -PmainClass=org.higherkindedj.example.order.runner.EnhancedOrderWorkflowDemo |
| OrderWorkflow.java | Core workflow | View source |
| ConfigurableOrderWorkflow.java | Feature flags and resilience | View source |
| EnhancedOrderWorkflow.java | VTask concurrency patterns | View source |
Domain Model
| File | Description |
|---|---|
| OrderError.java | Sealed error hierarchy |
| OrderRequest.java | Input request model |
| ValidatedOrder.java | Post-validation model |
| OrderResult.java | Workflow result |
Services
| File | Description |
|---|---|
| CustomerService.java | Customer lookup |
| InventoryService.java | Stock reservation |
| PaymentService.java | Payment processing |
| ShippingService.java | Shipment creation |
Extended Workflows
| File | Description |
|---|---|
| PartialFulfilmentWorkflow.java | Handling partial inventory |
| SplitShipmentWorkflow.java | Multi-warehouse shipping |
| OrderCancellationWorkflow.java | Cancellation with rollback |
Project Structure
hkj-examples/src/main/java/org/higherkindedj/example/order/
├── config/
│ └── WorkflowConfig.java # Feature flags and configuration
├── context/
│ └── OrderContext.java # Execution context
├── error/
│ └── OrderError.java # Sealed error hierarchy
├── model/
│ ├── OrderRequest.java # Input models
│ ├── ValidatedOrder.java # Domain models
│ ├── OrderResult.java # Result types
│ └── value/ # Value objects (Money, OrderId, etc.)
├── runner/
│ ├── OrderWorkflowDemo.java # Main runner
│ └── EnhancedOrderWorkflowDemo.java
├── service/
│ ├── CustomerService.java # Service interfaces
│ ├── InventoryService.java
│ └── impl/ # In-memory implementations
└── workflow/
├── OrderWorkflow.java # Core workflow
├── ConfigurableOrderWorkflow.java
├── EnhancedOrderWorkflow.java
└── FocusDSLExamples.java # Focus DSL usage
Related Documentation
- Order Walkthrough – Step-by-step guide to the workflow
- Effect Composition – Detailed pattern explanations
- Production Patterns – Feature flags, retries, and configuration
- Concurrency and Scale – VTask, Scope, and structured concurrency
Next: Draughts Game