Enterprise Architecture Patterns
A comprehensive skill for mastering enterprise architecture patterns, distributed systems design, and scalable application development. This skill covers strategic and tactical patterns for building robust, maintainable, and scalable enterprise systems.
When to Use This Skill
Use this skill when:
Designing microservices architectures for distributed systems Implementing domain-driven design (DDD) in complex business domains Building event-driven architectures with event sourcing and CQRS Designing saga patterns for distributed transactions Implementing API gateways and service mesh architectures Scaling applications horizontally and vertically Building resilient systems with fault tolerance Migrating monoliths to microservices Designing multi-tenant SaaS architectures Implementing backend-for-frontend (BFF) patterns Building real-time systems with event streaming Architecting cloud-native applications Core Architectural Concepts System Design Fundamentals
Separation of Concerns Divide systems into distinct sections where each section addresses a separate concern, reducing coupling and increasing cohesion.
Modularity Design systems as collections of independent modules that can be developed, tested, and deployed separately.
Abstraction Hide complex implementation details behind simple interfaces, making systems easier to understand and modify.
Scalability Dimensions
Horizontal scaling: Add more machines/instances Vertical scaling: Add more resources to existing machines Data scaling: Partition data across multiple stores Functional scaling: Decompose by business capability
Consistency Models
Strong consistency: All nodes see the same data at the same time Eventual consistency: All nodes will eventually see the same data Causal consistency: Related operations see consistent state Read-your-writes consistency: Users see their own updates immediately
CAP Theorem In distributed systems, you can only guarantee two of three properties:
Consistency: All nodes see the same data Availability: Every request receives a response Partition tolerance: System continues despite network failures
Distributed Computing Fallacies
The network is reliable Latency is zero Bandwidth is infinite The network is secure Topology doesn't change There is one administrator Transport cost is zero The network is homogeneous Domain-Driven Design (DDD) Strategic Design Patterns Bounded Context
A bounded context is an explicit boundary within which a domain model is consistent and valid. It defines the scope where particular terms, definitions, and rules apply.
Key Principles:
Each bounded context has its own ubiquitous language Models within a context are consistent Cross-context integration requires translation Contexts align with business capabilities
Implementation:
// Example: E-commerce system with multiple bounded contexts
// Sales Context namespace Sales { class Customer { customerId: string; email: string; orderHistory: Order[];
placeOrder(order: Order): void {
// Sales-specific logic
}
} }
// Billing Context namespace Billing { class Customer { customerId: string; paymentMethods: PaymentMethod[]; invoices: Invoice[];
processPayment(invoice: Invoice): void {
// Billing-specific logic
}
} }
// Different models for Customer in different contexts
Context Mapping Patterns:
Shared Kernel: Two contexts share a subset of the domain model
Use when: Teams are closely coordinated Risk: Changes affect multiple contexts
Customer-Supplier: Downstream context depends on upstream
Use when: Clear dependency direction exists Pattern: Upstream provides defined API
Conformist: Downstream conforms to upstream model
Use when: Upstream is external/unchangeable Pattern: Adapt to external API
Anti-Corruption Layer: Translate between contexts
Use when: Protecting from legacy or external systems Pattern: Adapter/facade to translate models
Separate Ways: Contexts are completely independent
Use when: No integration needed Pattern: Duplicate functionality if necessary
Open Host Service: Well-defined protocol for integration
Use when: Multiple consumers need access Pattern: REST API, GraphQL, gRPC
Published Language: Shared, well-documented language
Use when: Industry standards exist Pattern: XML schemas, JSON schemas, OpenAPI Ubiquitous Language
A shared vocabulary between developers and domain experts used consistently in code, documentation, and conversations.
Building Ubiquitous Language:
// Bad: Generic technical terms class DataProcessor { processData(data: any): void { // Unclear what this does in business terms } }
// Good: Business domain terms class OrderFulfillment { fulfillOrder(order: Order): void { this.pickItems(order.items); this.packForShipment(order); this.scheduleDelivery(order); }
private pickItems(items: OrderItem[]): void { // Business logic using domain language } }
Tactical Design Patterns Entities
Objects with unique identity that persist over time, tracking continuity and lifecycle.
Characteristics:
Unique identifier (ID) Mutable state Lifecycle (created, modified, deleted) Equality based on identity, not attributes
Implementation:
class Order { private readonly orderId: string; private orderItems: OrderItem[]; private status: OrderStatus; private orderDate: Date; private customerId: string;
constructor(orderId: string, customerId: string) { this.orderId = orderId; this.customerId = customerId; this.orderItems = []; this.status = OrderStatus.Draft; this.orderDate = new Date(); }
// Business behavior addItem(product: Product, quantity: number): void { if (this.status !== OrderStatus.Draft) { throw new Error("Cannot modify confirmed order"); } this.orderItems.push(new OrderItem(product, quantity)); }
confirm(): void { if (this.orderItems.length === 0) { throw new Error("Cannot confirm empty order"); } this.status = OrderStatus.Confirmed; }
// Identity-based equality equals(other: Order): boolean { return this.orderId === other.orderId; } }
Value Objects
Immutable objects defined by their attributes rather than identity, representing descriptive aspects of the domain.
Characteristics:
No unique identifier Immutable (cannot change state) Equality based on all attributes Often passed by value Can be shared safely
Implementation:
class Money { readonly amount: number; readonly currency: string;
constructor(amount: number, currency: string) { if (amount < 0) { throw new Error("Amount cannot be negative"); } this.amount = amount; this.currency = currency; }
// Operations return new instances add(other: Money): Money { if (this.currency !== other.currency) { throw new Error("Cannot add different currencies"); } return new Money(this.amount + other.amount, this.currency); }
multiply(factor: number): Money { return new Money(this.amount * factor, this.currency); }
// Value-based equality equals(other: Money): boolean { return this.amount === other.amount && this.currency === other.currency; } }
class Address { readonly street: string; readonly city: string; readonly state: string; readonly zipCode: string; readonly country: string;
constructor( street: string, city: string, state: string, zipCode: string, country: string ) { this.street = street; this.city = city; this.state = state; this.zipCode = zipCode; this.country = country; }
equals(other: Address): boolean { return this.street === other.street && this.city === other.city && this.state === other.state && this.zipCode === other.zipCode && this.country === other.country; } }
Aggregates
Clusters of entities and value objects with clear consistency boundaries, accessed through a single root entity.
Key Principles:
One aggregate = one transaction boundary External references only to aggregate root Root enforces all invariants Small aggregates perform better Eventual consistency between aggregates
Design Rules:
Model true invariants in consistency boundaries Design small aggregates Reference other aggregates by identity only Update other aggregates using eventual consistency Use repositories to retrieve aggregates
Implementation:
// Aggregate Root class Order { private readonly orderId: string; private readonly customerId: string; // Reference by ID only private orderItems: OrderItem[] = []; private shippingAddress: Address; private status: OrderStatus; private totalAmount: Money;
constructor(orderId: string, customerId: string, shippingAddress: Address) { this.orderId = orderId; this.customerId = customerId; this.shippingAddress = shippingAddress; this.status = OrderStatus.Draft; this.totalAmount = new Money(0, "USD"); }
// Public methods enforce invariants addItem(product: Product, quantity: number): void { if (this.status !== OrderStatus.Draft) { throw new Error("Cannot modify confirmed order"); }
if (quantity <= 0) {
throw new Error("Quantity must be positive");
}
const existingItem = this.findItem(product.id);
if (existingItem) {
existingItem.increaseQuantity(quantity);
} else {
this.orderItems.push(new OrderItem(product, quantity));
}
this.recalculateTotal();
}
removeItem(productId: string): void { if (this.status !== OrderStatus.Draft) { throw new Error("Cannot modify confirmed order"); }
this.orderItems = this.orderItems.filter(
item => item.productId !== productId
);
this.recalculateTotal();
}
confirm(): void { if (this.orderItems.length === 0) { throw new Error("Cannot confirm empty order"); }
if (!this.shippingAddress) {
throw new Error("Shipping address required");
}
this.status = OrderStatus.Confirmed;
}
private recalculateTotal(): void { this.totalAmount = this.orderItems.reduce( (total, item) => total.add(item.subtotal), new Money(0, "USD") ); }
private findItem(productId: string): OrderItem | undefined { return this.orderItems.find(item => item.productId === productId); }
// Getters for read-only access get id(): string { return this.orderId; } get total(): Money { return this.totalAmount; } get items(): readonly OrderItem[] { return this.orderItems; } }
// Entity within aggregate class OrderItem { readonly productId: string; readonly productName: string; readonly unitPrice: Money; private quantity: number;
constructor(product: Product, quantity: number) { this.productId = product.id; this.productName = product.name; this.unitPrice = product.price; this.quantity = quantity; }
increaseQuantity(amount: number): void { this.quantity += amount; }
get subtotal(): Money { return this.unitPrice.multiply(this.quantity); } }
Domain Events
Events that represent something significant that happened in the domain, enabling loose coupling and eventual consistency.
Characteristics:
Past tense naming (OrderPlaced, PaymentProcessed) Immutable Include all necessary information Timestamped Often include aggregate ID
Implementation:
interface DomainEvent { eventId: string; occurredAt: Date; aggregateId: string; eventType: string; }
class OrderPlacedEvent implements DomainEvent { readonly eventId: string; readonly occurredAt: Date; readonly aggregateId: string; readonly eventType = "OrderPlaced";
readonly orderId: string; readonly customerId: string; readonly totalAmount: Money; readonly items: OrderItemDto[];
constructor(order: Order) { this.eventId = generateId(); this.occurredAt = new Date(); this.aggregateId = order.id; this.orderId = order.id; this.customerId = order.customerId; this.totalAmount = order.total; this.items = order.items.map(item => ({ productId: item.productId, quantity: item.quantity, price: item.unitPrice })); } }
// Domain event publisher
class DomainEventPublisher {
private handlers: Map
subscribe(eventType: string, handler: Function): void { if (!this.handlers.has(eventType)) { this.handlers.set(eventType, []); } this.handlers.get(eventType)!.push(handler); }
async publish(event: DomainEvent): Promise
// Usage class OrderService { constructor( private orderRepository: OrderRepository, private eventPublisher: DomainEventPublisher ) {}
async placeOrder(order: Order): Promise
const event = new OrderPlacedEvent(order);
await this.eventPublisher.publish(event);
} }
Repositories
Abstraction for accessing aggregates, providing collection-like interface while hiding persistence details.
Principles:
One repository per aggregate root Collection-oriented interface Hide database implementation Return fully-formed aggregates Support querying by ID and business criteria
Implementation:
interface OrderRepository {
save(order: Order): Promise
class OrderRepositoryImpl implements OrderRepository { constructor(private db: Database) {}
async save(order: Order): Promise
async findById(orderId: string): Promise
async findByCustomer(customerId: string): Promise
async findByStatus(status: OrderStatus): Promise
async delete(orderId: string): Promise
private toDataModel(order: Order): any { // Convert domain model to database model return { id: order.id, customerId: order.customerId, items: order.items.map(item => ({ productId: item.productId, quantity: item.quantity, price: item.unitPrice.amount })), total: order.total.amount, status: order.status }; }
private toDomainModel(data: any): Order { // Reconstruct domain model from database data const order = new Order(data.id, data.customerId, data.shippingAddress); // Restore items and state return order; } }
Domain Services
Operations that don't naturally belong to any entity or value object, encapsulating domain logic that involves multiple aggregates.
When to Use:
Logic spans multiple aggregates Operation is a significant domain concept Behavior doesn't fit naturally in entity or value object
Implementation:
class PricingService { calculateOrderTotal( items: OrderItem[], customer: Customer, promotions: Promotion[] ): Money { let total = items.reduce( (sum, item) => sum.add(item.subtotal), new Money(0, "USD") );
// Apply customer discount
if (customer.isPremium) {
total = total.multiply(0.9); // 10% discount
}
// Apply promotions
for (const promo of promotions) {
total = promo.apply(total);
}
return total;
} }
class TransferService { transfer( fromAccount: Account, toAccount: Account, amount: Money ): void { if (!fromAccount.canWithdraw(amount)) { throw new Error("Insufficient funds"); }
fromAccount.withdraw(amount);
toAccount.deposit(amount);
} }
Event Sourcing
Event sourcing persists the state of a system as a sequence of events rather than storing current state. The current state is derived by replaying events.
Core Concepts
Event Store Append-only log of all events that have occurred in the system.
Event Stream Sequence of events for a specific aggregate, ordered by time.
Projection Read model built by processing events, optimized for queries.
Snapshot Cached state at a point in time to avoid replaying all events.
Implementation // Event interface interface Event { eventId: string; eventType: string; aggregateId: string; aggregateType: string; version: number; timestamp: Date; data: any; metadata?: any; }
// Account aggregate with event sourcing class Account { private accountId: string; private balance: number = 0; private isActive: boolean = true; private version: number = 0; private uncommittedEvents: Event[] = [];
constructor(accountId: string) { this.accountId = accountId; }
// Command handlers open(initialBalance: number): void { if (this.version > 0) { throw new Error("Account already opened"); } this.applyEvent({ eventType: "AccountOpened", data: { accountId: this.accountId, initialBalance } }); }
deposit(amount: number): void { if (!this.isActive) { throw new Error("Account is closed"); } if (amount <= 0) { throw new Error("Amount must be positive"); } this.applyEvent({ eventType: "MoneyDeposited", data: { amount } }); }
withdraw(amount: number): void { if (!this.isActive) { throw new Error("Account is closed"); } if (amount <= 0) { throw new Error("Amount must be positive"); } if (this.balance < amount) { throw new Error("Insufficient funds"); } this.applyEvent({ eventType: "MoneyWithdrawn", data: { amount } }); }
close(): void { if (!this.isActive) { throw new Error("Account already closed"); } if (this.balance > 0) { throw new Error("Cannot close account with positive balance"); } this.applyEvent({ eventType: "AccountClosed", data: {} }); }
// Event application
private applyEvent(eventData: Partial
this.apply(event);
this.uncommittedEvents.push(event);
}
// Event handlers (state mutations) private apply(event: Event): void { switch (event.eventType) { case "AccountOpened": this.balance = event.data.initialBalance; this.isActive = true; break;
case "MoneyDeposited":
this.balance += event.data.amount;
break;
case "MoneyWithdrawn":
this.balance -= event.data.amount;
break;
case "AccountClosed":
this.isActive = false;
break;
default:
throw new Error(`Unknown event type: ${event.eventType}`);
}
this.version = event.version;
}
// Replay events to rebuild state static fromEvents(events: Event[]): Account { if (events.length === 0) { throw new Error("Cannot create account from empty event stream"); }
const account = new Account(events[0].aggregateId);
events.forEach(event => account.apply(event));
return account;
}
getUncommittedEvents(): Event[] { return this.uncommittedEvents; }
markEventsAsCommitted(): void { this.uncommittedEvents = []; } }
// Event store interface
interface EventStore {
append(events: Event[]): Promise
// Event store implementation
class InMemoryEventStore implements EventStore {
private events: Map
async append(events: Event[]): Promise
// Store in global stream
this.allEvents.push(event);
}
}
async getEvents(
aggregateId: string,
fromVersion: number = 0
): Promise
async getAllEvents(fromTimestamp?: Date): Promise
// Repository with event sourcing class EventSourcedAccountRepository { constructor(private eventStore: EventStore) {}
async save(account: Account): Promise
async findById(accountId: string): Promise
Snapshots
Optimize performance by periodically saving aggregate state:
interface Snapshot { aggregateId: string; version: number; timestamp: Date; state: any; }
class SnapshotStore {
private snapshots: Map
async save(snapshot: Snapshot): Promise
async getLatest(aggregateId: string): Promise
class EventSourcedAccountRepositoryWithSnapshots { constructor( private eventStore: EventStore, private snapshotStore: SnapshotStore, private snapshotInterval: number = 100 ) {}
async save(account: Account): Promise
// Create snapshot every N events
if (account.version % this.snapshotInterval === 0) {
await this.snapshotStore.save({
aggregateId: account.id,
version: account.version,
timestamp: new Date(),
state: account.toSnapshot()
});
}
}
async findById(accountId: string): Promise
if (snapshot) {
account = Account.fromSnapshot(snapshot.state);
fromVersion = snapshot.version;
} else {
account = new Account(accountId);
}
// Apply events after snapshot
const events = await this.eventStore.getEvents(accountId, fromVersion);
events.forEach(event => account.apply(event));
return account;
} }
CQRS (Command Query Responsibility Segregation)
Separate read and write operations into different models, optimizing each for its specific use case.
Architecture Commands → Command Handlers → Aggregates → Events → Event Store ↓ Event Bus ↓ Projections → Read Models → Queries
Implementation // Commands (write operations) interface Command { commandId: string; timestamp: Date; }
class CreateAccountCommand implements Command { commandId: string; timestamp: Date; accountId: string; initialBalance: number;
constructor(accountId: string, initialBalance: number) { this.commandId = generateId(); this.timestamp = new Date(); this.accountId = accountId; this.initialBalance = initialBalance; } }
class DepositMoneyCommand implements Command { commandId: string; timestamp: Date; accountId: string; amount: number;
constructor(accountId: string, amount: number) { this.commandId = generateId(); this.timestamp = new Date(); this.accountId = accountId; this.amount = amount; } }
// Command handlers class AccountCommandHandler { constructor( private repository: EventSourcedAccountRepository, private eventBus: EventBus ) {}
async handle(command: Command): Promise
private async handleCreateAccount(
command: CreateAccountCommand
): Promise
// Publish events
const events = account.getUncommittedEvents();
await this.eventBus.publish(events);
}
private async handleDepositMoney(
command: DepositMoneyCommand
): Promise
account.deposit(command.amount);
await this.repository.save(account);
const events = account.getUncommittedEvents();
await this.eventBus.publish(events);
} }
// Read models (optimized for queries) interface AccountReadModel { accountId: string; balance: number; status: string; lastActivity: Date; transactionCount: number; }
interface AccountSummaryReadModel { accountId: string; balance: number; status: string; }
// Projections (build read models from events) class AccountProjection { constructor(private db: ReadDatabase) {}
async handleEvent(event: Event): Promise
private async handleAccountOpened(event: Event): Promise
private async handleMoneyDeposited(event: Event): Promise
private async handleMoneyWithdrawn(event: Event): Promise
private async handleAccountClosed(event: Event): Promise
// Query service (read-only) class AccountQueryService { constructor(private db: ReadDatabase) {}
async getAccount(accountId: string): Promise
async getAccountsByStatus(status: string): Promise
async getHighBalanceAccounts(
minBalance: number
): Promise
// Event bus for publishing events
class EventBus {
private subscribers: Map
subscribe(eventType: string, handler: Function): void { if (!this.subscribers.has(eventType)) { this.subscribers.set(eventType, []); } this.subscribers.get(eventType)!.push(handler); }
subscribeToAll(handler: Function): void { this.subscribe("*", handler); }
async publish(events: Event[]): Promise
// Call wildcard handlers
const allHandlers = this.subscribers.get("*") || [];
await Promise.all(allHandlers.map(h => h(event)));
}
} }
Saga Pattern
Manage distributed transactions across multiple services using a sequence of local transactions coordinated by a saga.
Orchestration-Based Saga
A central orchestrator coordinates all saga participants.
// Saga state enum SagaStatus { Started = "Started", Completed = "Completed", Compensating = "Compensating", Compensated = "Compensated", Failed = "Failed" }
interface SagaStep {
name: string;
action: () => Promise
class OrderSaga { private sagaId: string; private status: SagaStatus; private completedSteps: string[] = []; private currentStep: number = 0;
private steps: SagaStep[] = [ { name: "CreateOrder", action: async () => await this.createOrder(), compensation: async () => await this.cancelOrder() }, { name: "ReserveInventory", action: async () => await this.reserveInventory(), compensation: async () => await this.releaseInventory() }, { name: "ProcessPayment", action: async () => await this.processPayment(), compensation: async () => await this.refundPayment() }, { name: "ArrangeShipment", action: async () => await this.arrangeShipment(), compensation: async () => await this.cancelShipment() } ];
constructor( private orderId: string, private customerId: string, private items: OrderItem[] ) { this.sagaId = generateId(); this.status = SagaStatus.Started; }
async execute(): Promise
console.log(`Executing step: ${step.name}`);
await step.action();
this.completedSteps.push(step.name);
}
this.status = SagaStatus.Completed;
console.log("Saga completed successfully");
} catch (error) {
console.error(`Saga failed at step ${this.currentStep}:`, error);
await this.compensate();
}
}
private async compensate(): Promise
// Compensate in reverse order
for (let i = this.completedSteps.length - 1; i >= 0; i--) {
const stepName = this.completedSteps[i];
const step = this.steps.find(s => s.name === stepName);
if (step) {
try {
console.log(`Compensating step: ${step.name}`);
await step.compensation();
} catch (error) {
console.error(`Compensation failed for ${step.name}:`, error);
// Log for manual intervention
}
}
}
this.status = SagaStatus.Compensated;
console.log("Compensation completed");
}
// Step implementations
private async createOrder(): Promise
private async cancelOrder(): Promise
private async reserveInventory(): Promise
private async releaseInventory(): Promise
private async processPayment(): Promise
private async refundPayment(): Promise
private async arrangeShipment(): Promise
private async cancelShipment(): Promise
private calculateTotal(): Money { return this.items.reduce( (sum, item) => sum.add(item.price.multiply(item.quantity)), new Money(0, "USD") ); } }
// Orchestrator service
class SagaOrchestrator {
private activeSagas: Map
async startOrderSaga(
orderId: string,
customerId: string,
items: OrderItem[]
): Promise
try {
await saga.execute();
} finally {
this.activeSagas.delete(saga.sagaId);
}
}
getSagaStatus(sagaId: string): SagaStatus | null { const saga = this.activeSagas.get(sagaId); return saga ? saga.status : null; } }
Choreography-Based Saga
Services coordinate through events without central orchestrator.
// Event-driven saga with choreography class OrderCreatedEvent { constructor( public orderId: string, public customerId: string, public items: OrderItem[] ) {} }
class InventoryReservedEvent { constructor( public orderId: string, public reservationId: string ) {} }
class PaymentProcessedEvent { constructor( public orderId: string, public paymentId: string ) {} }
class ShipmentArrangedEvent { constructor( public orderId: string, public shipmentId: string ) {} }
// Compensation events class InventoryReservationFailedEvent { constructor( public orderId: string, public reason: string ) {} }
class PaymentFailedEvent { constructor( public orderId: string, public reason: string ) {} }
// Order service class OrderService { constructor(private eventBus: EventBus) { // Subscribe to compensation events eventBus.subscribe("InventoryReservationFailed", this.handleInventoryReservationFailed.bind(this)); eventBus.subscribe("PaymentFailed", this.handlePaymentFailed.bind(this)); }
async createOrder(order: CreateOrderRequest): Promise
// Publish event to trigger next step
await this.eventBus.publish(
new OrderCreatedEvent(order.orderId, order.customerId, order.items)
);
}
private async handleInventoryReservationFailed(
event: InventoryReservationFailedEvent
): PromiseOrder ${event.orderId} cancelled: ${event.reason});
}
private async handlePaymentFailed(event: PaymentFailedEvent): Promise
// Inventory service class InventoryService { constructor(private eventBus: EventBus) { eventBus.subscribe("OrderCreated", this.handleOrderCreated.bind(this)); eventBus.subscribe("ReleaseInventory", this.handleReleaseInventory.bind(this)); }
private async handleOrderCreated(event: OrderCreatedEvent): Promise
// Publish success event
await this.eventBus.publish(
new InventoryReservedEvent(event.orderId, reservationId)
);
} catch (error) {
// Publish failure event
await this.eventBus.publish(
new InventoryReservationFailedEvent(
event.orderId,
error.message
)
);
}
}
private async handleReleaseInventory(
command: ReleaseInventoryCommand
): Promise
// Payment service class PaymentService { constructor(private eventBus: EventBus) { eventBus.subscribe("InventoryReserved", this.handleInventoryReserved.bind(this)); eventBus.subscribe("RefundPayment", this.handleRefundPayment.bind(this)); }
private async handleInventoryReserved(
event: InventoryReservedEvent
): Promise
// Publish success event
await this.eventBus.publish(
new PaymentProcessedEvent(event.orderId, paymentId)
);
} catch (error) {
// Publish failure event
await this.eventBus.publish(
new PaymentFailedEvent(event.orderId, error.message)
);
}
}
private async handleRefundPayment(
command: RefundPaymentCommand
): Promise
API Gateway Pattern
Single entry point for clients, routing requests to appropriate microservices and handling cross-cutting concerns.
Implementation class APIGateway { constructor( private router: Router, private authService: AuthService, private rateLimiter: RateLimiter, private circuitBreaker: CircuitBreaker, private loadBalancer: LoadBalancer ) { this.setupRoutes(); }
private setupRoutes(): void { // User service routes this.router.get("/api/users/:id", this.authenticate.bind(this), this.rateLimit.bind(this), this.getUserHandler.bind(this) );
// Order service routes
this.router.post("/api/orders",
this.authenticate.bind(this),
this.rateLimit.bind(this),
this.createOrderHandler.bind(this)
);
// Product service routes
this.router.get("/api/products",
this.rateLimit.bind(this),
this.getProductsHandler.bind(this)
);
}
// Middleware: Authentication
private async authenticate(
req: Request,
res: Response,
next: NextFunction
): Promise
const user = await this.authService.validateToken(token);
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
}
// Middleware: Rate limiting
private async rateLimit(
req: Request,
res: Response,
next: NextFunction
): Promise
if (await this.rateLimiter.isAllowed(clientId)) {
next();
} else {
res.status(429).json({ error: "Too many requests" });
}
}
// Handler: Get user
private async getUserHandler(req: Request, res: Response): Promise
// Call user service with circuit breaker
const user = await this.circuitBreaker.execute(
"user-service",
async () => {
const instance = this.loadBalancer.getInstance("user-service");
return await instance.getUser(userId);
}
);
res.json(user);
} catch (error) {
res.status(500).json({ error: "Internal server error" });
}
}
// Handler: Create order
private async createOrderHandler(
req: Request,
res: Response
): Promise
// Call order service
const order = await this.callOrderService({
user,
items: req.body.items,
inventory
});
res.status(201).json(order);
} catch (error) {
res.status(500).json({ error: "Internal server error" });
}
}
private async callUserService(userId: string): Promise
private async callProductService(items: any[]): Promise
// Rate limiter implementation
class RateLimiter {
private requests: Map
constructor( private maxRequests: number = 100, private windowMs: number = 60000 // 1 minute ) {}
async isAllowed(clientId: string): Promise
// Get existing requests
const clientRequests = this.requests.get(clientId) || [];
// Filter out old requests
const recentRequests = clientRequests.filter(t => t > windowStart);
// Check if under limit
if (recentRequests.length < this.maxRequests) {
recentRequests.push(now);
this.requests.set(clientId, recentRequests);
return true;
}
return false;
} }
// Load balancer
class LoadBalancer {
private services: Map
registerService(name: string, instance: ServiceInstance): void { if (!this.services.has(name)) { this.services.set(name, []); this.currentIndex.set(name, 0); } this.services.get(name)!.push(instance); }
getInstance(serviceName: string): ServiceInstance {
const instances = this.services.get(serviceName);
if (!instances || instances.length === 0) {
throw new Error(No instances available for ${serviceName});
}
// Round-robin selection
const index = this.currentIndex.get(serviceName)!;
const instance = instances[index];
// Update index for next call
this.currentIndex.set(
serviceName,
(index + 1) % instances.length
);
return instance;
} }
Backend for Frontend (BFF) Pattern // Separate BFFs for different clients class WebBFF { constructor( private userService: UserService, private productService: ProductService, private orderService: OrderService ) {}
// Optimized for web client needs
async getHomePage(userId: string): Promise
return {
user: {
name: user.name,
email: user.email,
avatar: user.avatarUrl
},
recommendations: recommendations.map(p => ({
id: p.id,
name: p.name,
price: p.price,
imageUrl: p.images[0], // Full images for web
rating: p.averageRating
})),
recentOrders: recentOrders.map(o => ({
orderId: o.id,
date: o.createdAt,
total: o.totalAmount,
status: o.status,
itemCount: o.items.length
}))
};
} }
class MobileBFF { constructor( private userService: UserService, private productService: ProductService, private orderService: OrderService ) {}
// Optimized for mobile client needs
async getHomePage(userId: string): Promise
return {
user: {
name: user.name,
avatar: user.avatarThumbnailUrl // Smaller images for mobile
},
recommendations: recommendations.map(p => ({
id: p.id,
name: p.name,
price: p.price,
thumbnail: p.thumbnails.small, // Optimized image size
rating: Math.round(p.averageRating) // Simplified rating
})),
recentOrders: recentOrders.map(o => ({
id: o.id,
date: o.createdAt.toISOString(),
total: o.totalAmount,
status: o.status
}))
};
} }
Service Mesh Pattern
Infrastructure layer for service-to-service communication providing observability, traffic management, and security.
Key Features // Service mesh configuration example (Istio) const serviceMeshConfig = { // Traffic management virtualService: { name: "product-service", hosts: ["product-service"], http: [ { match: [{ uri: { prefix: "/api/v1" } }], route: [ { destination: { host: "product-service", subset: "v1" }, weight: 90 }, { destination: { host: "product-service", subset: "v2" }, weight: 10 // Canary deployment } ], retries: { attempts: 3, perTryTimeout: "2s" }, timeout: "10s" } ] },
// Destination rules destinationRule: { name: "product-service", host: "product-service", trafficPolicy: { connectionPool: { tcp: { maxConnections: 100 }, http: { http1MaxPendingRequests: 50, http2MaxRequests: 100, maxRequestsPerConnection: 2 } }, loadBalancer: { simple: "ROUND_ROBIN" }, outlierDetection: { consecutive5xxErrors: 5, interval: "30s", baseEjectionTime: "30s", maxEjectionPercent: 50 } }, subsets: [ { name: "v1", labels: { version: "v1" } }, { name: "v2", labels: { version: "v2" } } ] },
// Circuit breaker circuitBreaker: { consecutiveErrors: 5, interval: "30s", baseEjectionTime: "30s", maxEjectionPercent: 50 } };
Resilience Patterns Circuit Breaker
Prevent cascading failures by stopping requests to failing services.
enum CircuitState { Closed = "Closed", // Normal operation Open = "Open", // Blocking requests HalfOpen = "HalfOpen" // Testing if service recovered }
class CircuitBreaker { private state: CircuitState = CircuitState.Closed; private failureCount: number = 0; private successCount: number = 0; private lastFailureTime: number = 0;
constructor( private failureThreshold: number = 5, private successThreshold: number = 2, private timeout: number = 60000 // 1 minute ) {}
async execute
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void { this.failureCount = 0;
if (this.state === CircuitState.HalfOpen) {
this.successCount++;
if (this.successCount >= this.successThreshold) {
this.state = CircuitState.Closed;
this.successCount = 0;
}
}
}
private onFailure(): void { this.failureCount++; this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.state = CircuitState.Open;
}
}
getState(): CircuitState { return this.state; } }
Retry Pattern
Automatically retry failed operations with exponential backoff.
class RetryPolicy { constructor( private maxRetries: number = 3, private initialDelayMs: number = 100, private maxDelayMs: number = 5000, private backoffMultiplier: number = 2 ) {}
async execute
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt === this.maxRetries || !isRetryable(lastError)) {
throw lastError;
}
const delay = this.calculateDelay(attempt);
await this.sleep(delay);
}
}
throw lastError!;
}
private calculateDelay(attempt: number): number { const delay = this.initialDelayMs * Math.pow(this.backoffMultiplier, attempt); return Math.min(delay, this.maxDelayMs); }
private sleep(ms: number): Promise
// Usage const retryPolicy = new RetryPolicy(3, 100, 5000, 2);
await retryPolicy.execute( async () => await apiClient.get("/users/123"), (error) => error.statusCode >= 500 // Only retry server errors );
Bulkhead Pattern
Isolate resources to prevent failures from affecting entire system.
class Bulkhead {
private activeRequests: number = 0;
private queue: Array<{
resolve: (value: any) => void;
reject: (error: any) => void;
operation: () => Promise
constructor( private maxConcurrent: number = 10, private maxQueueSize: number = 100 ) {}
async execute
if (this.queue.length >= this.maxQueueSize) {
throw new Error("Bulkhead queue full");
}
return new Promise((resolve, reject) => {
this.queue.push({ resolve, reject, operation });
});
}
private async executeOperation
try {
const result = await operation();
this.processQueue();
return result;
} catch (error) {
this.processQueue();
throw error;
} finally {
this.activeRequests--;
}
}
private processQueue(): void { if (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) { const { resolve, reject, operation } = this.queue.shift()!;
this.executeOperation(operation)
.then(resolve)
.catch(reject);
}
} }
Timeout Pattern
Prevent indefinite waits by setting time limits.
class TimeoutPolicy { constructor(private timeoutMs: number = 30000) {}
async execute
private timeout(): PromiseOperation timed out after ${this.timeoutMs}ms));
}, this.timeoutMs);
});
}
}
Fallback Pattern
Provide alternative response when operation fails.
class FallbackPolicy
async execute(operation: () => Promise
// Usage const getUserWithFallback = new FallbackPolicy(async () => ({ id: "default", name: "Guest User", email: "guest@example.com" }));
const user = await getUserWithFallback.execute( async () => await userService.getUser(userId) );
Combined Resilience Strategy
class ResilientClient {
private circuitBreaker: CircuitBreaker;
private retryPolicy: RetryPolicy;
private timeoutPolicy: TimeoutPolicy;
private fallbackPolicy: FallbackPolicy
constructor() { this.circuitBreaker = new CircuitBreaker(5, 2, 60000); this.retryPolicy = new RetryPolicy(3, 100, 5000, 2); this.timeoutPolicy = new TimeoutPolicy(10000); this.fallbackPolicy = new FallbackPolicy(async () => null); this.bulkhead = new Bulkhead(10, 100); }
async call
const fallbackPolicy = options?.fallback
? new FallbackPolicy(options.fallback)
: this.fallbackPolicy;
return await fallbackPolicy.execute(async () => {
return await this.bulkhead.execute(async () => {
return await this.circuitBreaker.execute(async () => {
return await this.retryPolicy.execute(async () => {
return await timeoutPolicy.execute(operation);
});
});
});
});
} }
Scalability Patterns Horizontal Scaling (Scale Out)
Add more instances to handle increased load.
// Load balancer for horizontal scaling class RoundRobinLoadBalancer { private instances: string[] = []; private currentIndex: number = 0;
addInstance(url: string): void { this.instances.push(url); }
removeInstance(url: string): void { this.instances = this.instances.filter(i => i !== url); }
getNextInstance(): string { if (this.instances.length === 0) { throw new Error("No instances available"); }
const instance = this.instances[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.instances.length;
return instance;
} }
// Auto-scaling based on metrics class AutoScaler { constructor( private minInstances: number = 2, private maxInstances: number = 10, private targetCPU: number = 70 ) {}
async scale(currentInstances: number, currentCPU: number): Promise
return currentInstances;
} }
Caching Strategies // Cache-aside pattern class CacheAsideRepository { constructor( private cache: Cache, private database: Database, private ttl: number = 3600 ) {}
async get(id: string): Promise
// Cache miss - get from database
const data = await this.database.findById(id);
if (data) {
await this.cache.set(id, data, this.ttl);
}
return data;
}
async update(id: string, data: any): Promise
// Invalidate cache
await this.cache.delete(id);
} }
// Write-through cache class WriteThroughCache { constructor( private cache: Cache, private database: Database ) {}
async write(id: string, data: any): Promise
// Write-behind cache
class WriteBehindCache {
private writeQueue: Map
constructor( private cache: Cache, private database: Database, private flushInterval: number = 5000 ) { this.startFlushInterval(); }
async write(id: string, data: any): Promise
// Queue for database write
this.writeQueue.set(id, data);
}
private startFlushInterval(): void { setInterval(async () => { await this.flush(); }, this.flushInterval); }
private async flush(): Promise
await Promise.all(
entries.map(([id, data]) => this.database.save(id, data))
);
} }
Database Sharding
// Shard key-based routing
class ShardRouter {
private shards: Map
constructor(shards: Database[]) { this.totalShards = shards.length; shards.forEach((shard, index) => { this.shards.set(index, shard); }); }
private getShardIndex(key: string): number { // Hash-based sharding const hash = this.hashCode(key); return Math.abs(hash) % this.totalShards; }
private hashCode(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash; }
getShard(key: string): Database { const index = this.getShardIndex(key); return this.shards.get(index)!; }
async save(key: string, data: any): Promise
async find(key: string): Promise
Best Practices Architecture Design Start with the domain: Understand business requirements before choosing patterns Keep it simple: Don't over-engineer; add complexity only when needed Design for failure: Assume services will fail; build resilience Loose coupling: Services should be independent and deployable separately High cohesion: Group related functionality together API-first design: Define contracts before implementation Versioning strategy: Plan for API evolution from the start Observability: Build in logging, metrics, and tracing from day one Domain-Driven Design Collaborate with domain experts: Build shared understanding Use ubiquitous language: Consistent terminology everywhere Model bounded contexts: Clear boundaries prevent model confusion Small aggregates: Better performance and clearer boundaries Reference by ID: Aggregates reference others by identity Protect invariants: Aggregate roots enforce all business rules Domain events: Capture important business occurrences Repository per aggregate: One repository per aggregate root Event Sourcing & CQRS Event naming: Use past tense, business-meaningful names Event immutability: Never modify published events Event versioning: Plan for event schema evolution Snapshots: Use for long event streams Idempotent handlers: Events may be processed multiple times Separate concerns: Different models for read and write Eventual consistency: Accept and communicate delay Monitoring: Track projection lag and event processing Microservices Service size: Small enough to understand, large enough to provide value Data ownership: Each service owns its data Asynchronous communication: Prefer events over synchronous calls Service discovery: Dynamic service location Configuration management: Centralized, environment-specific config Deployment independence: Services deploy without coordinating Failure isolation: Circuit breakers and bulkheads Distributed tracing: Correlation IDs across service calls Performance & Scalability Measure first: Profile before optimizing Cache strategically: Right layer, right data, right TTL Async processing: Move slow operations to background Connection pooling: Reuse database/HTTP connections Pagination: Never return unbounded result sets Compression: Reduce network transfer size CDN usage: Serve static assets from edge locations Database indexes: Index query patterns, not all columns Security Defense in depth: Multiple security layers Least privilege: Minimal permissions necessary Encrypt in transit: TLS for all network communication Encrypt at rest: Sensitive data encrypted in storage Input validation: Validate and sanitize all inputs Authentication: Verify identity (JWT, OAuth) Authorization: Verify permissions (RBAC, ABAC) Audit logging: Track security-relevant events Testing Test pyramid: Many unit tests, fewer integration, few E2E Test behavior: Focus on business logic, not implementation Contract testing: Verify API contracts between services Chaos engineering: Test failure scenarios Performance testing: Load test before production Security testing: Automated vulnerability scanning Smoke tests: Quick validation after deployment Canary deployments: Gradual rollout to detect issues Monitoring & Observability Structured logging: JSON logs with context Metrics collection: RED metrics (Rate, Errors, Duration) Distributed tracing: Request flow across services Health checks: Liveness and readiness endpoints Alerting: Alert on symptoms, not causes Dashboards: Key metrics visible at a glance SLO/SLA: Define and track service levels Incident response: Runbooks for common issues Resources Books "Domain-Driven Design" by Eric Evans "Implementing Domain-Driven Design" by Vaughn Vernon "Microservices Patterns" by Chris Richardson "Building Microservices" by Sam Newman "Designing Data-Intensive Applications" by Martin Kleppmann Online Resources https://microservices.io/patterns - Microservices pattern catalog https://martinfowler.com - Architecture articles and patterns https://learn.microsoft.com/en-us/azure/architecture - Azure Architecture Center https://aws.amazon.com/architecture - AWS Architecture resources https://cloud.google.com/architecture - Google Cloud Architecture
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Enterprise Architecture, System Design, Distributed Systems