Service Composition
Learn how to combine services to create more powerful offerings
Learn how to combine multiple services into more powerful, valuable offerings.
Why Compose Services?
Individual services are valuable, but composed services create exponentially more value:
- Increased Value: Bundle complementary services
- Simplified User Experience: One request, multiple capabilities
- Better Pricing: Package discounts and bundling
- Enhanced Capabilities: Services working together achieve more
Learn more advanced patterns:
- Multi-Service Coordination → - Coordinate multiple services
- Service Chaining → - Sequential execution patterns
- Event-Driven Orchestration → - Event-based coordination
- Saga Pattern → - Distributed transaction management
Composition Patterns
Sequential Composition
Services execute one after another, each using the previous service's output. See advanced sequential patterns →
import $, { db, on, send } from 'sdk.do'
// Define a sequential composition
const blogProductionService = await $.Service.create({
name: 'Blog Production Pipeline',
type: $.ServiceType.Composite,
compositionType: 'sequential',
// Define the sequence
pipeline: [
{
order: 1,
service: $.Service['Keyword Research'],
outputs: ['keywords', 'topics'],
},
{
order: 2,
service: $.Service['Content Writer'],
inputs: {
keywords: { from: 1, field: 'keywords' },
topic: { from: 1, field: 'topics[0]' },
},
outputs: ['content', 'title'],
},
{
order: 3,
service: $.Service['SEO Optimizer'],
inputs: {
content: { from: 2, field: 'content' },
keywords: { from: 1, field: 'keywords' },
},
outputs: ['optimizedContent', 'seoMetadata'],
},
{
order: 4,
service: $.Service['Grammar Checker'],
inputs: {
text: { from: 3, field: 'optimizedContent' },
},
outputs: ['finalContent'],
},
],
pricing: {
model: 'bundled',
baseRate: 150.0,
discount: 0.15, // 15% off individual services
},
})
// Execute sequential pipeline
on.ServiceRequest.created(async (request) => {
if (request.serviceId !== blogProductionService.id) return
const results = []
try {
for (const stage of blogProductionService.pipeline) {
// Prepare inputs for this stage
const stageInputs = prepareStageInputs(stage, results, request.inputs)
// Execute service
const result = await send.ServiceRequest.create({
serviceId: stage.service.id,
inputs: stageInputs,
parentRequest: request.id,
})
// Wait for completion
const output = await waitForServiceCompletion(result.id)
// Store result
results.push({
stage: stage.order,
service: stage.service.name,
output,
})
// Update progress
send.ServiceProgress.updated({
requestId: request.id,
stage: stage.order,
total: blogProductionService.pipeline.length,
progress: stage.order / blogProductionService.pipeline.length,
})
}
// Deliver final result
send.ServiceResult.deliver({
requestId: request.id,
outputs: {
content: results[results.length - 1].output.finalContent,
seoMetadata: results[2].output.seoMetadata,
keywords: results[0].output.keywords,
pipeline: results,
},
})
} catch (error) {
send.ServiceRequest.fail({
requestId: request.id,
error: error.message,
completedStages: results.length,
partialResults: results,
})
}
})
// Helper: Prepare inputs from previous stages
function prepareStageInputs(stage, previousResults, initialInputs) {
const inputs = {}
// Map inputs from previous stages
for (const [key, mapping] of Object.entries(stage.inputs || {})) {
if (mapping.from) {
const sourceResult = previousResults[mapping.from - 1]
inputs[key] = getNestedValue(sourceResult.output, mapping.field)
}
}
// Include initial inputs if needed
for (const [key, value] of Object.entries(initialInputs)) {
if (!inputs[key]) {
inputs[key] = value
}
}
return inputs
}Parallel Composition
Multiple services execute simultaneously. See advanced parallel patterns →
// Define parallel composition
const contentPackageService = await $.Service.create({
name: 'Content Package Generator',
type: $.ServiceType.Composite,
compositionType: 'parallel',
// Services run in parallel
services: [
{
service: $.Service['Blog Post Writer'],
required: true,
},
{
service: $.Service['Social Media Creator'],
required: false,
},
{
service: $.Service['Email Newsletter'],
required: false,
},
{
service: $.Service['Infographic Generator'],
required: false,
},
],
pricing: {
model: 'per-component',
rates: {
blog: 100.0,
social: 30.0,
email: 50.0,
infographic: 75.0,
},
},
})
// Execute services in parallel
on.ServiceRequest.created(async (request) => {
if (request.serviceId !== contentPackageService.id) return
try {
// Start all services concurrently
const promises = contentPackageService.services.map(async (component) => {
// Check if component was requested
if (component.required || request.inputs.components?.includes(component.service.name)) {
const result = await send.ServiceRequest.create({
serviceId: component.service.id,
inputs: request.inputs,
parentRequest: request.id,
})
return {
service: component.service.name,
result: await waitForServiceCompletion(result.id),
}
}
return null
})
// Wait for all to complete
const results = (await Promise.all(promises)).filter((r) => r !== null)
// Deliver combined results
send.ServiceResult.deliver({
requestId: request.id,
outputs: results.reduce((acc, r) => {
acc[r.service] = r.result
return acc
}, {}),
})
// Calculate total cost
const cost = results.reduce((total, r) => {
const componentKey = r.service.toLowerCase().split(' ')[0]
return total + contentPackageService.pricing.rates[componentKey]
}, 0)
send.Payment.charge({
customerId: request.customerId,
amount: cost,
breakdown: results.map((r) => ({
service: r.service,
amount: contentPackageService.pricing.rates[r.service.toLowerCase().split(' ')[0]],
})),
})
} catch (error) {
send.ServiceRequest.fail({
requestId: request.id,
error: error.message,
})
}
})Conditional Composition
Services execute based on conditions or outcomes. See event-driven patterns →
// Define conditional composition
const smartContentService = await $.Service.create({
name: 'Smart Content Creator',
type: $.ServiceType.Composite,
compositionType: 'conditional',
workflow: [
{
service: $.Service['Content Analyzer'],
always: true,
},
{
service: $.Service['Simple Writer'],
condition: 'analysis.complexity === "simple"',
},
{
service: $.Service['Advanced Writer'],
condition: 'analysis.complexity === "complex"',
},
{
service: $.Service['Translation'],
condition: 'request.inputs.translate === true',
},
{
service: $.Service['Quality Check'],
always: true,
},
],
})
// Execute with conditional logic
on.ServiceRequest.created(async (request) => {
if (request.serviceId !== smartContentService.id) return
const context = {
request: request.inputs,
results: {},
}
try {
for (const step of smartContentService.workflow) {
// Check if step should execute
if (step.always || evaluateCondition(step.condition, context)) {
const result = await send.ServiceRequest.create({
serviceId: step.service.id,
inputs: prepareInputs(step, context),
parentRequest: request.id,
})
const output = await waitForServiceCompletion(result.id)
context.results[step.service.name] = output
// Update context for next steps
if (step.service.name === 'Content Analyzer') {
context.analysis = output
}
}
}
// Deliver final result
send.ServiceResult.deliver({
requestId: request.id,
outputs: context.results,
})
} catch (error) {
send.ServiceRequest.fail({
requestId: request.id,
error: error.message,
context,
})
}
})
// Evaluate condition
function evaluateCondition(condition: string, context: any): boolean {
// Simple condition evaluation
// In production, use a safe expression evaluator
try {
const func = new Function('ctx', `with(ctx) { return ${condition} }`)
return func(context)
} catch {
return false
}
}Hierarchical Composition
Services organized in a tree structure:
// Define hierarchical composition
const enterpriseContentService = await $.Service.create({
name: 'Enterprise Content Suite',
type: $.ServiceType.Composite,
compositionType: 'hierarchical',
structure: {
root: {
service: $.Service['Content Strategy'],
children: [
{
service: $.Service['Blog Content'],
children: [{ service: $.Service['Blog Writer'] }, { service: $.Service['SEO Optimizer'] }, { service: $.Service['Image Generator'] }],
},
{
service: $.Service['Social Media'],
children: [{ service: $.Service['Twitter Posts'] }, { service: $.Service['LinkedIn Posts'] }, { service: $.Service['Instagram Content'] }],
},
{
service: $.Service['Email Marketing'],
children: [{ service: $.Service['Newsletter Writer'] }, { service: $.Service['Campaign Creator'] }],
},
],
},
},
})
// Execute hierarchically
async function executeHierarchical(node, context) {
// Execute current service
const result = await send.ServiceRequest.create({
serviceId: node.service.id,
inputs: context.inputs,
parentRequest: context.requestId,
})
const output = await waitForServiceCompletion(result.id)
// If has children, execute them with output as context
if (node.children) {
const childResults = await Promise.all(
node.children.map((child) =>
executeHierarchical(child, {
...context,
inputs: { ...context.inputs, ...output },
})
)
)
return {
service: node.service.name,
output,
children: childResults,
}
}
return {
service: node.service.name,
output,
}
}Service Dependencies
Declaring Dependencies
Services can declare their dependencies:
const advancedWriterService = await $.Service.create({
name: 'Advanced Content Writer',
type: $.ServiceType.ContentGeneration,
// Declare dependencies
dependencies: {
required: [$.Service['Grammar Checker']],
recommended: [$.Service['SEO Optimizer'], $.Service['Plagiarism Checker']],
optional: [$.Service['Image Generator'], $.Service['Translation']],
},
// Auto-invoke dependencies
autoInvoke: {
'Grammar Checker': 'always',
'SEO Optimizer': 'if-available',
'Plagiarism Checker': 'if-requested',
},
})Dependency Resolution
Automatically resolve and execute dependencies:
async function executeWithDependencies(service, request) {
const results = {}
// Execute required dependencies first
for (const dep of service.dependencies.required) {
const result = await send.ServiceRequest.create({
serviceId: dep.id,
inputs: request.inputs,
parentRequest: request.id,
})
results[dep.name] = await waitForServiceCompletion(result.id)
}
// Execute main service with dependency results
const mainResult = await service.execute({
...request.inputs,
dependencies: results,
})
// Execute recommended dependencies if configured
if (service.autoInvoke) {
for (const [depName, condition] of Object.entries(service.autoInvoke)) {
if (shouldInvoke(condition, request, mainResult)) {
const dep = service.dependencies.recommended.find((d) => d.name === depName)
if (dep) {
const result = await send.ServiceRequest.create({
serviceId: dep.id,
inputs: { ...request.inputs, content: mainResult.output },
parentRequest: request.id,
})
results[depName] = await waitForServiceCompletion(result.id)
}
}
}
}
return {
main: mainResult,
dependencies: results,
}
}Service Orchestration
Workflow Engine
Build a workflow engine for complex compositions:
class ServiceOrchestrator {
async execute(workflow, request) {
const context = {
requestId: request.id,
inputs: request.inputs,
results: new Map(),
state: {},
}
// Execute workflow steps
for (const step of workflow.steps) {
try {
const result = await this.executeStep(step, context)
context.results.set(step.id, result)
// Update state
if (step.stateUpdates) {
this.updateState(context.state, step.stateUpdates, result)
}
// Check exit conditions
if (step.exitIf && this.evaluateCondition(step.exitIf, context)) {
break
}
} catch (error) {
if (step.onError === 'continue') {
context.results.set(step.id, { error })
continue
} else if (step.onError === 'retry') {
// Implement retry logic
const retried = await this.retryStep(step, context, error)
context.results.set(step.id, retried)
} else {
throw error
}
}
}
return context.results
}
async executeStep(step, context) {
switch (step.type) {
case 'service':
return await this.executeService(step, context)
case 'parallel':
return await this.executeParallel(step.steps, context)
case 'condition':
return await this.executeConditional(step, context)
case 'loop':
return await this.executeLoop(step, context)
default:
throw new Error(`Unknown step type: ${step.type}`)
}
}
async executeService(step, context) {
const inputs = this.prepareInputs(step.inputs, context)
const result = await send.ServiceRequest.create({
serviceId: step.serviceId,
inputs,
parentRequest: context.requestId,
})
return await waitForServiceCompletion(result.id)
}
async executeParallel(steps, context) {
const promises = steps.map((step) => this.executeStep(step, context))
return await Promise.all(promises)
}
async executeConditional(step, context) {
if (this.evaluateCondition(step.condition, context)) {
return await this.executeStep(step.then, context)
} else if (step.else) {
return await this.executeStep(step.else, context)
}
return null
}
async executeLoop(step, context) {
const results = []
const items = this.resolveValue(step.items, context)
for (const item of items) {
const itemContext = { ...context, currentItem: item }
const result = await this.executeStep(step.do, itemContext)
results.push(result)
}
return results
}
}
// Usage
const orchestrator = new ServiceOrchestrator()
const complexWorkflow = {
steps: [
{
id: 'analyze',
type: 'service',
serviceId: $.Service['Content Analyzer'].id,
inputs: { content: '${inputs.content}' },
},
{
id: 'parallel-generation',
type: 'parallel',
steps: [
{
id: 'blog',
type: 'service',
serviceId: $.Service['Blog Writer'].id,
},
{
id: 'social',
type: 'service',
serviceId: $.Service['Social Creator'].id,
},
],
},
{
id: 'quality-check',
type: 'condition',
condition: '${results.blog.quality} < 0.8',
then: {
type: 'service',
serviceId: $.Service['Content Improver'].id,
},
},
],
}
const result = await orchestrator.execute(complexWorkflow, request)Service Marketplace
Publishing Composed Services
Make your compositions available to others:
// Publish to marketplace
await $.Service.publish({
serviceId: blogProductionService.id,
marketplace: {
visibility: 'public',
category: 'Content Marketing',
tags: ['blog', 'seo', 'content-creation'],
featured: true,
},
pricing: {
model: 'subscription',
tiers: [
{
name: 'Starter',
price: 99,
limits: { postsPerMonth: 10 },
},
{
name: 'Professional',
price: 299,
limits: { postsPerMonth: 50 },
},
{
name: 'Enterprise',
price: 999,
limits: { postsPerMonth: 200 },
},
],
},
documentation: {
description: 'Complete blog production pipeline from research to publication',
useCases: ['Content marketing', 'SEO optimization', 'Blog automation'],
examples: ['...'],
},
})Discovering Services
Find services to compose:
// Search for services
const services = await db.Service.query({
where: {
category: 'Content Marketing',
type: { in: ['ContentGeneration', 'SEO'] },
rating: { gte: 4.5 },
},
orderBy: { popularity: 'desc' },
})
// Find complementary services
const complementary = await ai.recommend({
model: 'gpt-5',
for: myService,
type: 'complementary-services',
criteria: ['enhances-value', 'compatible-io', 'popular'],
})
// Auto-compose recommendation
const suggested = await ai.composeServices({
goal: 'Create complete content marketing solution',
available: services,
constraints: {
maxCost: 500,
maxDuration: '10 minutes',
},
})Best Practices
1. Design for Composability
// Good: Clear inputs/outputs
const service = {
inputs: {
content: 'string',
keywords: 'string[]',
},
outputs: {
optimized: 'string',
score: 'number',
},
}
// Good: Minimal dependencies
const service = {
dependencies: {
required: [], // Self-contained
optional: [$.Service['Enhancement']],
},
}2. Handle Partial Failures
// Save partial results
on.ServiceRequest.fail(async (request) => {
if (request.partialResults) {
db.PartialResult.create({
requestId: request.id,
results: request.partialResults,
failedAt: request.failedStage,
})
// Offer resume option
send.Customer.notify({
customerId: request.customerId,
message: 'Service partially completed. Resume?',
action: { resume: request.id },
})
}
})3. Optimize for Performance
// Cache intermediate results
const cache = new Map()
async function executeWithCache(service, inputs) {
const cacheKey = `${service.id}:${hash(inputs)}`
if (cache.has(cacheKey)) {
return cache.get(cacheKey)
}
const result = await executeService(service, inputs)
cache.set(cacheKey, result)
return result
}
// Batch similar requests
const batcher = {
queue: [],
timer: null,
add(request) {
this.queue.push(request)
if (!this.timer) {
this.timer = setTimeout(() => this.flush(), 100)
}
},
async flush() {
const batch = this.queue.splice(0)
this.timer = null
// Execute as batch
const results = await executeBatch(batch)
// Distribute results
batch.forEach((req, i) => {
deliverResult(req.id, results[i])
})
},
}Next Steps
- Multi-Service Patterns → - Advanced coordination
- Service Chaining → - Sequential patterns
- Event-Driven Patterns → - Event-based orchestration
- Saga Pattern → - Distributed transactions
- Implement Monetization → - Price composed services
- Deploy Services → - Launch compositions
- Explore Examples → - See real compositions