.do
ScaleServices-as-Software

Service Architecture

How Business-as-Code enables autonomous Services-as-Software architecture

Services-as-Software are built on Business-as-Code - semantic patterns that enable AI agents to understand, operate, and improve services autonomously.

Foundation: Business-as-Code

Traditional service architecture requires:

  • Imperative code telling systems how to execute
  • Custom data models that AI can't understand
  • Manual orchestration of service operations
  • Human operators to run the service

Services-as-Software architecture uses Business-as-Code to enable:

  • Semantic patterns expressing what things mean
  • Standardized vocabularies AI understands natively
  • Event-driven workflows that compose naturally
  • Autonomous AI agents that operate services 24/7

The Three Layers

Every Service-as-Software is built on three architectural layers:

graph TB subgraph "AI Layer - Intelligence & Automation" AI1[ai.analyze] AI2[ai.generate] AI3[ai.decide] end subgraph "Event Layer - What Happens" E1[on: Event Handlers] E2[send: Event Emitters] E3[workflow: Orchestration] end subgraph "Semantic Layer - What Things Mean" S1[$.Ticket] S2[$.Customer] S3[$.Resolution] S4[Schema.org Types] end AI1 --> E1 AI2 --> E2 AI3 --> E3 E1 --> S1 E2 --> S2 E3 --> S3 S1 & S2 & S3 --> S4

1. Semantic Layer - What things mean

// Semantic types from Schema.org
$.Ticket // AI knows this is a support ticket
$.Customer // AI knows this is a customer
$.Resolution // AI knows this is a resolution

2. Event Layer - What happens

// Events drive service operations
on($.Ticket.created, handler)
send($.Resolution.generated, data)

3. AI Layer - Intelligence & automation

// AI performs service operations
ai.analyze(ticket)
ai.generate(response)
ai.decide(escalation)

This architecture enables services to operate autonomously without human intervention.

Core Architecture Patterns

flowchart LR E1[$.Order.created] -->|Trigger| S1[Order Service] S1 -->|Validate| D1{Valid?} D1 -->|Yes| S2[Process Order] D1 -->|No| S3[Handle Invalid] S2 -->|Emit| E2[$.Order.processed] E2 -->|Trigger| S4[Fulfillment Service] S4 -->|Emit| E3[$.Shipment.created] E3 -->|Trigger| S5[Notification Service] S5 -->|Send| E4[Customer Notified]

Event-Driven Services

Services react to events in your business:

import { service, on } from 'sdk.do'

export default service({
  name: 'Order Fulfillment Service',

  // React to order events
  on: {
    '$.Order.created': async (event) => {
      // Validate inventory
      const available = await $.Inventory.check(event.data.items)

      if (available) {
        // Reserve inventory
        await $.Inventory.reserve(event.data.items)

        // Notify warehouse
        await $.Warehouse.send('fulfillment-request', event.data)

        // Update order status
        await event.data.update({ status: '$.Processing' })
      } else {
        // Notify customer of backorder
        await $.Email.send({
          to: event.data.customer,
          template: 'backorder-notification',
        })
      }
    },

    '$.Shipment.created': async (event) => {
      // Update customer with tracking
      await $.Email.send({
        to: event.data.customer,
        template: 'shipment-notification',
        data: { trackingNumber: event.data.tracking },
      })
    },
  },
})

AI-Powered Services

Leverage AI for intelligent decision-making:

// @errors: 7006
// @strict: true
import { service } from 'sdk.do'

type Comment = {
  $id: string
  content: string
  author: string
  update: (data: any) => Promise<void>
}

type ModerationResult = {
  violates: boolean
  needsReview: boolean
  reason?: string
  priority?: 'low' | 'medium' | 'high'
}

declare const $: any

// AI-powered service with full type safety
export default service({
  name: 'Content Moderation Service',

  on: {
    '$.Comment.created': async (event: { data: Comment }) => {
      // AI-powered content analysis - return type is inferred
      const analysis = await $.ai.generate({
        model: 'gpt-5',
        schema: $.ModerationResult,
        prompt: 'Analyze this content for policy violations',
        context: event.data,
      })
      //    ^?

      // Type-safe branching based on AI analysis
      if (analysis.violates) {
        // Quarantine content
        await event.data.update({
          status: '$.Quarantined',
          reason: analysis.reason,
        })

        // Notify moderators
        await $.send('$.Moderator.review-needed', {
          comment: event.data,
          analysis,
        })
      } else if (analysis.needsReview) {
        //              ^?
        // Flag for human review - priority is typed
        await $.send('$.Moderator.review-requested', {
          comment: event.data,
          priority: analysis.priority,
          //                 ^?
        })
      } else {
        // Auto-approve
        await event.data.update({ status: '$.Published' })
      }
    },
  },
})

Workflow Services

Orchestrate multi-step processes:

export default service({
  name: 'Onboarding Service',

  on: {
    '$.User.created': async (event) => {
      // Start onboarding workflow
      const workflow = await $.workflow('user-onboarding', {
        user: event.data,
      })

      // Step 1: Send welcome email
      await $.Email.send({
        to: event.data.email,
        template: 'welcome',
      })

      // Step 2: Create sample data
      await $.Database.seed({
        userId: event.data.id,
        template: 'starter',
      })

      // Step 3: Schedule follow-up tasks
      await $.schedule('3 days', async () => {
        await $.Email.send({
          to: event.data.email,
          template: 'onboarding-tips',
        })
      })

      await $.schedule('7 days', async () => {
        // Check engagement
        const activity = await $.Analytics.get({
          userId: event.data.id,
          metric: 'engagement',
        })

        if (activity.score < 0.3) {
          // Low engagement - offer help
          await $.Email.send({
            to: event.data.email,
            template: 'need-help',
          })
        }
      })
    },
  },
})

Architectural Components

Service Definition

Services are defined using the service() function:

import { service } from 'sdk.do'

export default service({
  // Service metadata
  name: 'My Service',
  description: 'Service description',
  version: '1.0.0',

  // Pricing model
  pricing: {
    model: 'subscription',
    plans: [
      /* ... */
    ],
  },

  // Event handlers
  on: {
    '$.Event.type': async (event) => {
      /* handler */
    },
  },

  // API endpoints
  api: {
    '/endpoint': async (req) => {
      /* handler */
    },
  },

  // Scheduled jobs
  scheduled: {
    daily: async () => {
      /* job */
    },
  },

  // Configuration
  config: {
    /* service config */
  },
})

Event Handlers

Services respond to semantic events:

on: {
  // Entity lifecycle events
  '$.User.created': async (event) => { /* ... */ },
  '$.User.updated': async (event) => { /* ... */ },
  '$.User.deleted': async (event) => { /* ... */ },

  // Business events
  '$.Order.placed': async (event) => { /* ... */ },
  '$.Payment.received': async (event) => { /* ... */ },
  '$.Subscription.cancelled': async (event) => { /* ... */ },

  // Custom events
  '$.Custom.event': async (event) => { /* ... */ },
}

API Endpoints

Expose HTTP endpoints:

api: {
  'GET /status': async (req) => {
    return { status: 'operational' }
  },

  'POST /process': async (req) => {
    const data = await req.json()
    const result = await processData(data)
    return { result }
  },

  'GET /metrics': async (req) => {
    const metrics = await $.Metric.query({
      service: 'my-service',
      timeRange: 'last_24_hours',
    })
    return { metrics }
  },
}

Scheduled Jobs

Run periodic tasks:

scheduled: {
  // Run every hour
  hourly: async () => {
    await performHourlyTasks()
  },

  // Run daily at 2am UTC
  daily: async () => {
    await performDailyTasks()
  },

  // Custom cron expression
  'custom': {
    cron: '0 */4 * * *',  // Every 4 hours
    handler: async () => {
      await performCustomTask()
    }
  }
}

Design Patterns

Autonomous Operation

Services should operate without human intervention:

export default service({
  name: 'Invoice Service',

  on: {
    '$.Subscription.renewed': async (event) => {
      // Generate invoice
      const invoice = await $.Invoice.create({
        subscription: event.data.id,
        amount: event.data.plan.price,
        dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
      })

      // Send to customer
      await $.Email.send({
        to: event.data.customer.email,
        template: 'invoice',
        data: { invoice },
      })

      // Schedule payment reminder
      await $.schedule(invoice.dueDate - 3 * 24 * 60 * 60 * 1000, async () => {
        if (!invoice.paid) {
          await $.Email.send({
            to: event.data.customer.email,
            template: 'payment-reminder',
            data: { invoice },
          })
        }
      })

      // Schedule overdue handling
      await $.schedule(invoice.dueDate + 1 * 24 * 60 * 60 * 1000, async () => {
        if (!invoice.paid) {
          await handleOverdueInvoice(invoice)
        }
      })
    },
  },
})

Error Handling and Resilience

Services should handle failures gracefully:

on: {
  '$.Order.created': async (event) => {
    try {
      // Attempt primary payment processor
      const payment = await $.Payment.charge({
        processor: 'stripe',
        amount: event.data.total,
        customer: event.data.customer,
      })
    } catch (error) {
      if (error.code === 'PROCESSOR_DOWN') {
        // Fallback to secondary processor
        const payment = await $.Payment.charge({
          processor: 'paypal',
          amount: event.data.total,
          customer: event.data.customer,
        })
      } else if (error.code === 'INSUFFICIENT_FUNDS') {
        // Notify customer
        await $.Email.send({
          to: event.data.customer.email,
          template: 'payment-failed',
          data: { reason: 'insufficient funds' },
        })
      } else {
        // Log error and retry later
        await $.log.error('Payment processing failed', { error, order: event.data })
        await $.schedule('1 hour', async () => {
          await $.send('$.Order.created', event.data)
        })
      }
    }
  },
}

Idempotency

Services should handle duplicate events safely:

on: {
  '$.Payment.received': async (event) => {
    // Check if already processed
    const existing = await $.Transaction.findOne({
      where: {
        paymentId: event.data.id,
        type: '$.Processed',
      },
    })

    if (existing) {
      // Already processed, skip
      return
    }

    // Process payment
    await $.Transaction.create({
      paymentId: event.data.id,
      amount: event.data.amount,
      type: '$.Processed',
    })

    // Fulfill order
    await $.Order.update(event.data.orderId, {
      status: '$.Paid',
    })
  },
}

Observability

Services should emit metrics and logs:

on: {
  '$.Request.received': async (event) => {
    const startTime = Date.now()

    try {
      // Process request
      const result = await processRequest(event.data)

      // Emit success metric
      await $.metric.increment('requests.success', {
        service: 'my-service',
        endpoint: event.data.endpoint,
      })

      // Emit latency metric
      await $.metric.histogram('requests.latency', Date.now() - startTime, {
        service: 'my-service',
        endpoint: event.data.endpoint,
      })

      return result
    } catch (error) {
      // Emit error metric
      await $.metric.increment('requests.error', {
        service: 'my-service',
        endpoint: event.data.endpoint,
        error: error.code,
      })

      // Log error
      await $.log.error('Request processing failed', {
        error,
        request: event.data,
      })

      throw error
    }
  },
}

Scaling Patterns

Horizontal Scaling

Services automatically scale horizontally:

  • Built on Cloudflare Workers
  • Scale from 0 to millions of requests
  • No infrastructure management
  • Pay only for usage
  • Global distribution (300+ locations)

Rate Limiting

Implement rate limiting to protect resources:

api: {
  'POST /api/process': async (req) => {
    const userId = req.headers.get('x-user-id')

    // Check rate limit
    const limit = await $.RateLimit.check({
      key: `user:${userId}`,
      limit: 100,  // 100 requests
      window: '1h',  // per hour
    })

    if (limit.exceeded) {
      return new Response('Rate limit exceeded', { status: 429 })
    }

    // Process request
    const result = await processRequest(await req.json())
    return { result }
  },
}

Caching

Optimize performance with caching:

on: {
  '$.Product.request': async (event) => {
    const cacheKey = `product:${event.data.id}`

    // Check cache
    let product = await $.cache.get(cacheKey)

    if (!product) {
      // Load from database
      product = await $.Product.findById(event.data.id)

      // Cache for 1 hour
      await $.cache.set(cacheKey, product, { ttl: 3600 })
    }

    return product
  },
}

Why This Architecture Matters

This architectural approach is what makes Services-as-Software fundamentally different from traditional Software-as-a-Service:

Traditional SaaS ArchitectureServices-as-Software Architecture
Imperative code (how to execute)Semantic patterns (what things mean)
Custom data modelsStandardized vocabularies (Schema.org)
Request/response APIsEvent-driven workflows
Humans operate the softwareAI agents operate the service
Configuration requiredWorks out of the box
Per-seat pricingPer-outcome pricing

The Paradigm Shift: Just as Infrastructure-as-Code enabled Software-as-a-Service, Business-as-Code enables Services-as-Software. See The Paradigm Shift for the full analogy.

Next Steps


Back: The Paradigm Shift · Next: Building Services →