Workshop: Microservices Architecture (5 hours) β
Build a complete microservices system with service discovery, API gateway, distributed data management, and orchestrated deployment. Create an e-commerce platform that demonstrates enterprise microservices patterns.
Workshop Overview β
Duration: 5 hours Difficulty: Advanced Result: Production-ready microservices system
You'll build MicroMart - an e-commerce platform with:
- ποΈ Multiple independent services
- π API Gateway with authentication
- π Event-driven architecture
- π Service discovery & load balancing
- π Distributed tracing & monitoring
- π Container orchestration
- πΎ Distributed data management
Prerequisites β
- Orchestre installed
- Docker and Docker Compose
- Basic microservices knowledge
- 5 hours of focused time
Part 1: Architecture Design (45 minutes) β
System Planning β
/orchestrate "Design a microservices e-commerce platform with:
- User service (authentication, profiles)
- Product catalog service
- Inventory service
- Cart service
- Order service
- Payment service
- Notification service
- Search service
Requirements:
- Each service independently deployable
- Event-driven communication
- API gateway for external access
- Service mesh for internal communication
- Distributed data (each service owns its data)
- Fault tolerance and resilience"Project Structure β
# Create the monorepo structure
mkdir micromart && cd micromart
# Initialize with service templates
/create services/user-service cloudflare-hono
/create services/product-service cloudflare-hono
/create services/order-service cloudflare-hono
/create services/payment-service cloudflare-hono
/create api-gateway cloudflare-honoArchitecture Overview β
Part 2: Core Services (1 hour) β
User Service β
cd services/user-service
/execute-task "Implement user service with JWT authentication and profile management"Implementation:
// services/user-service/src/index.ts
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import { EventBus } from '@micromart/event-bus'
const app = new Hono()
const eventBus = new EventBus()
// Health check for service discovery
app.get('/health', (c) => c.json({ status: 'healthy', service: 'user' }))
// User registration
app.post('/users/register', async (c) => {
const { email, password, name } = await c.req.json()
// Validate and hash password
const hashedPassword = await hashPassword(password)
// Create user
const user = await db.users.create({
id: generateId(),
email,
password: hashedPassword,
name,
createdAt: new Date()
})
// Emit event
await eventBus.publish('user.created', {
userId: user.id,
email: user.email,
timestamp: Date.now()
})
// Generate JWT
const token = await generateJWT(user)
return c.json({ user: sanitizeUser(user), token })
})
// User authentication
app.post('/users/login', async (c) => {
const { email, password } = await c.req.json()
const user = await db.users.findByEmail(email)
if (!user || !await verifyPassword(password, user.password)) {
return c.json({ error: 'Invalid credentials' }, 401)
}
const token = await generateJWT(user)
// Emit login event
await eventBus.publish('user.logged_in', {
userId: user.id,
timestamp: Date.now()
})
return c.json({ user: sanitizeUser(user), token })
})
// Protected routes
app.use('/users/*', jwt({ secret: process.env.JWT_SECRET }))
app.get('/users/profile', async (c) => {
const userId = c.get('jwtPayload').userId
const user = await db.users.findById(userId)
return c.json(sanitizeUser(user))
})
export default appProduct Service β
cd ../product-service
/execute-task "Create product catalog service with search and filtering"Product service implementation:
// services/product-service/src/index.ts
export const productService = new Hono()
// Product CRUD
productService.post('/products', authenticate('admin'), async (c) => {
const product = await c.req.json()
const created = await db.products.create({
...product,
id: generateId(),
createdAt: new Date()
})
// Update search index
await searchClient.index('products').addDocuments([created])
// Emit event
await eventBus.publish('product.created', {
productId: created.id,
price: created.price,
category: created.category
})
return c.json(created)
})
// Search with faceted filtering
productService.get('/products/search', async (c) => {
const {
q = '',
category,
minPrice,
maxPrice,
inStock,
page = 1,
limit = 20
} = c.req.query()
const searchParams = {
query: q,
filter: [],
facets: ['category', 'brand', 'price_range'],
page,
hitsPerPage: limit
}
if (category) searchParams.filter.push(`category = "${category}"`)
if (minPrice) searchParams.filter.push(`price >= ${minPrice}`)
if (maxPrice) searchParams.filter.push(`price <= ${maxPrice}`)
if (inStock) searchParams.filter.push('inventory > 0')
const results = await searchClient
.index('products')
.search(q, searchParams)
return c.json({
products: results.hits,
facets: results.facetDistribution,
total: results.totalHits,
page: results.page,
totalPages: results.totalPages
})
})
// Inventory check endpoint (for other services)
productService.post('/products/check-availability', async (c) => {
const { items } = await c.req.json() // [{productId, quantity}]
const availability = await Promise.all(
items.map(async item => {
const product = await db.products.findById(item.productId)
return {
productId: item.productId,
available: product.inventory >= item.quantity,
currentStock: product.inventory
}
})
)
return c.json(availability)
})Order Service β
cd ../order-service
/execute-task "Implement order service with saga pattern for distributed transactions"Order saga implementation:
// services/order-service/src/sagas/create-order.saga.ts
export class CreateOrderSaga {
private steps = [
{
name: 'validate-user',
service: 'user-service',
action: 'validateUser',
compensate: null
},
{
name: 'check-inventory',
service: 'product-service',
action: 'checkInventory',
compensate: null
},
{
name: 'reserve-inventory',
service: 'inventory-service',
action: 'reserveItems',
compensate: 'releaseItems'
},
{
name: 'process-payment',
service: 'payment-service',
action: 'processPayment',
compensate: 'refundPayment'
},
{
name: 'create-order',
service: 'order-service',
action: 'createOrder',
compensate: 'cancelOrder'
},
{
name: 'update-inventory',
service: 'inventory-service',
action: 'confirmReservation',
compensate: 'releaseItems'
},
{
name: 'send-notification',
service: 'notification-service',
action: 'sendOrderConfirmation',
compensate: null
}
]
async execute(orderData: CreateOrderRequest): Promise<Order> {
const completedSteps: CompletedStep[] = []
try {
for (const step of this.steps) {
const result = await this.executeStep(step, orderData)
completedSteps.push({ step, result })
// Pass results to next steps
orderData = { ...orderData, ...result }
}
return orderData.order
} catch (error) {
// Compensate in reverse order
await this.compensate(completedSteps.reverse())
throw new SagaFailedError('Order creation failed', error)
}
}
private async executeStep(step: SagaStep, data: any) {
const service = this.serviceClient.get(step.service)
return await service.call(step.action, data)
}
private async compensate(completedSteps: CompletedStep[]) {
for (const { step, result } of completedSteps) {
if (step.compensate) {
try {
const service = this.serviceClient.get(step.service)
await service.call(step.compensate, result)
} catch (error) {
// Log compensation failure
console.error(`Compensation failed for ${step.name}`, error)
}
}
}
}
}Part 3: Infrastructure Services (1 hour) β
API Gateway β
cd ../../api-gateway
/execute-task "Create API gateway with authentication, rate limiting, and request routing"Gateway implementation:
// api-gateway/src/index.ts
import { Hono } from 'hono'
import { ServiceRegistry } from './service-registry'
import { RateLimiter } from './rate-limiter'
import { CircuitBreaker } from './circuit-breaker'
const gateway = new Hono()
const registry = new ServiceRegistry()
const rateLimiter = new RateLimiter()
// Middleware
gateway.use('*', async (c, next) => {
// Rate limiting
const ip = c.req.header('cf-connecting-ip')
if (!await rateLimiter.checkLimit(ip)) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
// Add tracing headers
c.req.header('x-request-id', generateRequestId())
c.req.header('x-trace-id', generateTraceId())
await next()
})
// Service discovery and routing
gateway.all('/api/:service/*', async (c) => {
const service = c.req.param('service')
const path = c.req.path.replace(`/api/${service}`, '')
// Get healthy service instance
const instance = await registry.getHealthyInstance(service)
if (!instance) {
return c.json({ error: 'Service unavailable' }, 503)
}
// Circuit breaker
const breaker = CircuitBreaker.for(service)
try {
const response = await breaker.call(async () => {
return await fetch(`${instance.url}${path}`, {
method: c.req.method,
headers: {
...c.req.headers,
'x-gateway-auth': process.env.INTERNAL_AUTH_KEY
},
body: c.req.body
})
})
return new Response(response.body, {
status: response.status,
headers: response.headers
})
} catch (error) {
return c.json({
error: 'Service error',
service,
details: error.message
}, 503)
}
})
// Health check endpoint
gateway.get('/health', async (c) => {
const services = await registry.getAllServices()
const health = await Promise.all(
services.map(async service => ({
name: service.name,
status: await registry.checkHealth(service),
instances: service.instances.length
}))
)
return c.json({
gateway: 'healthy',
services: health
})
})
export default gatewayEvent Bus β
/execute-task "Implement event bus for asynchronous service communication"Event bus implementation:
// shared/event-bus/src/index.ts
export class EventBus {
private broker: KafkaClient
private handlers: Map<string, EventHandler[]> = new Map()
async publish(eventType: string, data: any) {
const event = {
id: generateId(),
type: eventType,
data,
timestamp: Date.now(),
source: process.env.SERVICE_NAME
}
await this.broker.send({
topic: this.getTopicForEvent(eventType),
messages: [{
key: event.id,
value: JSON.stringify(event),
headers: {
'event-type': eventType,
'correlation-id': getCurrentCorrelationId()
}
}]
})
// Also emit locally for same-service handlers
await this.emitLocal(event)
}
subscribe(eventPattern: string, handler: EventHandler) {
const regex = new RegExp(eventPattern.replace('*', '.*'))
if (!this.handlers.has(eventPattern)) {
this.handlers.set(eventPattern, [])
// Subscribe to Kafka topic
this.broker.subscribe({
topic: this.getTopicForPattern(eventPattern),
fromBeginning: false
})
}
this.handlers.get(eventPattern)!.push(handler)
}
private async handleMessage(message: KafkaMessage) {
const event = JSON.parse(message.value.toString())
for (const [pattern, handlers] of this.handlers) {
if (new RegExp(pattern).test(event.type)) {
await Promise.all(
handlers.map(handler =>
this.executeHandler(handler, event)
)
)
}
}
}
private async executeHandler(handler: EventHandler, event: Event) {
try {
await handler(event)
} catch (error) {
// Retry logic
await this.retryHandler(handler, event, error)
}
}
}Service Discovery β
/execute-task "Create service registry with health checking"Part 4: Data Management (45 minutes) β
Distributed Data Patterns β
/orchestrate "Implement distributed data patterns:
- Each service owns its data
- Event sourcing for order service
- CQRS for product catalog
- Cache-aside pattern
- Eventual consistency"Event Sourcing Implementation β
// services/order-service/src/event-store.ts
export class EventStore {
async append(streamId: string, events: DomainEvent[]) {
const stream = await this.getStream(streamId)
for (const event of events) {
await this.db.events.create({
streamId,
eventId: generateId(),
eventType: event.type,
eventData: event.data,
eventVersion: stream.version + 1,
timestamp: new Date()
})
stream.version++
}
// Publish to event bus
await this.eventBus.publishBatch(events)
}
async getEvents(streamId: string, fromVersion = 0) {
return await this.db.events.findMany({
where: {
streamId,
eventVersion: { gte: fromVersion }
},
orderBy: { eventVersion: 'asc' }
})
}
async getSnapshot(streamId: string) {
const snapshot = await this.db.snapshots.findLatest(streamId)
const events = await this.getEvents(streamId, snapshot?.version || 0)
return {
snapshot,
events
}
}
}
// Order aggregate using event sourcing
export class Order {
private events: DomainEvent[] = []
private version = 0
static async load(id: string): Promise<Order> {
const { snapshot, events } = await eventStore.getSnapshot(id)
const order = new Order(id)
if (snapshot) {
order.applySnapshot(snapshot)
}
for (const event of events) {
order.apply(event)
}
return order
}
createOrder(data: CreateOrderData) {
this.addEvent({
type: 'OrderCreated',
data: {
orderId: this.id,
userId: data.userId,
items: data.items,
total: data.total
}
})
}
private apply(event: DomainEvent) {
switch (event.type) {
case 'OrderCreated':
this.status = 'created'
this.items = event.data.items
this.total = event.data.total
break
case 'OrderPaid':
this.status = 'paid'
this.paymentId = event.data.paymentId
break
case 'OrderShipped':
this.status = 'shipped'
this.trackingNumber = event.data.trackingNumber
break
}
this.version++
}
async save() {
await eventStore.append(this.id, this.events)
this.events = []
}
}CQRS Implementation β
/execute-task "Implement CQRS for product catalog with separate read/write models"Part 5: Resilience Patterns (45 minutes) β
Circuit Breaker β
// shared/resilience/circuit-breaker.ts
export class CircuitBreaker {
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
private failures = 0
private successCount = 0
private lastFailureTime?: Date
constructor(
private options: {
failureThreshold: number
recoveryTimeout: number
halfOpenRequests: number
}
) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (this.shouldAttemptReset()) {
this.state = 'HALF_OPEN'
this.successCount = 0
} else {
throw new CircuitOpenError('Circuit breaker is OPEN')
}
}
try {
const result = await fn()
this.onSuccess()
return result
} catch (error) {
this.onFailure()
throw error
}
}
private onSuccess() {
this.failures = 0
if (this.state === 'HALF_OPEN') {
this.successCount++
if (this.successCount >= this.options.halfOpenRequests) {
this.state = 'CLOSED'
}
}
}
private onFailure() {
this.failures++
this.lastFailureTime = new Date()
if (this.failures >= this.options.failureThreshold) {
this.state = 'OPEN'
}
}
}Retry with Backoff β
// shared/resilience/retry.ts
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
options: {
maxRetries: number
initialDelay: number
maxDelay: number
factor: number
}
): Promise<T> {
let lastError: Error
for (let i = 0; i < options.maxRetries; i++) {
try {
return await fn()
} catch (error) {
lastError = error
if (i < options.maxRetries - 1) {
const delay = Math.min(
options.initialDelay * Math.pow(options.factor, i),
options.maxDelay
)
await sleep(delay + Math.random() * delay * 0.1) // Jitter
}
}
}
throw lastError!
}Bulkhead Pattern β
// shared/resilience/bulkhead.ts
export class Bulkhead {
private queue: Array<() => void> = []
private running = 0
constructor(private concurrency: number) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve))
}
this.running++
try {
return await fn()
} finally {
this.running--
const next = this.queue.shift()
if (next) next()
}
}
}Part 6: Deployment & Monitoring (45 minutes) β
Docker Compose β
# docker-compose.yml
version: '3.8'
services:
# API Gateway
gateway:
build: ./api-gateway
ports:
- "8080:8080"
environment:
- SERVICE_REGISTRY_URL=http://consul:8500
- TRACING_ENDPOINT=http://jaeger:14268
depends_on:
- consul
- jaeger
# User Service
user-service:
build: ./services/user-service
environment:
- DB_URL=postgresql://postgres:password@user-db:5432/users
- KAFKA_BROKERS=kafka:9092
- SERVICE_NAME=user-service
depends_on:
- user-db
- kafka
# Product Service
product-service:
build: ./services/product-service
environment:
- DB_URL=postgresql://postgres:password@product-db:5432/products
- SEARCH_URL=http://elasticsearch:9200
- KAFKA_BROKERS=kafka:9092
depends_on:
- product-db
- elasticsearch
- kafka
# Order Service with Event Store
order-service:
build: ./services/order-service
environment:
- EVENT_STORE_URL=postgresql://postgres:password@event-store:5432/events
- KAFKA_BROKERS=kafka:9092
depends_on:
- event-store
- kafka
# Databases
user-db:
image: postgres:15
environment:
- POSTGRES_DB=users
- POSTGRES_PASSWORD=password
volumes:
- user-data:/var/lib/postgresql/data
product-db:
image: postgres:15
environment:
- POSTGRES_DB=products
- POSTGRES_PASSWORD=password
volumes:
- product-data:/var/lib/postgresql/data
event-store:
image: postgres:15
environment:
- POSTGRES_DB=events
- POSTGRES_PASSWORD=password
volumes:
- event-data:/var/lib/postgresql/data
# Infrastructure
kafka:
image: confluentinc/cp-kafka:latest
environment:
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
consul:
image: consul:latest
ports:
- "8500:8500"
command: consul agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"
- "14268:14268"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
user-data:
product-data:
event-data:Kubernetes Deployment β
/execute-task "Create Kubernetes manifests for production deployment"# k8s/user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: micromart/user-service:latest
ports:
- containerPort: 8080
env:
- name: DB_URL
valueFrom:
secretKeyRef:
name: user-db-secret
key: url
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: user-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: user-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80Monitoring Setup β
// shared/monitoring/metrics.ts
import { Registry, Counter, Histogram, Gauge } from 'prom-client'
export class Metrics {
private registry = new Registry()
// HTTP metrics
httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
})
httpRequestTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
})
// Business metrics
ordersCreated = new Counter({
name: 'orders_created_total',
help: 'Total number of orders created',
labelNames: ['payment_method', 'source']
})
orderValue = new Histogram({
name: 'order_value_dollars',
help: 'Order value in dollars',
buckets: [10, 50, 100, 500, 1000, 5000]
})
// Service health
serviceHealth = new Gauge({
name: 'service_health',
help: 'Service health status (1 = healthy, 0 = unhealthy)',
labelNames: ['service']
})
constructor() {
this.registry.registerMetric(this.httpRequestDuration)
this.registry.registerMetric(this.httpRequestTotal)
this.registry.registerMetric(this.ordersCreated)
this.registry.registerMetric(this.orderValue)
this.registry.registerMetric(this.serviceHealth)
}
async getMetrics() {
return this.registry.metrics()
}
}Testing the System β
End-to-End Test β
# 1. Start all services
docker-compose up -d
# 2. Register a user
curl -X POST http://localhost:8080/api/user/users/register \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "secure123", "name": "Test User"}'
# 3. Search products
curl http://localhost:8080/api/product/products/search?q=laptop&category=electronics
# 4. Create order
curl -X POST http://localhost:8080/api/order/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"productId": "prod123", "quantity": 1}
],
"shippingAddress": {...}
}'
# 5. Check service health
curl http://localhost:8080/healthLoad Testing β
/execute-task "Create load testing scenarios for microservices"// k6-load-test.js
import http from 'k6/http'
import { check, sleep } from 'k6'
export let options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 500 },
{ duration: '2m', target: 1000 },
{ duration: '5m', target: 1000 },
{ duration: '2m', target: 0 }
],
thresholds: {
http_req_duration: ['p(99)<500'],
http_req_failed: ['rate<0.1']
}
}
export default function() {
// User registration
let registerRes = http.post(
'http://localhost:8080/api/user/users/register',
JSON.stringify({
email: `user${Date.now()}@example.com`,
password: 'test123',
name: 'Load Test User'
}),
{ headers: { 'Content-Type': 'application/json' } }
)
check(registerRes, {
'registration successful': (r) => r.status === 200
})
let token = registerRes.json('token')
// Search products
let searchRes = http.get(
'http://localhost:8080/api/product/products/search?q=test'
)
check(searchRes, {
'search successful': (r) => r.status === 200
})
// Create order
let orderRes = http.post(
'http://localhost:8080/api/order/orders',
JSON.stringify({
items: [{ productId: 'test-product', quantity: 1 }]
}),
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}
)
check(orderRes, {
'order created': (r) => r.status === 201
})
sleep(1)
}Production Considerations β
Security β
- mTLS between services
- API Gateway authentication
- Secrets management
- Network policies
Scalability β
- Horizontal pod autoscaling
- Database sharding
- Caching strategies
- CDN integration
Reliability β
- Circuit breakers
- Retry mechanisms
- Health checks
- Graceful shutdowns
Observability β
- Distributed tracing
- Centralized logging
- Metrics aggregation
- Alerting rules
What You've Built β
β Complete microservices architecture β Event-driven communication β Service discovery & load balancing β Distributed data management β Resilience patterns β Production-ready deployment β Comprehensive monitoring
Next Steps β
Your microservices platform is ready! Consider:
- Service Mesh: Add Istio for advanced traffic management
- CI/CD: Implement GitOps with ArgoCD
- Security: Add OAuth2/OIDC with Keycloak
- Data Pipeline: Add Apache Kafka for streaming
- Machine Learning: Add recommendation service
- Edge Computing: Deploy services to edge locations
Resources β
Congratulations! You've built a production-grade microservices system in 5 hours!
