Advanced Patterns
Saga Pattern for Distributed Transactions
The Saga pattern manages distributed transactions through a sequence of local operations, each with compensating actions for failure scenarios. This is particularly valuable for maintaining consistency across distributed components.
// Create a saga coordinator
const orderSaga = createNode("order-saga");
// Start saga for order processing
orderSaga.on("process-order", async (event) => {
const { orderId } = event.payload;
const context = { orderId, steps: [] };
try {
// Step 1: Reserve inventory
const inventoryResult = await orderSaga.send(inventoryNode, {
type: "reserve-inventory",
payload: event.payload
}).return();
if (!inventoryResult.success) {
throw new Error(`Inventory reservation failed: ${inventoryResult.reason}`);
}
context.steps.push("inventory-reserved");
// Step 2: Process payment
const paymentResult = await orderSaga.send(paymentNode, {
type: "process-payment",
payload: event.payload
}).return();
if (!paymentResult.success) {
throw new Error(`Payment failed: ${paymentResult.reason}`);
}
context.steps.push("payment-processed");
context.paymentId = paymentResult.paymentId;
// Step 3: Create shipment
const shipmentResult = await orderSaga.send(shipmentNode, {
type: "create-shipment",
payload: {
orderId,
items: event.payload.items,
address: event.payload.shippingAddress
}
}).return();
if (!shipmentResult.success) {
throw new Error(`Shipment creation failed: ${shipmentResult.reason}`);
}
// Complete the saga
orderSaga.broadcast({
type: "order-processed",
payload: {
orderId,
status: "processed",
shipmentId: shipmentResult.shipmentId
}
});
return {
success: true,
orderId,
shipmentId: shipmentResult.shipmentId
};
} catch (error) {
// Execute compensating transactions in reverse order
if (context.steps.includes("payment-processed")) {
await orderSaga.send(paymentNode, {
type: "refund-payment",
payload: {
paymentId: context.paymentId
}
}).return();
}
if (context.steps.includes("inventory-reserved")) {
await orderSaga.send(inventoryNode, {
type: "release-inventory",
payload: event.payload
}).return();
}
// Saga failed
orderSaga.broadcast({
type: "order-processing-failed",
payload: {
orderId,
reason: error.message
}
});
return {
success: false,
reason: error.message
};
}
});
This implementation shows how Happen's event-driven model naturally supports complex transactional scenarios that span multiple services.
Retry Pattern with Exponential Backoff
The Retry pattern handles transient failures by automatically retrying operations with increasing delays between attempts.
// Create a service node with retry capability
const paymentServiceNode = createNode("payment-service");
// Process payment with retry logic
paymentServiceNode.on("process-payment", async function processPayment(event, context) {
// Initialize retry context if not exists
context.retryCount = context.retryCount || 0;
context.startTime = context.startTime || Date.now();
try {
// Attempt payment processing
const result = await chargeCustomer(event.payload);
// Success - return result
return {
success: true,
transactionId: result.transactionId,
amount: event.payload.amount,
processedAt: Date.now()
};
} catch (error) {
// Store error in context
context.lastError = error;
context.retryCount++;
// Determine if we should retry
const maxRetries = 5;
const maxDuration = 2 * 60 * 1000; // 2 minutes
const timeElapsed = Date.now() - context.startTime;
if (context.retryCount < maxRetries && timeElapsed < maxDuration) {
// Calculate exponential backoff delay
const delay = Math.min(100 * Math.pow(2, context.retryCount), 5000);
// Log retry attempt
console.log(`Retrying payment (${context.retryCount}/${maxRetries}) after ${delay}ms delay`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
// Return self to retry
return processPayment;
}
// Too many retries or timeout exceeded
return {
success: false,
reason: "payment-failed-after-retries",
attempts: context.retryCount,
lastError: context.lastError.message
};
}
});
This pattern demonstrates Happen's function-returning mechanism to create sophisticated retry logic with minimal code.
Circuit Breaker Pattern
The Circuit Breaker pattern prevents cascading failures by stopping operations that are likely to fail.
// Create a node with circuit breaker capability
const apiGatewayNode = createNode("api-gateway");
// Circuit state storage
const circuits = {
payment: {
state: "closed", // closed, open, half-open
failures: 0,
lastFailure: null,
resetTimeout: null
}
};
// Handler with circuit breaker logic
apiGatewayNode.on("call-payment-service", function processPaymentRequest(event, context) {
const circuit = circuits.payment;
// Check circuit state
if (circuit.state === "open") {
// Circuit is open - fail fast
return {
success: false,
reason: "circuit-open",
message: "Payment service is unavailable"
};
}
// Process request with circuit awareness
return makePaymentServiceCall;
});
// Function to make the actual service call
async function makePaymentServiceCall(event, context) {
const circuit = circuits.payment;
try {
// Attempt to call the payment service
const result = await callPaymentService(event.payload);
// Success - reset circuit if needed
if (circuit.state === "half-open") {
circuit.state = "closed";
circuit.failures = 0;
}
// Return successful result
return result;
} catch (error) {
// Update circuit on failure
circuit.failures++;
circuit.lastFailure = Date.now();
// Check if we should open the circuit
if (circuit.state === "closed" && circuit.failures >= 5) {
// Open the circuit
circuit.state = "open";
// Schedule reset to half-open
circuit.resetTimeout = setTimeout(() => {
circuit.state = "half-open";
}, 30000); // 30 second timeout
}
// Return error with circuit status
return {
success: false,
reason: "payment-service-error",
message: error.message,
circuitState: circuit.state
};
}
}
This pattern shows how Happen can be extended to implement sophisticated resilience patterns that protect systems from cascading failures.
Supervisor Pattern
The Supervisor pattern manages component lifecycles and handles failures through a hierarchical oversight structure.
// Create a supervisor node
const supervisorNode = createNode("system-supervisor");
// Register handler for all error events
supervisorNode.on(type => type.endsWith(".error"), (event) => {
// Extract service name from event type
const service = event.type.split('.')[0];
// Update service state
supervisorNode.state.set(state => {
const services = state.services || {};
const serviceState = services[service] || {
errors: 0,
lastError: 0,
lastReset: 0,
restarts: 0
};
// Update error count
const updatedServiceState = {
...serviceState,
errors: serviceState.errors + 1,
lastError: Date.now()
};
// Return updated state
return {
...state,
services: {
...services,
[service]: updatedServiceState
}
};
});
// Get updated service state
const serviceState = supervisorNode.state.get(state =>
(state.services && state.services[service]) || { errors: 0, lastReset: 0 }
);
// Check restart threshold
if (serviceState.errors >= 5 && Date.now() - serviceState.lastReset < 60000) {
// Service is failing too frequently - trigger restart
supervisorNode.broadcast({
type: "service.restart-required",
payload: {
service,
reason: "excessive-errors",
errorCount: serviceState.errors
}
});
}
return { supervised: true };
});
// Handle service restart
supervisorNode.on("service.restart-required", (event) => {
const { service } = event.payload;
console.log(`Restarting service: ${service}`);
// Broadcast restart event
supervisorNode.broadcast({
type: "infrastructure.restart-service",
payload: {
service,
triggeredBy: "supervisor",
reason: event.payload.reason
}
});
// Update service state
supervisorNode.state.set(state => {
const services = state.services || {};
const serviceState = services[service] || {};
return {
...state,
services: {
...services,
[service]: {
...serviceState,
errors: 0,
lastReset: Date.now(),
restarts: (serviceState.restarts || 0) + 1
}
}
};
});
return { action: "restart", service };
});
This pattern demonstrates Happen's ability to implement hierarchical oversight and automated recovery mechanisms using its core primitives.
State Machine Workflow
The State Machine pattern models entities that transition between discrete states according to well-defined rules.
// Create a state machine node
const orderStateMachine = createNode("order-state-machine");
// Define allowed transitions
const allowedTransitions = {
"draft": ["submitted"],
"submitted": ["processing", "canceled"],
"processing": ["shipped", "canceled"],
"shipped": ["delivered"],
"canceled": []
};
// Process transition requests
orderStateMachine.on("transition-order", (event) => {
const { orderId, toState } = event.payload;
// Get current order state
orderStateMachine.state.get(state => {
const order = (state.orders || {})[orderId];
if (!order) {
return {
success: false,
reason: "order-not-found"
};
}
// Check if transition is allowed
const currentState = order.status;
if (!allowedTransitions[currentState]?.includes(toState)) {
return {
success: false,
reason: "invalid-transition",
message: `Cannot transition from ${currentState} to ${toState}`
};
}
// Apply the transition
orderStateMachine.state.set(state => {
const orders = state.orders || {};
return {
...state,
orders: {
...orders,
[orderId]: {
...order,
status: toState,
transitionedAt: Date.now(),
previousStatus: currentState
}
}
};
});
// Broadcast state change event
orderStateMachine.broadcast({
type: "order-state-changed",
payload: {
orderId,
fromState: currentState,
toState,
timestamp: Date.now()
}
});
return {
success: true,
orderId,
currentState: toState
};
});
});
This pattern shows how Happen's state management capabilities can be used to implement formal state machines with controlled transitions.
Reactive Aggregates
The Reactive Aggregates pattern combines event-driven reactivity with consistent state management for domain aggregates.
// Create an aggregate node
const customerAggregate = createNode("customer-aggregate");
// React to events and update aggregate state
customerAggregate.on("customer-registered", (event) => {
const { customerId, email, name } = event.payload;
// Create new aggregate state
customerAggregate.state.set(state => {
const customers = state.customers || {};
return {
...state,
customers: {
...customers,
[customerId]: {
id: customerId,
email,
name,
status: "active",
registeredAt: Date.now(),
orders: []
}
}
};
});
return { updated: true };
});
// Add order to customer aggregate
customerAggregate.on("order-created", (event) => {
const { orderId, customerId, amount } = event.payload;
// Update aggregate state
customerAggregate.state.set(state => {
const customers = state.customers || {};
const customer = customers[customerId];
if (!customer) {
return state; // Customer not found, state unchanged
}
return {
...state,
customers: {
...customers,
[customerId]: {
...customer,
orders: [
...customer.orders,
{
id: orderId,
amount,
createdAt: Date.now()
}
],
totalSpent: (customer.totalSpent || 0) + amount,
lastOrderAt: Date.now()
}
}
};
});
// Check if customer qualifies for premium status
const customer = customerAggregate.state.get(state =>
(state.customers || {})[customerId]
);
if (customer && customer.orders.length >= 5 && customer.status !== "premium") {
// Upgrade to premium status
customerAggregate.state.set(state => {
const customers = state.customers || {};
return {
...state,
customers: {
...customers,
[customerId]: {
...customers[customerId],
status: "premium",
upgradedAt: Date.now()
}
}
};
});
// Broadcast premium status event
customerAggregate.broadcast({
type: "customer-premium-status-achieved",
payload: {
customerId,
ordersCount: customer.orders.length,
totalSpent: customer.totalSpent + amount
}
});
}
return { updated: true };
});
// Command handler for direct interactions with the aggregate
customerAggregate.on("update-customer-email", (event) => {
const { customerId, email } = event.payload;
// Validate customer exists
const customer = customerAggregate.state.get(state =>
(state.customers || {})[customerId]
);
if (!customer) {
return {
success: false,
reason: "customer-not-found"
};
}
// Update email
customerAggregate.state.set(state => {
const customers = state.customers || {};
return {
...state,
customers: {
...customers,
[customerId]: {
...customers[customerId],
email,
emailUpdatedAt: Date.now()
}
}
};
});
// Broadcast email changed event
customerAggregate.broadcast({
type: "customer-email-changed",
payload: {
customerId,
previousEmail: customer.email,
newEmail: email
}
});
return {
success: true,
customerId
};
});
This pattern demonstrates how Happen can combine reactive event handling with consistent state management in domain-driven designs.
Last updated