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:
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 resolution2. 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
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 Architecture | Services-as-Software Architecture |
|---|---|
| Imperative code (how to execute) | Semantic patterns (what things mean) |
| Custom data models | Standardized vocabularies (Schema.org) |
| Request/response APIs | Event-driven workflows |
| Humans operate the software | AI agents operate the service |
| Configuration required | Works out of the box |
| Per-seat pricing | Per-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
- The Paradigm Shift - Understand how BaC unlocks Services-as-Software
- Building Services - Step-by-step guide to building services
- Deployment - Deploy services globally
- Best Practices - Service design guidelines
Back: The Paradigm Shift · Next: Building Services →