ScaleServices-as-Software
Building Services
How to create and develop Services-as-Software
Learn how to build Services-as-Software on the .do platform, from initial setup to production deployment.
flowchart TD
Start[Create Service] --> Define[Define Service Config]
Define --> Events[Add Event Handlers]
Events --> AI[Integrate AI]
AI --> Test[Write Tests]
Test --> Local[Test Locally]
Local --> Pass{Tests Pass?}
Pass -->|No| Fix[Fix Issues]
Fix --> Test
Pass -->|Yes| Deploy[Deploy to Staging]
Deploy --> Verify[Verify Staging]
Verify --> Prod[Deploy to Production]
Prod --> Monitor[Monitor & Iterate]
Quick Start
Create a new service in minutes:
# Create service from template
$ do create service customer-support
# Navigate to service directory
$ cd customer-support
# Start development server
$ do devYour service structure:
customer-support/
├── service.ts # Service definition
├── handlers/ # Event handlers
│ ├── ticket.ts
│ └── message.ts
├── lib/ # Shared utilities
│ ├── ai.ts
│ └── email.ts
├── tests/ # Test files
│ └── service.test.ts
├── package.json
└── do.config.ts # Service configurationService Definition
Define your service in service.ts:
import { service } from 'sdk.do'
export default service({
name: 'Customer Support Service',
description: 'AI-powered customer support automation',
version: '1.0.0',
// Pricing configuration
pricing: {
model: 'usage',
tiers: [
{ up_to: 1000, price_per_unit: 0.5 },
{ up_to: 10000, price_per_unit: 0.3 },
{ above: 10000, price_per_unit: 0.2 },
],
},
// Event handlers
on: {
'$.Ticket.created': './handlers/ticket',
'$.Message.received': './handlers/message',
},
// API endpoints
api: {
'GET /health': () => ({ status: 'ok' }),
'POST /analyze': './api/analyze',
},
// Configuration
config: {
aiModel: 'gpt-5',
responseTime: 'under-2-minutes',
escalationThreshold: 0.7,
},
})Event Handlers
Create event handlers in handlers/:
// handlers/ticket.ts
import { $ } from 'sdk.do'
export default async function handleTicketCreated(event) {
const ticket = event.data
// Categorize using AI
const category = await $.ai.generate({
model: 'gpt-5',
schema: $.TicketCategory,
prompt: `Categorize this support ticket`,
context: { ticket },
})
// Generate suggested response
const response = await $.ai.generate({
model: 'claude-sonnet-4.5',
prompt: `Generate a helpful response for this ticket`,
context: {
ticket,
category,
knowledge: await $.Knowledge.search(ticket.subject),
},
})
// Update ticket
await ticket.update({
category: category.name,
priority: category.priority,
suggestedResponse: response,
status: '$.AwaitingReview',
})
// Notify support team
await $.send('$.Support.ticket-categorized', {
ticket,
category,
})
}AI Integration
flowchart LR
Input[User Input] --> Context[Gather Context]
Context --> Model{AI Model}
Model -->|GPT-5| Gen1[Content Generation]
Model -->|Claude| Gen2[Sentiment Analysis]
Model -->|Llama-4| Gen3[Data Extraction]
Gen1 & Gen2 & Gen3 --> Process[Process Results]
Process --> Validate{Valid?}
Validate -->|Yes| Output[Return Results]
Validate -->|No| Retry[Retry/Fallback]
Retry --> Model
Integrate AI capabilities into services:
Content Generation
// Generate personalized responses
const response = await $.ai.generate({
model: 'gpt-5',
prompt: 'Write a professional response to this customer inquiry',
context: {
inquiry: ticket.message,
customerHistory: await $.Customer.history(ticket.customerId),
tone: 'friendly and helpful',
},
})Sentiment Analysis
// Analyze customer sentiment
const sentiment = await $.ai.generate({
model: 'gpt-5',
schema: {
sentiment: ['positive', 'neutral', 'negative', 'urgent'],
confidence: 'number',
concerns: 'string[]',
},
prompt: 'Analyze the sentiment and concerns in this message',
context: { message: ticket.message },
})
if (sentiment.sentiment === 'urgent') {
// Escalate immediately
await $.send('$.Support.escalate', {
ticket,
reason: 'urgent-sentiment',
})
}Structured Data Extraction
// Extract structured data from unstructured text
const orderInfo = await $.ai.generate({
model: 'gpt-5',
schema: $.OrderInquiry,
prompt: 'Extract order details from this message',
context: { message: ticket.message },
})
console.log(orderInfo)
// {
// orderId: 'ORD-12345',
// issue: 'delayed shipment',
// requestedAction: 'refund'
// }Database Integration
Store and query data:
// Create records
const ticket = await $.Ticket.create({
subject: 'Order not received',
message: 'I ordered on 10/1 but haven't received my package',
customerId: 'cust_123',
status: '$.Open',
})
// Query records
const openTickets = await $.Ticket.findMany({
where: {
status: '$.Open',
assignee: userId,
},
orderBy: { priority: 'desc' },
limit: 10,
})
// Update records
await ticket.update({
status: '$.Resolved',
resolvedAt: new Date(),
resolution: 'Refund processed',
})
// Delete records
await ticket.delete()Workflows
Orchestrate multi-step processes:
// Define workflow
const workflow = $.workflow('ticket-resolution', async (ticket) => {
// Step 1: Analyze
const analysis = await $.ai.generate({
model: 'gpt-5',
schema: $.TicketAnalysis,
prompt: 'Analyze this ticket',
context: { ticket },
})
// Step 2: Route based on analysis
if (analysis.needsHuman) {
await $.send('$.Support.assign', {
ticket,
priority: analysis.priority,
})
return { routed: 'human' }
}
// Step 3: Generate AI response
const response = await $.ai.generate({
model: 'claude-sonnet-4.5',
prompt: 'Generate solution',
context: { ticket, analysis },
})
// Step 4: Send response
await $.Email.send({
to: ticket.customer.email,
template: 'ticket-response',
data: { ticket, response },
})
// Step 5: Update ticket
await ticket.update({
status: '$.Resolved',
resolution: response,
resolvedBy: 'AI',
})
return { routed: 'ai', resolved: true }
})
// Execute workflow
const result = await workflow.run(ticket)API Endpoints
Expose HTTP endpoints:
// api/analyze.ts
export default async function analyzeTicket(req) {
const { ticketId } = await req.json()
const ticket = await $.Ticket.findById(ticketId)
if (!ticket) {
return new Response('Ticket not found', { status: 404 })
}
const analysis = await $.ai.generate({
model: 'gpt-5',
schema: $.TicketAnalysis,
prompt: 'Analyze this ticket',
context: { ticket },
})
return {
ticketId,
analysis,
recommendations: generateRecommendations(analysis),
}
}Testing
Write tests for your service:
// tests/service.test.ts
import { describe, it, expect, mock } from '@dotdo/test'
import service from '../service'
describe('Customer Support Service', () => {
it('categorizes tickets correctly', async () => {
const ticket = mock.ticket({
subject: 'Refund request',
message: 'I want my money back',
})
const result = await service.handle('$.Ticket.created', { data: ticket })
expect(result.category).toBe('billing')
expect(result.priority).toBe('high')
})
it('generates appropriate responses', async () => {
const ticket = mock.ticket({
subject: 'How do I reset my password?',
})
const response = await service.generateResponse(ticket)
expect(response).toContain('password reset')
expect(response).toContain('link')
})
it('escalates urgent tickets', async () => {
const ticket = mock.ticket({
message: 'URGENT: System is down and we are losing money!',
})
const result = await service.handle('$.Ticket.created', { data: ticket })
expect(result.escalated).toBe(true)
expect(result.priority).toBe('critical')
})
})Run tests:
$ do test
$ do test --watch
$ do test --coverageConfiguration
Configure your service:
// do.config.ts
export default {
service: {
name: 'customer-support',
region: 'auto', // or specific regions: ['us-east', 'eu-west']
env: {
AI_MODEL: 'gpt-5',
MAX_RESPONSE_TIME: '2m',
},
},
database: {
// Database configuration
collections: ['Ticket', 'Customer', 'Response'],
},
integrations: {
// Third-party integrations
stripe: {
apiKey: process.env.STRIPE_API_KEY,
},
sendgrid: {
apiKey: process.env.SENDGRID_API_KEY,
},
},
monitoring: {
// Monitoring configuration
alerts: {
errorRate: { threshold: 0.05, window: '5m' },
latency: { threshold: 2000, window: '1m' },
},
},
}Environment Variables
Manage secrets and configuration:
# Set environment variables
$ do env set STRIPE_API_KEY=sk_live_...
$ do env set SENDGRID_API_KEY=SG....
# List environment variables
$ do env list
# Remove environment variables
$ do env remove STRIPE_API_KEYAccess in code:
const apiKey = process.env.STRIPE_API_KEYLocal Development
Develop and test locally:
# Start development server
$ do dev
# Watch for changes
$ do dev --watch
# Test with local events
$ do emit '$.Ticket.created' --data ticket.json
# View logs
$ do logs --follow
# Test API endpoints
$ curl http://localhost:8787/healthDebugging
Debug your service:
// Add debug logging
import { $ } from 'sdk.do'
export default async function handler(event) {
// Log event
$.log.debug('Processing event', { event })
try {
const result = await processEvent(event)
// Log result
$.log.debug('Event processed', { result })
return result
} catch (error) {
// Log error
$.log.error('Error processing event', { error, event })
throw error
}
}View logs:
$ do logs --level debug
$ do logs --search "Processing event"
$ do logs --tail 100Code Organization
Organize your service code:
service/
├── service.ts # Service definition
├── handlers/ # Event handlers
│ ├── ticket/
│ │ ├── created.ts
│ │ ├── updated.ts
│ │ └── resolved.ts
│ └── message/
│ ├── received.ts
│ └── sent.ts
├── api/ # API endpoints
│ ├── analyze.ts
│ ├── metrics.ts
│ └── health.ts
├── lib/ # Shared code
│ ├── ai/
│ │ ├── categorize.ts
│ │ ├── respond.ts
│ │ └── sentiment.ts
│ ├── email/
│ │ └── send.ts
│ └── utils/
│ ├── validate.ts
│ └── format.ts
├── workflows/ # Workflows
│ ├── resolution.ts
│ └── escalation.ts
├── tests/ # Tests
│ ├── handlers/
│ ├── api/
│ └── workflows/
└── do.config.ts # ConfigurationBest Practices
Keep Handlers Focused
Each handler should do one thing well:
// Good: Focused handler
export default async function handleTicketCreated(event) {
await categorizeTicket(event.data)
await generateResponse(event.data)
await notifyTeam(event.data)
}
// Bad: Doing too much
export default async function handleEverything(event) {
// 500 lines of code...
}Use TypeScript
Type-safe services are better services:
import { Service, Event } from 'sdk.do'
interface TicketCreatedEvent extends Event {
data: {
id: string
subject: string
message: string
customerId: string
}
}
export default async function handleTicketCreated(event: TicketCreatedEvent) {
// TypeScript knows the shape of event.data
const { subject, message, customerId } = event.data
}Handle Errors Gracefully
Always handle potential failures:
export default async function handler(event) {
try {
return await processEvent(event)
} catch (error) {
if (error.retryable) {
// Retry later
await $.schedule('5 minutes', () => handler(event))
} else {
// Log and alert
await $.log.error('Unrecoverable error', { error, event })
await $.alert('service-error', { error, event })
}
}
}Test Thoroughly
Write comprehensive tests:
describe('Ticket Handler', () => {
it('handles normal tickets')
it('handles urgent tickets')
it('handles invalid tickets')
it('handles API failures')
it('handles rate limits')
it('handles concurrent requests')
})Next Steps
- Deployment - Deploy your service to production
- Monetization - Set up pricing and billing
- Examples - See complete service examples