Service Composition
Combining services for complex workflows
Learn how to combine multiple Services-as-Software to create powerful, complex workflows.
Composition Patterns
Sequential Composition
Execute services in sequence:
const workflow = $.workflow('content-pipeline', async (topic) => {
// Step 1: Generate content
const article = await services.contentGenerator.generate({
topic,
keywords: ['AI', 'automation'],
length: 1500,
})
// Step 2: Generate images
const images = await services.imageGenerator.generate({
descriptions: extractImageNeeds(article),
style: 'professional',
})
// Step 3: Optimize for SEO
const optimized = await services.seoOptimizer.optimize({
content: article,
images,
targetKeywords: ['AI', 'automation'],
})
// Step 4: Publish
const published = await services.cms.publish({
content: optimized,
images,
schedule: 'next_available',
})
return published
})Parallel Composition
Execute services concurrently:
const workflow = $.workflow('lead-enrichment', async (lead) => {
// Run multiple services in parallel
const [companyData, socialProfiles, creditScore, techStack] = await Promise.all([
services.companyEnrichment.enrich({ company: lead.company }),
services.socialLookup.find({ email: lead.email }),
services.creditCheck.score({ company: lead.company }),
services.techDetector.detect({ domain: lead.website }),
])
// Combine results
return {
...lead,
companyData,
socialProfiles,
creditScore,
techStack,
}
})Conditional Composition
Route based on conditions:
const workflow = $.workflow('order-fulfillment', async (order) => {
// Check inventory
const available = await services.inventory.check(order.items)
if (available) {
// Standard fulfillment
return await services.fulfillment.process({
order,
priority: 'standard',
})
} else {
// Backorder process
const backorder = await services.backorder.create(order)
// Notify customer
await services.notifications.send({
to: order.customer,
template: 'backorder',
data: { backorder },
})
return backorder
}
})Event-Driven Composition
Services communicate via events:
// Service A: Order Processing
export default service({
name: 'Order Processing',
on: {
'$.Order.created': async (event) => {
// Process order
const processed = await processOrder(event.data)
// Emit event for next service
await $.send('$.Order.processed', processed)
},
},
})
// Service B: Fulfillment
export default service({
name: 'Fulfillment',
on: {
'$.Order.processed': async (event) => {
// Fulfill order
const shipment = await fulfillOrder(event.data)
// Emit event for next service
await $.send('$.Shipment.created', shipment)
},
},
})
// Service C: Notifications
export default service({
name: 'Notifications',
on: {
'$.Shipment.created': async (event) => {
// Notify customer
await $.Email.send({
to: event.data.customer.email,
template: 'shipment-notification',
data: event.data,
})
},
},
})Service Discovery
Install Services
Install services from marketplace:
// Install a service
const service = await $.services.install('customer-support-ai')
// Use the service
const result = await service.analyzeTicket({
ticketId: 'ticket_123',
})Service Registry
Query available services:
// Find services by category
const services = await $.services.find({
category: 'marketing',
tags: ['email', 'automation'],
})
// Get service info
const info = await $.services.info('email-marketing-pro')
console.log(info)
// {
// name: 'Email Marketing Pro',
// description: 'Advanced email marketing automation',
// version: '2.1.0',
// pricing: { model: 'subscription', plans: [...] },
// capabilities: ['send', 'track', 'analyze']
// }Service Dependencies
Declare Dependencies
Declare required services:
export default service({
name: 'Content Marketing Suite',
dependencies: {
contentGenerator: 'content-generator@^2.0.0',
imageGenerator: 'image-generator@^1.5.0',
seoOptimizer: 'seo-optimizer@^3.2.0',
cms: 'cms-publisher@^1.0.0',
},
api: {
'POST /create-campaign': async (req) => {
const { topic } = await req.json()
// Use dependent services
const article = await $.deps.contentGenerator.generate({ topic })
const images = await $.deps.imageGenerator.generate({ article })
const optimized = await $.deps.seoOptimizer.optimize({ article, images })
const published = await $.deps.cms.publish({ content: optimized })
return { published }
},
},
})Version Compatibility
Specify version constraints:
dependencies: {
'service-a': '2.1.0', // Exact version
'service-b': '^2.0.0', // Compatible with 2.x.x
'service-c': '~1.5.0', // Compatible with 1.5.x
'service-d': '>=1.0.0', // 1.0.0 or higher
}Data Flow Patterns
Pipeline Pattern
Process data through multiple stages:
const pipeline = $.pipeline('data-enrichment', [
// Stage 1: Clean
async (data) => {
return await services.dataCleaner.clean(data)
},
// Stage 2: Validate
async (data) => {
return await services.validator.validate(data)
},
// Stage 3: Enrich
async (data) => {
return await services.enrichment.enrich(data)
},
// Stage 4: Score
async (data) => {
return await services.scorer.score(data)
},
])
// Run pipeline
const result = await pipeline.run(rawData)Fan-Out/Fan-In Pattern
Distribute work and aggregate results:
const workflow = $.workflow('multi-channel-campaign', async (campaign) => {
// Fan-out: Send to multiple channels
const results = await Promise.all([services.email.send(campaign), services.sms.send(campaign), services.push.send(campaign), services.social.post(campaign)])
// Fan-in: Aggregate results
const aggregated = {
totalSent: results.reduce((sum, r) => sum + r.sent, 0),
totalDelivered: results.reduce((sum, r) => sum + r.delivered, 0),
byChannel: results.map((r) => ({
channel: r.channel,
sent: r.sent,
delivered: r.delivered,
})),
}
return aggregated
})Request-Reply Pattern
Synchronous service calls:
// Service A: Make request
const response = await services.analyzer.analyze({
data: inputData,
options: { detailed: true },
})
// Service B: Process and reply
export default service({
name: 'Analyzer',
api: {
'POST /analyze': async (req) => {
const { data, options } = await req.json()
const analysis = await performAnalysis(data, options)
return { analysis } // Reply
},
},
})Error Handling in Composition
Retry Logic
Retry failed service calls:
const workflow = $.workflow('resilient-process', async (data) => {
try {
return await services.external.process(data)
} catch (error) {
if (error.retryable) {
// Retry with exponential backoff
return await retry(() => services.external.process(data), {
attempts: 3,
delay: 1000,
backoff: 2,
})
}
throw error
}
})Fallback Services
Use fallback when primary fails:
const workflow = $.workflow('payment-processing', async (payment) => {
try {
// Try primary processor
return await services.stripe.charge(payment)
} catch (error) {
// Fallback to secondary
try {
return await services.paypal.charge(payment)
} catch (fallbackError) {
// Both failed - alert and queue
await $.alert('payment-processors-down', { payment })
return await $.queue.add('pending-payments', payment)
}
}
})Compensating Transactions
Rollback on failure:
const workflow = $.workflow('order-placement', async (order) => {
let inventoryReserved = false
let paymentCharged = false
try {
// Step 1: Reserve inventory
await services.inventory.reserve(order.items)
inventoryReserved = true
// Step 2: Charge payment
await services.payment.charge(order)
paymentCharged = true
// Step 3: Create shipment
const shipment = await services.fulfillment.ship(order)
return { success: true, shipment }
} catch (error) {
// Compensate for completed steps
if (paymentCharged) {
await services.payment.refund(order)
}
if (inventoryReserved) {
await services.inventory.release(order.items)
}
throw error
}
})Service Orchestration
Workflow Orchestration
Coordinate complex workflows:
export default service({
name: 'Workflow Orchestrator',
workflows: {
'customer-onboarding': {
steps: [
{
name: 'create-account',
service: 'auth-service',
action: 'createAccount',
input: '$.input.customer',
},
{
name: 'setup-billing',
service: 'billing-service',
action: 'setupCustomer',
input: '$.steps.create-account.output',
dependsOn: ['create-account'],
},
{
name: 'send-welcome',
service: 'email-service',
action: 'sendWelcome',
input: '$.steps.create-account.output',
dependsOn: ['create-account'],
},
{
name: 'provision-resources',
service: 'provisioning-service',
action: 'provision',
input: '$.steps.create-account.output',
dependsOn: ['create-account', 'setup-billing'],
},
],
},
},
})Service Mesh
Manage service-to-service communication:
export default service({
name: 'API Gateway',
mesh: {
services: {
'customer-service': {
url: 'https://customer.do',
auth: 'jwt',
retry: { attempts: 3 },
timeout: 5000,
},
'order-service': {
url: 'https://order.do',
auth: 'jwt',
retry: { attempts: 3 },
timeout: 5000,
},
'payment-service': {
url: 'https://payment.do',
auth: 'api-key',
retry: { attempts: 5 },
timeout: 10000,
},
},
},
})Monitoring Composed Services
Distributed Tracing
Track requests across services:
const workflow = $.workflow('multi-service-process', async (data) => {
// Start trace
const trace = $.trace.start('multi-service-process')
try {
// Step 1
const result1 = await trace.span('service-a', () => services.serviceA.process(data))
// Step 2
const result2 = await trace.span('service-b', () => services.serviceB.process(result1))
// Step 3
const result3 = await trace.span('service-c', () => services.serviceC.process(result2))
trace.end({ success: true })
return result3
} catch (error) {
trace.end({ success: false, error })
throw error
}
})Aggregate Metrics
Monitor composed services:
// Track workflow metrics
await $.metric.histogram('workflow.duration', duration, {
workflow: 'content-pipeline',
status: 'success',
})
await $.metric.increment('workflow.executions', {
workflow: 'content-pipeline',
})
// Query metrics
const metrics = await $.metric.query({
metric: 'workflow.duration',
workflow: 'content-pipeline',
timeRange: 'last_24_hours',
})Best Practices
Loose Coupling
Keep services independent:
// Good: Services communicate via events
on: {
'$.Order.placed': async (event) => {
await $.send('$.Fulfillment.requested', event.data)
},
}
// Bad: Direct service dependencies
on: {
'$.Order.placed': async (event) => {
await services.fulfillment.process(event.data) // Tight coupling
},
}Idempotency
Make compositions idempotent:
const workflow = $.workflow('idempotent-process', async (data) => {
const key = `workflow:${data.id}`
// Check if already processed
const existing = await $.cache.get(key)
if (existing) {
return existing
}
// Process
const result = await processData(data)
// Cache result
await $.cache.set(key, result, { ttl: 3600 })
return result
})Graceful Degradation
Handle service unavailability:
const workflow = $.workflow('resilient-workflow', async (data) => {
// Try enhanced processing
try {
return await services.enhanced.process(data)
} catch (error) {
// Fall back to basic processing
return await services.basic.process(data)
}
})Next Steps
- Architecture - Service architecture patterns
- Examples - Service composition examples
- Best Practices - Composition best practices