.do

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

PatternPurposeComplexityUse When
Multi-ServiceParallel service executionIntermediateRunning independent services together
Service ChainingSequential service workflowsIntermediateOutput of one service feeds the next
Event-DrivenReactive service orchestrationAdvancedLoosely coupled, scalable systems
Saga PatternDistributed transactionsAdvancedMulti-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

graph LR Input[Input Data] --> S1[Service 1] Input --> S2[Service 2] Input --> S3[Service 3] S1 --> Agg[Aggregator] S2 --> Agg S3 --> Agg Agg --> Output[Combined Output]

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

  1. Timeout Management: Set appropriate timeouts for each service
  2. Partial Results: Return partial data when some services fail
  3. Result Aggregation: Define clear merging logic for results
  4. Cost Tracking: Track costs across all services
  5. 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

graph LR Input[Input] --> S1[Service 1] S1 --> |Output 1| S2[Service 2] S2 --> |Output 2| S3[Service 3] S3 --> |Output 3| Result[Final Result]

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

  1. Checkpoint Strategy: Save intermediate results for recovery
  2. Error Propagation: Decide what errors stop the pipeline
  3. Data Transformation: Clearly define input/output contracts
  4. Progress Tracking: Emit progress events for monitoring
  5. 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

graph TB E[Event Source] --> Bus[Event Bus] Bus --> S1[Service 1] Bus --> S2[Service 2] Bus --> S3[Service 3] S1 --> Bus2[Event Bus] S2 --> Bus2 S3 --> Bus2 Bus2 --> S4[Service 4]

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

  1. Event Design: Clear, versioned event schemas
  2. Idempotency: Handlers should be idempotent
  3. Ordering: Don't assume event order unless guaranteed
  4. Dead Letter Queue: Handle failed event processing
  5. 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

sequenceDiagram participant O as Orchestrator participant S1 as Service 1 participant S2 as Service 2 participant S3 as Service 3 O->>S1: Execute Step 1 S1-->>O: Success O->>S2: Execute Step 2 S2-->>O: Success O->>S3: Execute Step 3 S3-->>O: Failure O->>S2: Compensate Step 2 S2-->>O: Compensated O->>S1: Compensate Step 1 S1-->>O: Compensated

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

  1. Idempotency: All steps and compensations must be idempotent
  2. Timeout Handling: Set timeouts for each step
  3. Compensation Logic: Design careful rollback procedures
  4. State Persistence: Save saga state for recovery
  5. Manual Intervention: Plan for compensation failures

Complete Saga Pattern Guide →


Pattern Selection Guide

Decision Matrix

RequirementRecommended Pattern
Independent parallel tasksMulti-Service
Sequential data transformationService Chaining
Loose coupling neededEvent-Driven
Distributed transactionsSaga
Real-time reactivityEvent-Driven
Complex rollback logicSaga
Aggregate multiple sourcesMulti-Service
Long-running workflowsSaga + 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:

Or explore related topics: