Microservices Core Architecture Design
Microservices Core Components
Typical Microservices Architecture Diagram:
┌─────────────────────────────────────────────────┐
│ API Gateway │
│ (Bun.js-based aggregation layer, routing, load balancing) │
└───────────────┬─────────────────┬───────────────┘
│ │
┌───────────────▼─────┐ ┌─────────▼───────────────┐
│ User Service │ │ Order Service │
│ (Independent Bun.js process) │ (Independent Bun.js process) │
└───────────────┬─────┘ └─────────┬───────────────┘
│ │
┌───────────────▼─────────────────▼───────────────┐
│ Database Cluster/Message Queue │
│ (MySQL/PostgreSQL + Redis + RabbitMQ/Kafka) │
└─────────────────────────────────────────────────┘Service Splitting Principles:
- Business Capability-Oriented: Divide by business domains (user, order, payment)
- Data Independence: Each service owns its independent database
- Independent Deployment: Services communicate via lightweight protocols
- Fault Tolerance Boundaries: Failure of a single service does not affect the entire system
Communication Mechanism Design
gRPC Communication Implementation:
// proto/user.proto
syntax = "proto3";
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc CreateUser (CreateUserRequest) returns (UserResponse);
}
message UserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
}
// src/services/userServiceGrpc.ts
import { serve } from 'bun'
import { loadProto } from '@grpc/proto-loader'
import { UserServiceClient } from './generated/user_grpc_web_pb'
// Generate gRPC code (requires protobuf tools)
// protoc --js_out=import_style=commonjs,binary:. \
// --grpc-web_out=import_style=commonjs,mode=grpcwebtext:. \
// user.proto
const server = serve({
port: 50051,
grpc: {
services: {
UserService: {
GetUser: async (call) => {
const userId = call.request.getId()
// Fetch user from database
const user = await getUserFromDB(userId)
return { id: user.id, name: user.name, email: user.email }
},
CreateUser: async (call) => {
const { name, email } = call.request.toObject()
// Create user logic
const newUser = await createUserInDB({ name, email })
return { id: newUser.id, name: newUser.name, email: newUser.email }
}
}
}
}
})
console.log('gRPC User Service running on port 50051')RESTful Communication Implementation:
// src/services/orderService.ts
import { serve } from 'bun'
const orderService = serve({
port: 3002,
fetch(request) {
const url = new URL(request.url)
if (url.pathname === '/orders' && request.method === 'GET') {
return getOrders(request)
}
if (url.pathname === '/orders' && request.method === 'POST') {
return createOrder(request)
}
return new Response('Not Found', { status: 404 })
}
})
async function getOrders(request: Request) {
// JWT validation logic...
const orders = await fetchOrdersFromDB()
return new Response(JSON.stringify(orders), {
headers: { 'Content-Type': 'application/json' }
})
}
async function createOrder(request: Request) {
const orderData = await request.json()
// Business validation logic...
const newOrder = await createOrderInDB(orderData)
return new Response(JSON.stringify(newOrder), {
status: 201,
headers: { 'Content-Type': 'application/json' }
})
}
console.log('Order Service running on http://localhost:3002')Service Governance Implementation
Service Registration and Discovery
Consul-Based Service Registration:
// src/services/serviceRegistry.ts
import { serve } from 'bun'
import axios from 'axios'
const SERVICE_NAME = 'user-service'
const CONSUL_URL = 'http://localhost:8500'
// Register service on startup
async function registerService() {
const serviceInfo = {
Name: SERVICE_NAME,
ID: `${SERVICE_NAME}-${process.pid}`,
Address: 'localhost',
Port: 3001,
Check: {
HTTP: `http://localhost:3001/health`,
Interval: '10s',
Timeout: '5s'
}
}
try {
await axios.put(`${CONSUL_URL}/v1/agent/service/register`, serviceInfo)
console.log(`Service ${SERVICE_NAME} registered with Consul`)
} catch (err) {
console.error('Service registration failed:', err)
}
}
// Service health check endpoint
serve({
port: 3001,
fetch(request) {
if (request.url.endsWith('/health')) {
return new Response('OK', { status: 200 })
}
// ...other route handling
}
})
// Register service on startup
registerService()
// Deregister service on graceful shutdown
process.on('SIGTERM', async () => {
await axios.put(`${CONSUL_URL}/v1/agent/service/deregister/${SERVICE_NAME}-${process.pid}`)
process.exit(0)
})Client-Side Service Discovery:
// src/utils/serviceDiscovery.ts
import axios from 'axios'
const CONSUL_URL = 'http://localhost:8500'
export async function discoverService(serviceName: string) {
try {
const response = await axios.get(`${CONSUL_URL}/v1/catalog/service/${serviceName}`)
const instances = response.data
if (instances.length === 0) {
throw new Error(`No instances available for ${serviceName}`)
}
// Simple load balancing: random selection
const instance = instances[Math.floor(Math.random() * instances.length)]
return `http://${instance.ServiceAddress}:${instance.ServicePort}`
} catch (err) {
console.error('Service discovery failed:', err)
throw err
}
}
// Usage example
const userServiceUrl = await discoverService('user-service')
const response = await fetch(`${userServiceUrl}/users/123`)Load Balancing Strategies
Client-Side Load Balancing Implementation:
// src/loadBalancer/roundRobin.ts
type ServiceInstance = {
url: string
healthy: boolean
}
export class RoundRobinLoadBalancer {
private instances: ServiceInstance[]
private currentIndex = 0
constructor(instances: ServiceInstance[]) {
this.instances = instances
}
getNextInstance(): ServiceInstance {
let attempts = 0
while (attempts < this.instances.length) {
const instance = this.instances[this.currentIndex]
this.currentIndex = (this.currentIndex + 1) % this.instances.length
if (instance.healthy) {
return instance
}
attempts++
}
throw new Error('No healthy instances available')
}
async healthCheck() {
// Periodically check instance health
for (const instance of this.instances) {
try {
await fetch(`${instance.url}/health`)
instance.healthy = true
} catch {
instance.healthy = false
}
}
}
}
// Usage example
const instances = [
{ url: 'http://localhost:3001', healthy: true },
{ url: 'http://localhost:3002', healthy: true }
]
const loadBalancer = new RoundRobinLoadBalancer(instances)
async function callUserService() {
const instance = loadBalancer.getNextInstance()
const response = await fetch(`${instance.url}/users/123`)
return response.json()
}Circuit Breaking and Fault Tolerance
Circuit Breaker Pattern Implementation:
// src/circuitBreaker/circuitBreaker.ts
export class CircuitBreaker {
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
private failureCount = 0
private lastFailureTime = 0
private readonly resetTimeout: number
private readonly failureThreshold: number
constructor(
private readonly action: () => Promise<any>,
resetTimeout = 5000,
failureThreshold = 3
) {
this.resetTimeout = resetTimeout
this.failureThreshold = failureThreshold
}
async execute(): Promise<any> {
if (this.state === 'OPEN') {
const now = Date.now()
if (now - this.lastFailureTime > this.resetTimeout) {
this.state = 'HALF_OPEN'
} else {
throw new Error('Circuit breaker is OPEN')
}
}
try {
const result = await this.action()
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED'
this.failureCount = 0
}
return result
} catch (err) {
this.failureCount++
this.lastFailureTime = Date.now()
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN'
}
throw err
}
}
}
// Usage example
const breaker = new CircuitBreaker(async () => {
const response = await fetch('http://user-service/users/123')
if (!response.ok) throw new Error('Failed to fetch user')
return response.json()
}, 5000, 3)
try {
const user = await breaker.execute()
console.log(user)
} catch (err) {
console.error('Request failed:', err)
// Return fallback response
return { id: 'fallback', name: 'Default User' }
}Data Consistency Assurance
Distributed Transaction Patterns
Saga Pattern Implementation:
// src/saga/orchestrator.ts
export class OrderSagaOrchestrator {
async execute(orderData: any) {
try {
// Step 1: Create order (local transaction)
const order = await createOrder(orderData)
// Step 2: Reserve inventory (call inventory service)
await this.callInventoryService(order.items)
// Step 3: Deduct account balance (call payment service)
await this.callPaymentService(order.userId, order.totalAmount)
// All steps successful
await confirmOrder(order.id)
return { success: true }
} catch (err) {
// Execute compensating transaction
await this.compensate(orderData)
return { success: false, error: err }
}
}
private async callInventoryService(items: any[]) {
try {
const response = await fetch('http://inventory-service/reserve', {
method: 'POST',
body: JSON.stringify(items)
})
if (!response.ok) throw new Error('Inventory reservation failed')
} catch (err) {
throw new Error(`Inventory service error: ${err.message}`)
}
}
private async callPaymentService(userId: string, amount: number) {
try {
const response = await fetch('http://payment-service/charge', {
method: 'POST',
body: JSON.stringify({ userId, amount })
})
if (!response.ok) throw new Error('Payment failed')
} catch (err) {
throw new Error(`Payment service error: ${err.message}`)
}
}
private async compensate(orderData: any) {
// Compensation logic: cancel order, release inventory, etc.
await cancelOrder(orderData.id)
await releaseInventory(orderData.items)
}
}Event-Driven Architecture
Event Publish/Subscribe Implementation:
// src/eventBus/bunEventBus.ts
type EventHandler = (data: any) => Promise<void>
export class BunEventBus {
private handlers: Map<string, EventHandler[]> = new Map()
subscribe(eventType: string, handler: EventHandler) {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, [])
}
this.handlers.get(eventType)?.push(handler)
}
async publish(eventType: string, data: any) {
const handlers = this.handlers.get(eventType)
if (handlers) {
await Promise.all(handlers.map(handler => handler(data)))
}
}
}
// Usage example
const eventBus = new BunEventBus()
// Subscribe to user created event
eventBus.subscribe('user.created', async (userData) => {
console.log('Handling user created event:', userData)
// Send welcome email, initialize user preferences, etc.
})
// Publish event in user service
serve({
port: 3001,
fetch(request) {
if (request.url.endsWith('/users') && request.method === 'POST') {
const userData = await request.json()
// Save user to database...
// Publish event
eventBus.publish('user.created', userData)
return new Response(JSON.stringify({ id: '123' }), {
headers: { 'Content-Type': 'application/json' }
})
}
}
})Message Queue Integration (RabbitMQ):
// src/messageQueue/rabbitMq.ts
import { connect, Channel, Connection } from 'amqplib'
export class RabbitMQClient {
private connection: Connection
private channel: Channel
async connect() {
this.connection = await connect('amqp://localhost')
this.channel = await this.connection.createChannel()
}
async publish(exchange: string, routingKey: string, message: any) {
await this.channel.assertExchange(exchange, 'topic', { durable: true })
this.channel.publish(
exchange,
routingKey,
Buffer.from(JSON.stringify(message)),
{ persistent: true }
)
}
async consume(queue: string, handler: (msg: any) => Promise<void>) {
await this.channel.assertQueue(queue, { durable: true })
await this.channel.consume(queue, async (msg) => {
if (msg) {
try {
const message = JSON.parse(msg.content.toString())
await handler(message)
this.channel.ack(msg)
} catch (err) {
console.error('Message processing failed:', err)
this.channel.nack(msg, false, true) // Retry
}
}
})
}
}
// Usage example
const rabbitMQ = new RabbitMQClient()
await rabbitMQ.connect()
// Order service publishes order created event
await rabbitMQ.publish('order-events', 'order.created', {
orderId: '123',
userId: '456',
amount: 100
})
// Inventory service consumes event
await rabbitMQ.consume('inventory-queue', async (message) => {
console.log('Received order created event:', message)
// Deduct inventory logic...
})Observability and Monitoring
Metrics Monitoring System
Prometheus Metrics Exposure:
// src/metrics/prometheus.ts
import { serve } from 'bun'
import { Registry, Counter, Gauge, Histogram } from 'prom-client'
const registry = new Registry()
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics
collectDefaultMetrics({ registry })
// Custom metrics
const httpRequestsTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status'],
registry
})
const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'path'],
buckets: [0.1, 0.5, 1, 2, 5],
registry
})
// Middleware example
export function metricsMiddleware(ctx: any, next: () => Promise<any>) {
const start = Date.now()
const path = ctx.request.url
const method = ctx.request.method
return next().then(() => {
const duration = (Date.now() - start) / 1000
const status = ctx.response.status
httpRequestsTotal.inc({ method, path, status })
httpRequestDuration.observe({ method, path }, duration)
})
}
// Metrics endpoint
serve({
port: 9090,
fetch() {
return new Response(registry.metrics(), {
headers: { 'Content-Type': registry.contentType }
})
}
})
console.log('Prometheus metrics server running on port 9090')Distributed Tracing
OpenTelemetry Integration:
// src/tracing/opentelemetry.ts
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
// Create Tracer Provider
const provider = new NodeTracerProvider()
provider.register()
// Configure Jaeger exporter
const jaegerExporter = new JaegerExporter({
endpoint: 'http://localhost:14268/api/traces',
serviceName: 'bun-microservice'
})
provider.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter))
// Automatically instrument HTTP
registerInstrumentations({
instrumentations: [
new HttpInstrumentation({
requestHook: (span, request) => {
span.setAttribute('http.url', request.url)
span.setAttribute('http.method', request.method)
}
})
]
})
// Get Tracer instance
export const tracer = provider.getTracer('bun-microservice')
// Usage example
export async function createUser(ctx: any) {
const span = tracer.startSpan('createUser')
try {
// Business logic...
span.setAttribute('user.id', '123')
span.setStatus({ code: 0 }) // OK
return { id: '123' }
} catch (err) {
span.recordException(err)
span.setStatus({ code: 2, message: err.message })
throw err
} finally {
span.end()
}
}Deployment and Operations
Containerized Deployment
Docker Compose Orchestration:
# docker-compose.yml
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
environment:
- CONSUL_HOST=consul
depends_on:
- consul
user-service:
build: ./user-service
environment:
- CONSUL_HOST=consul
depends_on:
- consul
- mysql
order-service:
build: ./order-service
environment:
- CONSUL_HOST=consul
depends_on:
- consul
- mysql
- rabbitmq
inventory-service:
build: ./inventory-service
environment:
- CONSUL_HOST=consul
depends_on:
- consul
- mysql
consul:
image: consul:latest
ports:
- "8500:8500"
command: agent -dev -client=0.0.0.0
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: microservices
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
jaeger:
image: jaegertracing/all-in-one
ports:
- "16686:16686"
volumes:
mysql-data:Bun Service Dockerfile:
# Multi-stage build optimization
FROM node:18-alpine as builder
WORKDIR /app
COPY . .
RUN bun install --production
RUN bun build
# Production image
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json .
RUN bun install --production
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["bun", "run", "--bun", "dist/app.js"]CI/CD Pipeline
GitHub Actions Configuration:
# .github/workflows/deploy.yml
name: Deploy Microservices
on:
push:
branches: [main]
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: my-org/bun-microservice
jobs:
build-and-push:
runs-on: ubuntu-latest
strategy:
matrix:
service: ['api-gateway', 'user-service', 'order-service']
steps:
- uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push ${{ matrix.service }}
run: |
docker build -t ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}:${{ github.sha }} \
-f ./${{ matrix.service }}/Dockerfile ./${{ matrix.service }}
docker push ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}:${{ github.sha }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/${{ matrix.service }} \
${{ matrix.service }}=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.service }}:${{ github.sha }}
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
smoke-test:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run integration tests
run: |
npm install -g newman
newman run ./tests/api-tests.json \
--environment=./tests/envs/production.jsonSummary
Core advantages of Bun.js microservices architecture:
- Blazing Fast Startup: Bun’s cold start is 10-100x faster than Node.js, ideal for Serverless and frequent scaling scenarios
- Native Performance: Bun’s optimized JavaScript/TypeScript execution engine delivers higher throughput
- Unified Toolchain: Built-in testing, bundling, and HTTP server reduce dependency complexity
- Modern Protocol Support: Native support for gRPC, HTTP/2, and other modern communication protocols
Implementation Recommendations:
- Use Consul/Eureka for service discovery
- Leverage gRPC for high-performance inter-service communication
- Decouple services with event buses (RabbitMQ/Kafka)
- Manage distributed transactions with the Saga pattern
- Implement full-stack tracing with OpenTelemetry
- Use containerized deployment with Kubernetes orchestration
A microservices architecture built with Bun.js maximizes its performance advantages while maintaining system resilience and maintainability.



