Overview
The order fulfillment workflow coordinates several microservices:- Inventory Service (child workflow)
- Payment Service (child workflow)
- Shipping Service (child workflow)
- Notification Service (activities)
Parent Workflow Implementation
<?php
namespace App\Workflows;
use function Workflow\{activity, child};
use Workflow\SignalMethod;
use Workflow\Workflow;
class OrderFulfillmentWorkflow extends Workflow
{
private string $orderStatus = 'pending';
private ?string $trackingNumber = null;
#[SignalMethod]
public function updateShippingStatus(string $status, ?string $tracking = null): void
{
$this->orderStatus = $status;
$this->trackingNumber = $tracking;
}
#[SignalMethod]
public function cancelOrder(): void
{
$this->orderStatus = 'cancelled';
}
public function execute(
string $orderId,
string $customerId,
array $items,
array $shippingAddress,
string $paymentMethod
) {
// Step 1: Start inventory reservation as child workflow
$inventoryResult = yield child(
InventoryReservationWorkflow::class,
$orderId,
$items
);
if (!$inventoryResult['success']) {
throw new \Exception('Insufficient inventory: ' . $inventoryResult['message']);
}
// Step 2: Process payment via Payment Service child workflow
$paymentResult = yield child(
PaymentProcessingChildWorkflow::class,
$orderId,
$customerId,
$inventoryResult['totalAmount'],
$paymentMethod
);
if (!$paymentResult['success']) {
// Rollback inventory reservation
yield child(
InventoryRollbackWorkflow::class,
$inventoryResult['reservationId']
);
throw new \Exception('Payment failed: ' . $paymentResult['message']);
}
// Step 3: Create shipment via Shipping Service child workflow
$shipmentResult = yield child(
ShipmentCreationWorkflow::class,
$orderId,
$items,
$shippingAddress,
$inventoryResult['warehouseId']
);
// Step 4: Send confirmation notification
yield activity(
SendOrderConfirmationActivity::class,
$customerId,
$orderId,
$shipmentResult['trackingNumber']
);
// Step 5: Monitor shipment status (async)
// The shipping service will send signals as status updates
$this->trackingNumber = $shipmentResult['trackingNumber'];
$this->orderStatus = 'shipped';
return [
'orderId' => $orderId,
'status' => $this->orderStatus,
'reservationId' => $inventoryResult['reservationId'],
'paymentId' => $paymentResult['transactionId'],
'shipmentId' => $shipmentResult['shipmentId'],
'trackingNumber' => $this->trackingNumber,
];
}
}
Child Workflow: Inventory Service
<?php
namespace App\Workflows;
use function Workflow\activity;
use Workflow\Workflow;
class InventoryReservationWorkflow extends Workflow
{
public function execute(string $orderId, array $items)
{
// Check availability across multiple warehouses
$availabilityCheck = yield activity(
CheckInventoryAvailabilityActivity::class,
$items
);
if (!$availabilityCheck['available']) {
return [
'success' => false,
'message' => 'Items not available',
'unavailableItems' => $availabilityCheck['unavailableItems'],
];
}
// Select optimal warehouse based on location and stock
$warehouse = yield activity(
SelectOptimalWarehouseActivity::class,
$items,
$availabilityCheck['warehouses']
);
// Reserve inventory
$reservation = yield activity(
ReserveInventoryActivity::class,
$orderId,
$items,
$warehouse['id']
);
// Calculate total amount
$totalAmount = yield activity(
CalculateOrderTotalActivity::class,
$items
);
return [
'success' => true,
'reservationId' => $reservation['id'],
'warehouseId' => $warehouse['id'],
'totalAmount' => $totalAmount,
];
}
}
Child Workflow: Payment Service
<?php
namespace App\Workflows;
use function Workflow\{activity, timer};
use Workflow\Workflow;
class PaymentProcessingChildWorkflow extends Workflow
{
public function execute(
string $orderId,
string $customerId,
float $amount,
string $paymentMethod
) {
// Pre-authorize payment
$preAuthResult = yield activity(
PreAuthorizePaymentActivity::class,
$customerId,
$amount,
$paymentMethod
);
if (!$preAuthResult['success']) {
return [
'success' => false,
'message' => $preAuthResult['error'],
];
}
// Apply fraud detection
$fraudCheck = yield activity(
FraudDetectionActivity::class,
$orderId,
$customerId,
$amount
);
if ($fraudCheck['risk'] === 'high') {
// Void pre-authorization
yield activity(
VoidPreAuthorizationActivity::class,
$preAuthResult['authId']
);
return [
'success' => false,
'message' => 'Payment flagged for fraud review',
];
}
// Capture payment
$captureResult = yield activity(
CapturePaymentActivity::class,
$preAuthResult['authId']
);
// Send payment receipt
yield activity(
SendPaymentReceiptActivity::class,
$customerId,
$captureResult['transactionId'],
$amount
);
return [
'success' => true,
'transactionId' => $captureResult['transactionId'],
'amount' => $amount,
];
}
}
Child Workflow: Shipping Service
<?php
namespace App\Workflows;
use function Workflow\{activity, timer};
use Workflow\Workflow;
use Workflow\WorkflowStub;
class ShipmentCreationWorkflow extends Workflow
{
public function execute(
string $orderId,
array $items,
array $shippingAddress,
string $warehouseId
) {
// Select shipping carrier based on address and weight
$carrier = yield activity(
SelectShippingCarrierActivity::class,
$shippingAddress,
$items
);
// Create shipping label
$label = yield activity(
CreateShippingLabelActivity::class,
$orderId,
$carrier['id'],
$shippingAddress,
$items
);
// Create pick list for warehouse
yield activity(
CreatePickListActivity::class,
$warehouseId,
$orderId,
$items
);
// Wait for warehouse to confirm shipment (max 2 business days)
yield timer('2 days');
// Send tracking notification to parent workflow
$parentWorkflow = WorkflowStub::load($this->storedWorkflow->parents()->first()->id);
$parentWorkflow->updateShippingStatus('in_transit', $label['trackingNumber']);
return [
'shipmentId' => $label['shipmentId'],
'trackingNumber' => $label['trackingNumber'],
'carrier' => $carrier['name'],
'estimatedDelivery' => $label['estimatedDelivery'],
];
}
}
Inventory Rollback Workflow
<?php
namespace App\Workflows;
use function Workflow\activity;
use Workflow\Workflow;
class InventoryRollbackWorkflow extends Workflow
{
public function execute(string $reservationId)
{
// Release reserved inventory
yield activity(
ReleaseReservationActivity::class,
$reservationId
);
// Log rollback for audit
yield activity(
LogInventoryRollbackActivity::class,
$reservationId,
'Payment failure'
);
return ['status' => 'rolled_back'];
}
}
Example Activities
Check Inventory Availability
<?php
namespace App\Activities;
use App\Services\InventoryService;
use Workflow\Activity;
class CheckInventoryAvailabilityActivity extends Activity
{
public function __construct(
private InventoryService $inventory
) {}
public function execute(array $items): array
{
$warehouses = [];
$unavailableItems = [];
foreach ($items as $item) {
$availability = $this->inventory->checkStock(
$item['sku'],
$item['quantity']
);
if (!$availability['available']) {
$unavailableItems[] = $item['sku'];
} else {
$warehouses = array_merge(
$warehouses,
$availability['warehouses']
);
}
}
return [
'available' => empty($unavailableItems),
'unavailableItems' => $unavailableItems,
'warehouses' => array_unique($warehouses),
];
}
}
Fraud Detection
<?php
namespace App\Activities;
use App\Services\FraudDetectionService;
use Workflow\Activity;
class FraudDetectionActivity extends Activity
{
public function __construct(
private FraudDetectionService $fraudService
) {}
public function execute(
string $orderId,
string $customerId,
float $amount
): array {
$score = $this->fraudService->analyzeTransaction([
'order_id' => $orderId,
'customer_id' => $customerId,
'amount' => $amount,
]);
$risk = match (true) {
$score >= 80 => 'high',
$score >= 50 => 'medium',
default => 'low',
};
return [
'risk' => $risk,
'score' => $score,
];
}
}
Starting the Orchestration
use App\Workflows\OrderFulfillmentWorkflow;
use Workflow\WorkflowStub;
$workflow = WorkflowStub::make(OrderFulfillmentWorkflow::class);
$workflow->start(
orderId: 'ORD-12345',
customerId: 'CUST-67890',
items: [
['sku' => 'WIDGET-A', 'quantity' => 2, 'price' => 29.99],
['sku' => 'GADGET-B', 'quantity' => 1, 'price' => 149.99],
],
shippingAddress: [
'street' => '123 Main St',
'city' => 'San Francisco',
'state' => 'CA',
'zip' => '94102',
'country' => 'US',
],
paymentMethod: 'pm_card_visa'
);
// Monitor progress
while ($workflow->running()) {
sleep(2);
// Check child workflows
$children = $workflow->children();
foreach ($children as $child) {
echo "{$child->class()}: {$child->status()}\n";
}
}
if ($workflow->completed()) {
$result = $workflow->output();
echo "Order {$result['orderId']} fulfilled successfully\n";
echo "Tracking: {$result['trackingNumber']}\n";
}
Handling Service Failures
Scenario 1: Inventory Service Unavailable
// The child workflow will automatically retry
// Parent workflow waits for child to complete
Scenario 2: Payment Declined
// Payment child workflow returns failure
// Parent workflow triggers inventory rollback
// Order marked as failed
Scenario 3: Partial Shipment
// Shipping workflow handles split shipments
// Multiple tracking numbers sent via signals
// Parent workflow aggregates all shipments
Monitoring Child Workflows
// Get all child workflows
$parentWorkflow = WorkflowStub::load($workflowId);
$children = $parentWorkflow->children();
foreach ($children as $child) {
echo "Service: {$child->class()}\n";
echo "Status: {$child->status()}\n";
if ($child->completed()) {
echo "Output: " . json_encode($child->output()) . "\n";
}
echo "---\n";
}
Key Features
- Service Isolation: Each microservice runs as independent child workflow
- Distributed Transactions: Coordinated across multiple services
- Automatic Rollback: Failed operations trigger compensating workflows
- Signal Communication: Child workflows can signal parent workflow
- Independent Scaling: Each service scales based on its own load
- Fault Tolerance: Child workflow failures don’t crash parent
- Audit Trail: Complete visibility into cross-service operations
- Async Communication: Non-blocking communication between services