Advanced Patterns
Enterprise-grade patterns for multi-service orchestration, event-driven architectures, and distributed service execution
Enterprise-grade architectural patterns for building complex, scalable, and resilient Services-as-Software systems.
Overview
As your Services-as-Software ecosystem grows, you'll need sophisticated patterns for orchestrating multiple services, handling distributed transactions, managing events, and ensuring reliability. These patterns provide battle-tested approaches to common architectural challenges.
Pattern Overview
| Pattern | Purpose | Complexity | Use When |
|---|---|---|---|
| Multi-Service | Parallel service execution | Intermediate | Running independent services together |
| Service Chaining | Sequential service workflows | Intermediate | Output of one service feeds the next |
| Event-Driven | Reactive service orchestration | Advanced | Loosely coupled, scalable systems |
| Saga Pattern | Distributed transactions | Advanced | Multi-service transactions need rollback |
Multi-Service Pattern
Execute multiple independent services in parallel to achieve a complex goal.
When to Use
- Services can run independently without dependencies
- Need to aggregate results from multiple sources
- Want to reduce total execution time through parallelization
- Building composite services from existing primitives
Architecture
Implementation
import $, { db } from 'sdk.do'
const multiServiceOrchestrator = await $.Service.create({
name: 'Lead Intelligence Aggregator',
description: 'Combine multiple data sources for comprehensive lead profiles',
type: $.ServiceType.DataEnrichment,
workflow: async (inputs) => {
const { email, company } = inputs
// Execute services in parallel
const [emailValidation, socialProfiles, companyData, techStack] = await Promise.all([
// Service 1: Email validation
$.ServiceExecution.start({
serviceId: 'email-validator',
inputs: { email },
}).then((e) => e.waitForCompletion()),
// Service 2: Social profile discovery
$.ServiceExecution.start({
serviceId: 'social-finder',
inputs: { email, name: inputs.name },
}).then((e) => e.waitForCompletion()),
// Service 3: Company research
$.ServiceExecution.start({
serviceId: 'company-researcher',
inputs: { company },
}).then((e) => e.waitForCompletion()),
// Service 4: Technology stack analysis
$.ServiceExecution.start({
serviceId: 'tech-stack-analyzer',
inputs: { domain: company },
}).then((e) => e.waitForCompletion()),
])
// Aggregate results
return {
contact: {
email: emailValidation.outputs.email,
isValid: emailValidation.outputs.isValid,
social: socialProfiles.outputs.profiles,
},
company: {
...companyData.outputs,
technologies: techStack.outputs.stack,
},
completeness: calculateCompleteness([emailValidation, socialProfiles, companyData, techStack]),
}
},
})Error Handling
// Graceful degradation - continue with partial results
const results = await Promise.allSettled([service1.execute(), service2.execute(), service3.execute()])
const successfulResults = results.filter((r) => r.status === 'fulfilled').map((r) => r.value)
const failedServices = results.filter((r) => r.status === 'rejected').map((r, i) => ({ index: i, error: r.reason }))
if (failedServices.length > 0) {
console.warn('Some services failed:', failedServices)
}
return {
data: aggregateResults(successfulResults),
warnings: failedServices,
completeness: successfulResults.length / results.length,
}Best Practices
- Timeout Management: Set appropriate timeouts for each service
- Partial Results: Return partial data when some services fail
- Result Aggregation: Define clear merging logic for results
- Cost Tracking: Track costs across all services
- Performance: Monitor total execution time vs. sequential
Complete Multi-Service Pattern Guide →
Service Chaining Pattern
Execute services sequentially where each service's output feeds into the next service's input.
When to Use
- Service outputs depend on previous service results
- Building multi-step processing pipelines
- Need to transform data through multiple stages
- Creating complex workflows from simple services
Architecture
Implementation
import $, { db } from 'sdk.do'
const contentPipeline = await $.Service.create({
name: 'Content Production Pipeline',
description: 'Complete content pipeline from topic to published article',
type: $.ServiceType.ContentGeneration,
workflow: async (inputs) => {
// Step 1: Research topic
const research = await $.ServiceExecution.start({
serviceId: 'topic-researcher',
inputs: {
topic: inputs.topic,
depth: 'comprehensive',
},
}).then((e) => e.waitForCompletion())
// Step 2: Generate outline
const outline = await $.ServiceExecution.start({
serviceId: 'outline-generator',
inputs: {
topic: inputs.topic,
research: research.outputs.findings,
targetLength: inputs.wordCount,
},
}).then((e) => e.waitForCompletion())
// Step 3: Write content
const content = await $.ServiceExecution.start({
serviceId: 'content-writer',
inputs: {
outline: outline.outputs.sections,
tone: inputs.tone,
style: inputs.style,
},
}).then((e) => e.waitForCompletion())
// Step 4: Grammar check
const edited = await $.ServiceExecution.start({
serviceId: 'grammar-checker',
inputs: {
text: content.outputs.text,
},
}).then((e) => e.waitForCompletion())
// Step 5: SEO optimization
const optimized = await $.ServiceExecution.start({
serviceId: 'seo-optimizer',
inputs: {
text: edited.outputs.correctedText,
keywords: inputs.keywords,
},
}).then((e) => e.waitForCompletion())
// Step 6: Generate images
const images = await $.ServiceExecution.start({
serviceId: 'image-generator',
inputs: {
content: optimized.outputs.text,
count: 3,
style: 'professional',
},
}).then((e) => e.waitForCompletion())
// Return complete package
return {
article: {
title: optimized.outputs.title,
content: optimized.outputs.text,
excerpt: content.outputs.excerpt,
images: images.outputs.urls,
},
metadata: {
wordCount: optimized.outputs.wordCount,
readingTime: Math.ceil(optimized.outputs.wordCount / 200),
seoScore: optimized.outputs.score,
},
pipeline: {
steps: [
{ name: 'research', duration: research.duration },
{ name: 'outline', duration: outline.duration },
{ name: 'writing', duration: content.duration },
{ name: 'editing', duration: edited.duration },
{ name: 'seo', duration: optimized.duration },
{ name: 'images', duration: images.duration },
],
totalDuration: research.duration + outline.duration + content.duration + edited.duration + optimized.duration + images.duration,
},
}
},
})Error Recovery
// Checkpoint-based recovery
const pipeline = {
checkpoints: new Map(),
async execute(steps, inputs) {
let currentData = inputs
for (let i = 0; i < steps.length; i++) {
const step = steps[i]
try {
// Execute step
const result = await this.executeStep(step, currentData)
// Save checkpoint
this.checkpoints.set(i, {
step: step.name,
data: result,
timestamp: new Date(),
})
currentData = result
} catch (error) {
// Retry from last checkpoint
console.error(`Step ${step.name} failed:`, error)
if (step.retryable && this.checkpoints.has(i - 1)) {
console.log(`Retrying from checkpoint ${i - 1}`)
const checkpoint = this.checkpoints.get(i - 1)
currentData = checkpoint.data
i-- // Retry current step
} else {
throw new Error(`Pipeline failed at step ${step.name}: ${error.message}`)
}
}
}
return currentData
},
}Best Practices
- Checkpoint Strategy: Save intermediate results for recovery
- Error Propagation: Decide what errors stop the pipeline
- Data Transformation: Clearly define input/output contracts
- Progress Tracking: Emit progress events for monitoring
- Cost Accumulation: Track costs across all steps
Complete Service Chaining Guide →
Event-Driven Pattern
Build loosely coupled systems where services react to events rather than direct calls.
When to Use
- Need to decouple services for scalability
- Building reactive, real-time systems
- Want to add new services without changing existing ones
- Implementing pub/sub architectures
- Need audit trails and event replay
Architecture
Implementation
import { on, send } from 'sdk.do'
// Event publisher
class OrderService {
async createOrder(inputs) {
const order = await $.ServiceOrder.create(inputs)
// Emit event
await send($.Event.publish, {
type: 'order.created',
data: order,
metadata: {
source: 'order-service',
timestamp: new Date(),
},
})
return order
}
}
// Event subscribers
on('order.created', async (event) => {
// Service 1: Email notification
await $.ServiceExecution.start({
serviceId: 'email-notifier',
inputs: {
to: event.data.customerEmail,
template: 'order-confirmation',
data: event.data,
},
})
})
on('order.created', async (event) => {
// Service 2: Analytics tracking
await $.ServiceExecution.start({
serviceId: 'analytics-tracker',
inputs: {
event: 'order_created',
properties: {
orderId: event.data.id,
amount: event.data.amount,
customerId: event.data.customerId,
},
},
})
})
on('order.created', async (event) => {
// Service 3: Inventory update
await $.ServiceExecution.start({
serviceId: 'inventory-manager',
inputs: {
action: 'reserve',
items: event.data.items,
},
})
})
// Cascading events
on('order.created', async (event) => {
// Trigger fulfillment workflow
const fulfillment = await $.ServiceExecution.start({
serviceId: 'fulfillment-orchestrator',
inputs: { orderId: event.data.id },
})
// Emit fulfillment started event
await send($.Event.publish, {
type: 'fulfillment.started',
data: {
orderId: event.data.id,
fulfillmentId: fulfillment.id,
},
})
})Event Schema
interface ServiceEvent {
id: string
type: string
timestamp: Date
source: string
data: Record<string, any>
metadata?: {
correlationId?: string
causationId?: string
version?: string
}
}
// Versioned events
await send($.Event.publish, {
type: 'order.created',
version: '2.0',
data: {
// v2 schema
},
metadata: {
schemaVersion: '2.0',
migrations: {
'1.0': 'Migration guide URL',
},
},
})Best Practices
- Event Design: Clear, versioned event schemas
- Idempotency: Handlers should be idempotent
- Ordering: Don't assume event order unless guaranteed
- Dead Letter Queue: Handle failed event processing
- Event Store: Persist events for replay and audit
Complete Event-Driven Pattern Guide →
Saga Pattern
Manage distributed transactions across multiple services with compensating actions for rollback.
When to Use
- Multi-service transactions need atomicity
- Need to maintain consistency across services
- Can't use traditional database transactions
- Require compensating actions for rollback
- Building long-running workflows
Architecture
Implementation
import $, { db, send } from 'sdk.do'
class SagaOrchestrator {
async execute(sagaDefinition, inputs) {
const saga = await this.initializeSaga(sagaDefinition, inputs)
const executedSteps = []
try {
// Execute each step
for (const step of sagaDefinition.steps) {
const result = await this.executeStep(step, saga.context)
executedSteps.push({
step,
result,
timestamp: new Date(),
})
// Update saga context
saga.context = { ...saga.context, ...result }
// Save checkpoint
await this.saveCheckpoint(saga.id, executedSteps)
}
// All steps succeeded
await this.completeSaga(saga.id)
return saga.context
} catch (error) {
// Rollback executed steps
console.error('Saga failed, rolling back:', error)
await this.rollback(saga.id, executedSteps)
throw error
}
}
async rollback(sagaId, executedSteps) {
// Execute compensating actions in reverse order
for (const { step, result } of executedSteps.reverse()) {
if (step.compensate) {
try {
await step.compensate(result)
console.log(`Compensated step: ${step.name}`)
} catch (error) {
console.error(`Failed to compensate ${step.name}:`, error)
// Log for manual intervention
await this.logCompensationFailure(sagaId, step, error)
}
}
}
await this.markSagaFailed(sagaId)
}
}
// Define a saga
const orderSaga = {
name: 'Process Order Saga',
steps: [
{
name: 'validate-payment',
execute: async (context) => {
const validation = await $.ServiceExecution.start({
serviceId: 'payment-validator',
inputs: { paymentMethod: context.paymentMethod },
}).then((e) => e.waitForCompletion())
return { paymentValidated: true, validationId: validation.id }
},
compensate: async (result) => {
// Release payment hold
await $.ServiceExecution.start({
serviceId: 'payment-releaser',
inputs: { validationId: result.validationId },
})
},
},
{
name: 'reserve-inventory',
execute: async (context) => {
const reservation = await $.ServiceExecution.start({
serviceId: 'inventory-reserver',
inputs: { items: context.items },
}).then((e) => e.waitForCompletion())
return {
inventoryReserved: true,
reservationId: reservation.outputs.reservationId,
}
},
compensate: async (result) => {
// Release inventory
await $.ServiceExecution.start({
serviceId: 'inventory-releaser',
inputs: { reservationId: result.reservationId },
})
},
},
{
name: 'charge-payment',
execute: async (context) => {
const charge = await $.ServiceExecution.start({
serviceId: 'payment-processor',
inputs: {
amount: context.amount,
paymentMethod: context.paymentMethod,
},
}).then((e) => e.waitForCompletion())
return { paymentCharged: true, chargeId: charge.outputs.chargeId }
},
compensate: async (result) => {
// Refund payment
await $.ServiceExecution.start({
serviceId: 'payment-refunder',
inputs: { chargeId: result.chargeId },
})
},
},
{
name: 'create-shipment',
execute: async (context) => {
const shipment = await $.ServiceExecution.start({
serviceId: 'shipment-creator',
inputs: {
orderId: context.orderId,
address: context.shippingAddress,
},
}).then((e) => e.waitForCompletion())
return {
shipmentCreated: true,
shipmentId: shipment.outputs.shipmentId,
}
},
compensate: async (result) => {
// Cancel shipment
await $.ServiceExecution.start({
serviceId: 'shipment-canceller',
inputs: { shipmentId: result.shipmentId },
})
},
},
],
}
// Execute saga
const orchestrator = new SagaOrchestrator()
try {
const result = await orchestrator.execute(orderSaga, {
orderId: '12345',
items: [{ sku: 'ABC', quantity: 2 }],
amount: 99.99,
paymentMethod: 'card_xyz',
shippingAddress: {
/* ... */
},
})
console.log('Order processed successfully:', result)
} catch (error) {
console.error('Order processing failed:', error)
}Best Practices
- Idempotency: All steps and compensations must be idempotent
- Timeout Handling: Set timeouts for each step
- Compensation Logic: Design careful rollback procedures
- State Persistence: Save saga state for recovery
- Manual Intervention: Plan for compensation failures
Pattern Selection Guide
Decision Matrix
| Requirement | Recommended Pattern |
|---|---|
| Independent parallel tasks | Multi-Service |
| Sequential data transformation | Service Chaining |
| Loose coupling needed | Event-Driven |
| Distributed transactions | Saga |
| Real-time reactivity | Event-Driven |
| Complex rollback logic | Saga |
| Aggregate multiple sources | Multi-Service |
| Long-running workflows | Saga + Event-Driven |
Combining Patterns
Patterns can be combined for complex requirements:
// Saga + Event-Driven
on('order.created', async (event) => {
// Start saga orchestration
await sagaOrchestrator.execute(orderSaga, event.data)
})
// Service Chaining + Multi-Service
const hybridWorkflow = async (inputs) => {
// Parallel phase
const [data1, data2] = await Promise.all([executeService('service-1', inputs), executeService('service-2', inputs)])
// Sequential phase
const result1 = await executeService('service-3', { data1, data2 })
const result2 = await executeService('service-4', result1)
return result2
}Performance Considerations
Latency Optimization
// Reduce latency with parallel execution
const fastPath = async (inputs) => {
// Critical path
const critical = await executeCriticalService(inputs)
// Non-blocking optimizations
Promise.all([logAnalytics(inputs), updateCache(critical), notifyWebhooks(critical)]).catch((error) => console.error('Background tasks failed:', error))
return critical
}Cost Optimization
// Smart service selection based on cost/quality tradeoff
const selectService = (requirements) => {
if (requirements.quality === 'premium') {
return 'expensive-accurate-service'
} else if (requirements.budget === 'low') {
return 'cheap-fast-service'
} else {
return 'balanced-service'
}
}Monitoring Patterns
Track pattern execution:
import { metrics } from 'sdk.do'
// Track pattern performance
class PatternMetrics {
async trackExecution(patternName, fn) {
const start = Date.now()
const result = await fn()
const duration = Date.now() - start
await metrics.record({
pattern: patternName,
duration,
success: true,
})
return result
}
}
// Usage
const result = await patternMetrics.trackExecution('saga', async () => {
return orchestrator.execute(orderSaga, inputs)
})Next Steps
Dive deeper into specific patterns:
- Multi-Service Pattern → - Parallel execution details
- Service Chaining → - Sequential workflows
- Event-Driven → - Reactive architectures
- Saga Pattern → - Distributed transactions
Or explore related topics:
- Implementation Guides - Step-by-step guides
- API Reference - Complete API docs
- Examples - Real-world implementations
- Best Practices - Proven guidelines