Service Architecture
Design composable, autonomous services with clear boundaries and contracts
Service architecture on the .do platform focuses on creating composable, autonomous services that can be orchestrated through semantic patterns.
Services-as-Software
Unlike traditional microservices, Services-as-Software are:
- Semantic: Every interface follows
$.Subject.predicate.Objectpatterns - Autonomous: Services operate independently with their own data and logic
- Composable: Services can be combined in multiple ways
- Monetizable: Built-in billing and marketplace distribution
- Versionable: Services are versioned and can coexist
Architecture Principles
1. Clear Boundaries
Each service has a well-defined scope and responsibility:
// ✅ Good: Focused service
const EmailService = $.service({
name: 'Email Delivery Service',
operations: {
send: $.Email.send,
schedule: $.Email.schedule,
track: $.Email.track,
},
})
// ❌ Bad: Too broad
const CommunicationService = $.service({
name: 'Communication Service',
operations: {
sendEmail: $.Email.send,
sendSMS: $.SMS.send,
makeCall: $.Phone.call,
sendPush: $.Push.send,
},
})2. Explicit Contracts
Define clear input and output schemas:
import { Email, Person } from 'schema.org.ai'
const EmailService = $.service({
operations: {
send: {
input: {
to: { type: 'string', format: 'email', required: true },
from: { type: 'string', format: 'email', required: true },
subject: { type: 'string', required: true },
body: { type: 'string', required: true },
template: { type: 'string', required: false },
},
output: {
messageId: { type: 'string' },
status: { type: 'enum', values: ['sent', 'queued', 'failed'] },
timestamp: { type: 'datetime' },
},
},
},
})3. Loose Coupling
Services communicate through events and semantic patterns, not direct calls:
// Event-driven communication
on($.Order.created, async (order) => {
// Each service independently reacts
await send($.Email.send, {
to: order.customer.email,
template: 'order-confirmation',
data: order,
})
await send($.Inventory.reserve, {
items: order.items,
orderId: order.id,
})
await send($.Analytics.track, {
event: 'order_created',
properties: { orderId: order.id, total: order.total },
})
})4. Data Ownership
Each service owns its data and exposes it through well-defined operations:
// UserService owns user data
const UserService = $.service({
data: {
users: $.db.collection<Person>('users'),
sessions: $.db.collection('sessions'),
},
operations: {
// Public operations
getProfile: async (userId: string) => {
return await UserService.data.users.findOne({ id: userId })
},
updateProfile: async (userId: string, updates: Partial<Person>) => {
return await UserService.data.users.update({ id: userId }, updates)
},
// Internal operations (not exposed)
_hashPassword: (password: string) => {
// Internal logic
},
},
})Service Patterns
1. API Services
Expose HTTP endpoints for external consumption:
const APIService = $.service({
name: 'Product API',
type: 'api',
endpoints: {
'GET /products': async (c) => {
const products = await db.list($.Product)
return c.json(products)
},
'GET /products/:id': async (c) => {
const id = c.req.param('id')
const product = await db.get($.Product, id)
return c.json(product)
},
'POST /products': async (c) => {
const data = await c.req.json()
const product = await db.create($.Product, data)
return c.json(product, 201)
},
},
})2. Event Services
Process events and emit new events:
const OrderProcessingService = $.service({
name: 'Order Processing',
type: 'event',
handlers: {
[$.Order.created]: async (order) => {
// Validate order
const validation = await validateOrder(order)
if (!validation.valid) {
await send($.Order.rejected, { order, reason: validation.reason })
return
}
// Process payment
const payment = await processPayment(order)
if (payment.status === 'failed') {
await send($.Order.payment_failed, { order, payment })
return
}
// Fulfill order
await send($.Order.confirmed, { order, payment })
},
},
})3. Worker Services
Run background jobs and scheduled tasks:
const ReportingService = $.service({
name: 'Reporting Service',
type: 'worker',
jobs: {
dailyReport: {
schedule: '0 0 * * *', // Daily at midnight
handler: async () => {
const stats = await calculateDailyStats()
await send($.Report.generated, { type: 'daily', data: stats })
},
},
weeklyDigest: {
schedule: '0 9 * * 1', // Monday at 9am
handler: async () => {
const users = await db.list($.User, { where: { emailPrefs: 'weekly' } })
for (const user of users) {
await send($.Email.send, {
to: user.email,
template: 'weekly-digest',
data: await getUserDigest(user),
})
}
},
},
},
})4. Agent Services
AI-powered autonomous services:
const CustomerSupportAgent = $.service({
name: 'Customer Support Agent',
type: 'agent',
capabilities: {
answerQuestion: async (question: string, context: any) => {
const answer = await ai.generate({
model: 'gpt-5',
prompt: question,
context: context,
schema: $.Answer,
})
return answer
},
resolveIssue: async (issue: string, user: Person) => {
// Agent autonomously decides how to resolve
const resolution = await ai.agentic({
goal: 'Resolve customer issue',
tools: ['refund', 'replace', 'escalate'],
context: { issue, user },
})
return resolution
},
},
})Service Composition
1. Sequential Composition
Chain services together:
// Order -> Payment -> Fulfillment
const OrderFlow = $.compose([OrderValidationService, PaymentService, FulfillmentService])
on($.Order.created, async (order) => {
const result = await OrderFlow.execute(order)
await send($.Order.completed, result)
})2. Parallel Composition
Run services concurrently:
// Parallel processing
const EnrichmentPipeline = $.compose.parallel([EmailVerificationService, CompanyLookupService, SocialProfileService])
on($.Lead.captured, async (lead) => {
const enriched = await EnrichmentPipeline.execute(lead)
await send($.Lead.enriched, enriched)
})3. Conditional Composition
Route based on conditions:
const PaymentRouter = $.compose.conditional({
conditions: [
{ when: (order) => order.total < 100, use: BasicPaymentService },
{ when: (order) => order.total < 1000, use: StandardPaymentService },
{ when: (order) => order.total >= 1000, use: EnterprisePaymentService },
],
default: StandardPaymentService,
})Service Communication
1. Synchronous (RPC)
Direct service-to-service calls:
// Call another service
const user = await $.rpc.UserService.getProfile(userId)
const balance = await $.rpc.BillingService.getBalance(userId)2. Asynchronous (Events)
Loosely coupled event-driven communication:
// Emit event
await send($.User.registered, user)
// Multiple services can react
on($.User.registered, async (user) => {
// Email service
await send($.Email.welcome, user)
})
on($.User.registered, async (user) => {
// Analytics service
await send($.Analytics.track, { event: 'signup', user })
})3. Streaming
Real-time data streams:
const stream = $.stream.subscribe($.Order.status_changed)
for await (const event of stream) {
console.log('Order status:', event.data.status)
// React to status changes in real-time
}Service Deployment
1. Edge Deployment
Deploy to Cloudflare Workers:
export default $.service({
name: 'API Service',
deployment: {
target: 'edge',
regions: 'global',
},
})2. Regional Deployment
Deploy to specific regions:
export default $.service({
name: 'Data Processing',
deployment: {
target: 'regional',
regions: ['us-east', 'eu-west'],
},
})3. Hybrid Deployment
Combine edge and regional:
export default $.service({
name: 'E-commerce Platform',
deployment: {
api: { target: 'edge', regions: 'global' },
database: { target: 'regional', regions: ['us-east'] },
analytics: { target: 'edge', regions: 'global' },
},
})Service Versioning
1. Semantic Versioning
Follow semver for service versions:
const EmailService_v1 = $.service({
name: 'Email Service',
version: '1.0.0',
operations: {
send: $.Email.send,
},
})
const EmailService_v2 = $.service({
name: 'Email Service',
version: '2.0.0',
operations: {
send: $.Email.send,
schedule: $.Email.schedule, // New operation
},
})2. Gradual Migration
Support multiple versions simultaneously:
// Route based on caller version
const EmailService = $.service({
versions: {
'1.x': EmailService_v1,
'2.x': EmailService_v2,
},
})Service Monitoring
1. Health Checks
Define service health checks:
const service = $.service({
name: 'Payment Service',
health: {
check: async () => {
const dbOk = await checkDatabase()
const apiOk = await checkStripeAPI()
return {
status: dbOk && apiOk ? 'healthy' : 'unhealthy',
checks: { database: dbOk, stripe: apiOk },
}
},
interval: 30000, // Check every 30 seconds
},
})2. Metrics
Expose service metrics:
const service = $.service({
name: 'Order Service',
metrics: {
requests: $.metric.counter('requests_total'),
latency: $.metric.histogram('request_latency_ms'),
errors: $.metric.counter('errors_total'),
},
})Best Practices
Do's
- Keep services focused and single-purpose
- Use semantic patterns for all operations
- Define explicit contracts with schemas
- Version services properly
- Implement health checks and metrics
- Use events for loose coupling
- Document service interfaces
Don'ts
- Don't create monolithic services
- Don't share databases between services
- Don't couple services tightly
- Don't ignore versioning
- Don't skip error handling
- Don't forget to monitor services
Advanced Architecture Patterns
Service Mesh Pattern
Use a service mesh for sophisticated inter-service communication:
const ServiceMesh = $.mesh({
services: [
UserService,
OrderService,
PaymentService,
InventoryService,
],
policies: {
// Automatic retries
retry: {
maxAttempts: 3,
backoff: 'exponential',
timeout: 5000,
},
// Circuit breaking
circuitBreaker: {
threshold: 5,
timeout: 60000,
fallback: 'cached',
},
// Load balancing
loadBalancing: {
strategy: 'round-robin',
healthCheck: true,
},
// Service discovery
discovery: {
enabled: true,
ttl: 30000,
},
},
observability: {
tracing: true,
metrics: true,
logging: true,
},
})
// Use mesh for service communication
const order = await ServiceMesh.call(OrderService.getOrder, orderId)Event Sourcing Pattern
Store all changes as events for complete audit trail:
// Event store
const EventStore = $.service({
name: 'Event Store',
type: 'storage',
operations: {
append: async (stream: string, event: Event) => {
return await db.Events.create({
stream,
type: event.type,
data: event.data,
metadata: {
timestamp: new Date(),
version: await getStreamVersion(stream),
},
})
},
getStream: async (stream: string, fromVersion = 0) => {
return await db.Events.list({
where: {
stream,
'metadata.version': { $gte: fromVersion },
},
orderBy: { 'metadata.version': 'asc' },
})
},
project: async (stream: string, projection: Function) => {
const events = await EventStore.getStream(stream)
return events.reduce(projection, {})
},
},
})
// Define aggregate
const OrderAggregate = {
// Initial state
initial: () => ({
id: null,
status: 'draft',
items: [],
total: 0,
}),
// Apply events to state
apply: (state, event) => {
switch (event.type) {
case 'OrderCreated':
return { ...state, id: event.data.id, status: 'created' }
case 'ItemAdded':
return {
...state,
items: [...state.items, event.data.item],
total: state.total + event.data.item.price,
}
case 'OrderConfirmed':
return { ...state, status: 'confirmed' }
case 'OrderShipped':
return { ...state, status: 'shipped' }
default:
return state
}
},
}
// Use event sourcing
const orderId = 'order-123'
const orderState = await EventStore.project(orderId, OrderAggregate.apply)CQRS Pattern
Separate read and write models for optimal performance:
// Write model (Commands)
const OrderCommands = $.service({
name: 'Order Commands',
type: 'command',
commands: {
createOrder: async (data) => {
// Validate
const validation = await validateOrder(data)
if (!validation.valid) throw new Error(validation.error)
// Create order
const order = await db.Orders.create(data)
// Emit event
await send($.Order.created, order)
return { orderId: order.$id }
},
confirmOrder: async (orderId) => {
const order = await db.Orders.get(orderId)
if (order.status !== 'pending') {
throw new Error('Order cannot be confirmed')
}
await db.Orders.update(orderId, { status: 'confirmed' })
await send($.Order.confirmed, { orderId })
return { success: true }
},
},
})
// Read model (Queries)
const OrderQueries = $.service({
name: 'Order Queries',
type: 'query',
// Optimized read database
database: 'read-replica',
queries: {
getOrder: async (orderId) => {
return await readDb.Orders.get(orderId)
},
listOrders: async (filters) => {
return await readDb.Orders.list({
where: filters,
include: ['customer', 'items', 'payment'],
})
},
getOrderStats: async (customerId) => {
return await readDb.OrderStats.get(customerId)
},
},
})
// Projection: Update read model from events
on($.Order.created, async (order) => {
await readDb.Orders.create(order)
await readDb.OrderStats.increment(order.customerId, 'totalOrders')
})
on($.Order.confirmed, async ({ orderId }) => {
const order = await db.Orders.get(orderId)
await readDb.Orders.update(orderId, { status: 'confirmed' })
await readDb.OrderStats.add(order.customerId, 'revenue', order.total)
})Saga Pattern
Manage distributed transactions across services:
const OrderSaga = $.saga({
name: 'Order Processing Saga',
steps: [
{
name: 'reserve-inventory',
action: async (order) => {
return await send($.Inventory.reserve, {
items: order.items,
orderId: order.id,
})
},
compensation: async (order, reservation) => {
await send($.Inventory.release, { reservationId: reservation.id })
},
},
{
name: 'process-payment',
action: async (order) => {
return await send($.Payment.process, {
orderId: order.id,
amount: order.total,
customer: order.customerId,
})
},
compensation: async (order, payment) => {
await send($.Payment.refund, { paymentId: payment.id })
},
},
{
name: 'create-shipment',
action: async (order) => {
return await send($.Shipment.create, {
orderId: order.id,
items: order.items,
address: order.shippingAddress,
})
},
compensation: async (order, shipment) => {
await send($.Shipment.cancel, { shipmentId: shipment.id })
},
},
{
name: 'send-confirmation',
action: async (order) => {
await send($.Email.send, {
to: order.customer.email,
template: 'order-confirmation',
data: order,
})
},
compensation: async (order) => {
await send($.Email.send, {
to: order.customer.email,
template: 'order-cancelled',
data: order,
})
},
},
],
})
// Execute saga
on($.Order.created, async (order) => {
try {
await OrderSaga.execute(order)
await send($.Order.completed, { orderId: order.id })
} catch (error) {
// Saga automatically executes compensation steps
await send($.Order.failed, { orderId: order.id, error })
}
})Performance Optimization
Caching Strategies
Implement multi-level caching:
const CachedService = $.service({
name: 'Product Service',
cache: {
// L1: In-memory cache
l1: {
enabled: true,
ttl: 60000, // 1 minute
maxSize: 1000,
},
// L2: Distributed cache (Redis)
l2: {
enabled: true,
ttl: 3600000, // 1 hour
provider: 'redis',
},
// L3: CDN edge cache
l3: {
enabled: true,
ttl: 86400000, // 24 hours
provider: 'cloudflare',
},
},
operations: {
getProduct: async (productId: string) => {
// Automatic cache lookup and population
return await db.Products.get(productId)
},
listProducts: async (filters) => {
// Cache with dynamic keys
const cacheKey = `products:${JSON.stringify(filters)}`
return await db.Products.list(filters)
},
},
})
// Manual cache control
const product = await CachedService.getProduct('prod-123', {
cache: 'skip', // Bypass cache
})
// Invalidate cache
await CachedService.cache.invalidate('prod-123')
await CachedService.cache.invalidatePattern('products:*')Database Optimization
Optimize database access patterns:
const OptimizedService = $.service({
name: 'Order Service',
database: {
// Connection pooling
pool: {
min: 2,
max: 10,
idleTimeout: 30000,
},
// Read replicas
replicas: {
enabled: true,
strategy: 'round-robin',
endpoints: [
'replica-1.db.do',
'replica-2.db.do',
'replica-3.db.do',
],
},
// Query optimization
optimization: {
// Automatic index suggestions
indexHints: true,
// Query plan caching
planCache: true,
// Batch operations
batching: {
enabled: true,
maxBatchSize: 100,
maxWaitTime: 100,
},
},
},
operations: {
getOrders: async (filters) => {
// Use read replica for queries
return await db.Orders.list(filters, {
replica: true,
hint: 'idx_customer_status_date',
})
},
createOrders: async (orders) => {
// Batch insert for efficiency
return await db.Orders.createMany(orders, {
batch: true,
})
},
},
})Rate Limiting
Implement rate limiting for API protection:
const RateLimitedService = $.service({
name: 'API Service',
rateLimit: {
// Global rate limit
global: {
max: 1000,
window: 60000, // per minute
},
// Per-user rate limit
user: {
max: 100,
window: 60000,
},
// Per-endpoint rate limits
endpoints: {
'POST /orders': {
max: 10,
window: 60000,
},
'GET /products': {
max: 100,
window: 60000,
},
},
// Rate limit storage
storage: 'redis',
// Custom key generation
keyGenerator: (c) => {
return `ratelimit:${c.req.header('x-user-id')}:${c.req.path}`
},
},
endpoints: {
'POST /orders': async (c) => {
// Automatic rate limiting applied
const data = await c.req.json()
const order = await db.Orders.create(data)
return c.json(order)
},
},
})Security Patterns
Authentication Service
Implement secure authentication:
const AuthService = $.service({
name: 'Authentication Service',
security: {
jwt: {
secret: env.JWT_SECRET,
algorithm: 'RS256',
expiresIn: '1h',
refreshExpiresIn: '7d',
},
password: {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true,
algorithm: 'argon2',
},
mfa: {
enabled: true,
methods: ['totp', 'sms', 'email'],
},
},
operations: {
register: async (email: string, password: string) => {
// Validate password strength
const validation = await validatePassword(password)
if (!validation.valid) throw new Error(validation.error)
// Hash password
const hashedPassword = await hash(password)
// Create user
const user = await db.Users.create({
email,
password: hashedPassword,
verified: false,
})
// Send verification email
await send($.Email.sendVerification, { userId: user.$id })
return { userId: user.$id }
},
login: async (email: string, password: string) => {
// Find user
const user = await db.Users.findOne({ where: { email } })
if (!user) throw new Error('Invalid credentials')
// Verify password
const valid = await verify(password, user.password)
if (!valid) throw new Error('Invalid credentials')
// Check MFA
if (user.mfaEnabled) {
return { requiresMFA: true, userId: user.$id }
}
// Generate tokens
const accessToken = await generateJWT(user, '1h')
const refreshToken = await generateJWT(user, '7d')
return { accessToken, refreshToken, user }
},
verifyMFA: async (userId: string, code: string) => {
const user = await db.Users.get(userId)
const valid = await verifyTOTP(user.mfaSecret, code)
if (!valid) throw new Error('Invalid MFA code')
const accessToken = await generateJWT(user, '1h')
const refreshToken = await generateJWT(user, '7d')
return { accessToken, refreshToken, user }
},
},
})Authorization Service
Implement role-based access control (RBAC):
const AuthorizationService = $.service({
name: 'Authorization Service',
rbac: {
roles: {
admin: {
permissions: ['*'], // All permissions
},
manager: {
permissions: [
'orders:read',
'orders:create',
'orders:update',
'products:read',
'products:update',
'users:read',
],
},
user: {
permissions: [
'orders:read:own',
'orders:create:own',
'products:read',
],
},
},
// Resource-based permissions
resources: {
orders: {
read: ['admin', 'manager', 'user:own'],
create: ['admin', 'manager', 'user:own'],
update: ['admin', 'manager'],
delete: ['admin'],
},
products: {
read: ['*'],
create: ['admin', 'manager'],
update: ['admin', 'manager'],
delete: ['admin'],
},
},
},
operations: {
can: async (userId: string, permission: string, resource?: any) => {
const user = await db.Users.get(userId)
// Check role permissions
const role = user.role
const permissions = AuthorizationService.rbac.roles[role].permissions
// Wildcard permission
if (permissions.includes('*')) return true
// Exact match
if (permissions.includes(permission)) return true
// Resource ownership check
if (permission.endsWith(':own') && resource) {
const basePermission = permission.replace(':own', '')
if (permissions.includes(basePermission)) {
return resource.userId === userId || resource.ownerId === userId
}
}
return false
},
require: async (userId: string, permission: string, resource?: any) => {
const allowed = await AuthorizationService.can(userId, permission, resource)
if (!allowed) {
throw new Error(`Permission denied: ${permission}`)
}
},
},
})
// Use in services
const OrderService = $.service({
operations: {
getOrder: async (userId: string, orderId: string) => {
const order = await db.Orders.get(orderId)
// Check permission
await AuthorizationService.require(userId, 'orders:read', order)
return order
},
updateOrder: async (userId: string, orderId: string, updates: any) => {
const order = await db.Orders.get(orderId)
// Check permission
await AuthorizationService.require(userId, 'orders:update', order)
return await db.Orders.update(orderId, updates)
},
},
})Testing Services
Unit Testing
Test individual service operations:
import { describe, it, expect, beforeEach } from 'vitest'
import { UserService } from './user-service'
describe('UserService', () => {
beforeEach(async () => {
// Setup test database
await db.test.reset()
})
describe('createUser', () => {
it('should create a new user', async () => {
const user = await UserService.createUser({
email: '[email protected]',
name: 'Test User',
})
expect(user).toHaveProperty('$id')
expect(user.email).toBe('[email protected]')
expect(user.name).toBe('Test User')
})
it('should throw error for duplicate email', async () => {
await UserService.createUser({
email: '[email protected]',
name: 'User 1',
})
await expect(
UserService.createUser({
email: '[email protected]',
name: 'User 2',
})
).rejects.toThrow('Email already exists')
})
it('should validate email format', async () => {
await expect(
UserService.createUser({
email: 'invalid-email',
name: 'Test User',
})
).rejects.toThrow('Invalid email format')
})
})
describe('getUser', () => {
it('should retrieve existing user', async () => {
const created = await UserService.createUser({
email: '[email protected]',
name: 'Test User',
})
const user = await UserService.getUser(created.$id)
expect(user).toEqual(created)
})
it('should throw error for non-existent user', async () => {
await expect(
UserService.getUser('non-existent-id')
).rejects.toThrow('User not found')
})
})
})Integration Testing
Test service interactions:
import { describe, it, expect, beforeEach } from 'vitest'
import { OrderService, PaymentService, InventoryService } from './services'
describe('Order Processing Integration', () => {
beforeEach(async () => {
await db.test.reset()
await db.test.seed('products', 'inventory')
})
it('should process order end-to-end', async () => {
// Create order
const order = await OrderService.createOrder({
customerId: 'customer-1',
items: [
{ productId: 'product-1', quantity: 2 },
],
})
expect(order.status).toBe('pending')
// Process payment
const payment = await PaymentService.processPayment({
orderId: order.$id,
amount: order.total,
})
expect(payment.status).toBe('success')
// Check inventory was reserved
const inventory = await InventoryService.getReservation(order.$id)
expect(inventory.reserved).toBe(true)
// Verify order confirmed
const confirmed = await OrderService.getOrder(order.$id)
expect(confirmed.status).toBe('confirmed')
})
it('should rollback on payment failure', async () => {
const order = await OrderService.createOrder({
customerId: 'customer-1',
items: [{ productId: 'product-1', quantity: 2 }],
})
// Simulate payment failure
await expect(
PaymentService.processPayment({
orderId: order.$id,
amount: -1, // Invalid amount
})
).rejects.toThrow()
// Verify inventory was released
const inventory = await InventoryService.getReservation(order.$id)
expect(inventory).toBeNull()
// Verify order was cancelled
const cancelled = await OrderService.getOrder(order.$id)
expect(cancelled.status).toBe('cancelled')
})
})E2E Testing
Test complete user workflows:
import { describe, it, expect } from 'vitest'
import { test, expect as playwrightExpect } from '@playwright/test'
describe('E2E: Order Workflow', () => {
test('user can complete purchase', async ({ page }) => {
// Navigate to product page
await page.goto('/products/wireless-headphones')
// Add to cart
await page.click('[data-testid="add-to-cart"]')
await playwrightExpect(page.locator('.cart-count')).toHaveText('1')
// Go to checkout
await page.click('[data-testid="checkout"]')
// Fill shipping information
await page.fill('[name="name"]', 'John Doe')
await page.fill('[name="email"]', '[email protected]')
await page.fill('[name="address"]', '123 Main St')
await page.fill('[name="city"]', 'San Francisco')
await page.fill('[name="zip"]', '94102')
// Fill payment information
await page.fill('[name="cardNumber"]', '4242424242424242')
await page.fill('[name="expiry"]', '12/25')
await page.fill('[name="cvv"]', '123')
// Submit order
await page.click('[data-testid="submit-order"]')
// Verify confirmation
await playwrightExpect(page.locator('.order-confirmation')).toBeVisible()
await playwrightExpect(page.locator('.order-number')).toContainText('ORDER-')
// Verify email was sent
const email = await getLastEmail('[email protected]')
expect(email.subject).toBe('Order Confirmation')
expect(email.body).toContain('ORDER-')
})
})Monitoring and Observability
Distributed Tracing
Implement distributed tracing across services:
const TracedService = $.service({
name: 'Order Service',
tracing: {
enabled: true,
provider: 'opentelemetry',
// Sampling strategy
sampling: {
rate: 0.1, // Sample 10% of requests
alwaysSample: ['POST /orders'], // Always sample critical endpoints
},
// Custom attributes
attributes: {
'service.name': 'order-service',
'service.version': '1.0.0',
'deployment.environment': env.ENVIRONMENT,
},
},
operations: {
createOrder: async (data) => {
// Automatic span creation
const span = trace.currentSpan()
span.setAttribute('order.items.count', data.items.length)
span.setAttribute('order.total', data.total)
// Create order
const order = await db.Orders.create(data)
// Add span event
span.addEvent('order.created', {
'order.id': order.$id,
})
return order
},
},
})Metrics Collection
Collect and expose service metrics:
const MetricsService = $.service({
name: 'Metrics Service',
metrics: {
// Counter metrics
requestsTotal: $.metric.counter({
name: 'requests_total',
description: 'Total number of requests',
labels: ['method', 'path', 'status'],
}),
// Histogram metrics
requestDuration: $.metric.histogram({
name: 'request_duration_ms',
description: 'Request duration in milliseconds',
labels: ['method', 'path'],
buckets: [10, 50, 100, 500, 1000, 5000],
}),
// Gauge metrics
activeConnections: $.metric.gauge({
name: 'active_connections',
description: 'Number of active connections',
}),
// Summary metrics
orderValue: $.metric.summary({
name: 'order_value',
description: 'Order value distribution',
percentiles: [0.5, 0.9, 0.95, 0.99],
}),
},
middleware: [
// Automatic metrics collection
async (c, next) => {
const start = Date.now()
MetricsService.metrics.activeConnections.inc()
try {
await next()
const duration = Date.now() - start
MetricsService.metrics.requestsTotal.inc({
method: c.req.method,
path: c.req.path,
status: c.res.status,
})
MetricsService.metrics.requestDuration.observe(
{ method: c.req.method, path: c.req.path },
duration
)
} finally {
MetricsService.metrics.activeConnections.dec()
}
},
],
})
// Expose metrics endpoint
app.get('/metrics', async (c) => {
const metrics = await MetricsService.collect()
return c.text(metrics, 200, {
'Content-Type': 'text/plain; version=0.0.4',
})
})Logging
Implement structured logging:
const LoggingService = $.service({
name: 'Logging Service',
logging: {
level: env.LOG_LEVEL || 'info',
format: 'json',
// Log destinations
destinations: [
{ type: 'console' },
{ type: 'file', path: '/var/log/app.log' },
{ type: 'cloudwatch', logGroup: '/app/production' },
],
// Structured fields
fields: {
service: 'order-service',
environment: env.ENVIRONMENT,
version: env.VERSION,
},
// PII redaction
redact: ['email', 'password', 'ssn', 'creditCard'],
},
operations: {
log: (level: string, message: string, context?: any) => {
const log = {
timestamp: new Date().toISOString(),
level,
message,
...LoggingService.logging.fields,
...context,
}
// Redact sensitive fields
const redacted = redactPII(log, LoggingService.logging.redact)
// Send to destinations
LoggingService.logging.destinations.forEach((dest) => {
sendLog(dest, redacted)
})
},
},
})
// Use in services
const OrderService = $.service({
operations: {
createOrder: async (data) => {
LoggingService.log('info', 'Creating order', {
customerId: data.customerId,
itemCount: data.items.length,
total: data.total,
})
try {
const order = await db.Orders.create(data)
LoggingService.log('info', 'Order created successfully', {
orderId: order.$id,
})
return order
} catch (error) {
LoggingService.log('error', 'Failed to create order', {
error: error.message,
stack: error.stack,
customerId: data.customerId,
})
throw error
}
},
},
})Next Steps
- Data Modeling → - Design your data structures
- Workflows → - Orchestrate services
- Examples → - See complete architectures
Architecture Tip: Great service architectures are modular, semantic, and loosely coupled. Each service should be independently deployable and scalable.