Prerequisites
Core Concepts
Order Parties
Parties represent entities involved in an order with specific roles:- ORDERER - Entity placing the order
- PAYER - Entity paying for the order
- RECEIVER - Entity receiving the goods
- EXECUTOR - Entity fulfilling the order
- Order level - Applies to all lines
- Line level - Specific to individual order lines
Order Lifecycle
Step-by-Step Tutorial
import com.softwarearchetypes.ordering.*;
import com.softwarearchetypes.ordering.commands.*;
OrderingConfiguration config = OrderingConfiguration.inMemory();
OrderingFacade facade = config.orderingFacade();
OrderingQueries queries = config.orderingQueries();
import java.util.*;
OrderView order = facade.handle(new CreateOrderCommand(
// Parties involved
List.of(
new CreateOrderCommand.OrderPartyData(
"customer-123",
"Jan Kowalski",
"[email protected]",
Set.of("ORDERER", "PAYER", "RECEIVER")
),
new CreateOrderCommand.OrderPartyData(
"shop-001",
"TechShop",
"[email protected]",
Set.of("EXECUTOR")
)
),
// Order lines
List.of(
new CreateOrderCommand.OrderLineData(
"LAPTOP-DELL-XPS-15",
1,
"pieces",
Map.of(
"color", "silver",
"ram", "32GB",
"storage", "1TB SSD"
),
null // No line-level parties
)
)
)).getSuccess();
System.out.println("Order created: " + order.id());
System.out.println("Status: " + order.status()); // DRAFT
System.out.println("Lines: " + order.lines().size()); // 1
OrderView updated = facade.handle(new AddOrderLineCommand(
order.id(),
"MOUSE-LOGITECH-MX3",
1,
"pieces",
Map.of("color", "black")
)).getSuccess();
System.out.println("Lines after add: " + updated.lines().size()); // 2
// Add a second mouse
updated = facade.handle(new AddOrderLineCommand(
order.id(),
"USB-C-CABLE-2M",
2,
"pieces",
Map.of("type", "thunderbolt")
)).getSuccess();
System.out.println("Lines after second add: " + updated.lines().size()); // 3
// Get the line ID for the mouse
OrderLineId mouseLineId = updated.lines().stream()
.filter(line -> line.productId().equals("MOUSE-LOGITECH-MX3"))
.findFirst()
.map(OrderLineView::id)
.orElseThrow();
// Change quantity from 1 to 3
OrderView modified = facade.handle(new ChangeOrderLineQuantityCommand(
order.id(),
mouseLineId,
3,
"pieces"
)).getSuccess();
OrderLineView mouseLine = modified.lines().stream()
.filter(line -> line.id().equals(mouseLineId))
.findFirst()
.orElseThrow();
System.out.println("Mouse quantity: " + mouseLine.quantity()); // 3 pieces
// Remove the USB cable
OrderLineId cableLineId = modified.lines().stream()
.filter(line -> line.productId().equals("USB-C-CABLE-2M"))
.findFirst()
.map(OrderLineView::id)
.orElseThrow();
OrderView afterRemoval = facade.handle(new RemoveOrderLineCommand(
order.id(),
cableLineId
)).getSuccess();
System.out.println("Lines after removal: " + afterRemoval.lines().size()); // 2
import com.softwarearchetypes.common.Result;
Result<String, OrderView> confirmResult = facade.handle(
new ConfirmOrderCommand(order.id())
);
if (confirmResult.success()) {
OrderView confirmed = confirmResult.getSuccess();
System.out.println("Order confirmed: " + confirmed.id());
System.out.println("Status: " + confirmed.status()); // CONFIRMED
// Check side effects
InventoryService inventory = config.inventoryService();
System.out.println("Inventory allocations: " +
inventory.allocateRequests().size()); // 2 (laptop + mouse)
PaymentService payment = config.paymentService();
System.out.println("Payment authorizations: " +
payment.authorizeRequests().size()); // 1
FulfillmentService fulfillment = config.fulfillmentService();
System.out.println("Fulfillment started: " +
fulfillment.startedOrders().size()); // 1
} else {
System.err.println("Confirmation failed: " + confirmResult.getFailure());
}
import java.time.LocalDateTime;
// Fulfillment in progress
facade.handle(new FulfillmentUpdated(
order.id(),
FulfillmentStatus.IN_PROGRESS,
"Items picked and packed",
LocalDateTime.now()
));
OrderView processing = queries.findById(order.id()).orElseThrow();
System.out.println("Status: " + processing.status()); // PROCESSING
// Fulfillment completed
facade.handle(new FulfillmentUpdated(
order.id(),
FulfillmentStatus.COMPLETED,
"Delivered to customer",
LocalDateTime.now()
));
OrderView fulfilled = queries.findById(order.id()).orElseThrow();
System.out.println("Status: " + fulfilled.status()); // FULFILLED
// Create a new order to cancel
OrderView orderToCancel = facade.handle(new CreateOrderCommand(
List.of(
new CreateOrderCommand.OrderPartyData(
"customer-456",
"Maria Nowak",
"[email protected]",
Set.of("ORDERER", "PAYER", "RECEIVER")
)
),
List.of(
new CreateOrderCommand.OrderLineData(
"PHONE-SAMSUNG-S24",
1,
"pieces",
Map.of("color", "black"),
null
)
)
)).getSuccess();
// Cancel it
Result<String, OrderView> cancelResult = facade.handle(
new CancelOrderCommand(
orderToCancel.id(),
"Customer changed mind"
)
);
if (cancelResult.success()) {
OrderView cancelled = cancelResult.getSuccess();
System.out.println("Order cancelled: " + cancelled.status()); // CANCELLED
}
Advanced: Line-Level Parties
For B2B scenarios, different lines can have different parties:Complete E-Commerce Example
Common Patterns
Pattern: Guest Checkout
Allow orders without registered accounts:Pattern: Validation Before Confirmation
Pattern: Bulk Order Creation
Pattern: Order Search and Filtering
Integration Points
Inventory Service
The inventory service allocates products when orders are confirmed:Payment Service
The payment service authorizes payment on confirmation:Fulfillment Service
The fulfillment service starts picking/packing:Next Steps
- Learn about Order Pricing for integrating with the Pricing module
- Explore Party Role Policies for complex authorization
- See Order Events for event-driven architectures
The Ordering module uses the Command pattern extensively. All operations return
Result<String, T> for explicit error handling.