enterprise-architecture-patterns by manutej/luxor-claude-marketplace
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill enterprise-architecture-patterns掌握企业架构模式、分布式系统设计和可扩展应用开发的综合技能。本技能涵盖构建健壮、可维护和可扩展企业系统的战略和战术模式。
在以下情况下使用此技能:
关注点分离 将系统划分为不同的部分,每个部分处理一个独立的关注点,从而减少耦合并增加内聚性。
模块化 将系统设计为独立模块的集合,这些模块可以单独开发、测试和部署。
抽象 通过简单接口隐藏复杂的实现细节,使系统更易于理解和修改。
可扩展性维度
一致性模型
CAP 定理 在分布式系统中,你只能保证三个属性中的两个:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
分布式计算的谬误
限界上下文是领域模型保持一致和有效的显式边界。它定义了特定术语、定义和规则适用的范围。
关键原则:
实现:
// 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
上下文映射模式:
开发人员和领域专家之间共享的词汇表,在代码、文档和对话中一致使用。
构建通用语言:
// 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
}
}
具有唯一标识、随时间持续存在的对象,跟踪连续性和生命周期。
特征:
实现:
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;
}
}
由其属性而非标识定义的不可变对象,表示领域的描述性方面。
特征:
实现:
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;
}
}
具有明确一致性边界的实体和值对象的集群,通过单个根实体进行访问。
关键原则:
设计规则:
实现:
// 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);
}
}
表示领域中发生的重大事件的事件,支持松耦合和最终一致性。
特征:
实现:
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<string, Function[]> = new 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<void> {
const handlers = this.handlers.get(event.eventType) || [];
await Promise.all(handlers.map(handler => handler(event)));
}
}
// Usage
class OrderService {
constructor(
private orderRepository: OrderRepository,
private eventPublisher: DomainEventPublisher
) {}
async placeOrder(order: Order): Promise<void> {
order.confirm();
await this.orderRepository.save(order);
const event = new OrderPlacedEvent(order);
await this.eventPublisher.publish(event);
}
}
用于访问聚合的抽象,提供类似集合的接口,同时隐藏持久化细节。
原则:
实现:
interface OrderRepository {
save(order: Order): Promise<void>;
findById(orderId: string): Promise<Order | null>;
findByCustomer(customerId: string): Promise<Order[]>;
findByStatus(status: OrderStatus): Promise<Order[]>;
delete(orderId: string): Promise<void>;
}
class OrderRepositoryImpl implements OrderRepository {
constructor(private db: Database) {}
async save(order: Order): Promise<void> {
const data = this.toDataModel(order);
await this.db.orders.upsert(data);
}
async findById(orderId: string): Promise<Order | null> {
const data = await this.db.orders.findOne({ id: orderId });
if (!data) return null;
return this.toDomainModel(data);
}
async findByCustomer(customerId: string): Promise<Order[]> {
const results = await this.db.orders.find({ customerId });
return results.map(data => this.toDomainModel(data));
}
async findByStatus(status: OrderStatus): Promise<Order[]> {
const results = await this.db.orders.find({ status });
return results.map(data => this.toDomainModel(data));
}
async delete(orderId: string): Promise<void> {
await this.db.orders.delete({ id: orderId });
}
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;
}
}
不属于任何实体或值对象的操作,封装涉及多个聚合的领域逻辑。
使用时机:
实现:
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 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<Event>): void {
const event: Event = {
eventId: generateId(),
eventType: eventData.eventType!,
aggregateId: this.accountId,
aggregateType: "Account",
version: this.version + 1,
timestamp: new Date(),
data: eventData.data,
metadata: eventData.metadata
};
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<void>;
getEvents(aggregateId: string, fromVersion?: number): Promise<Event[]>;
getAllEvents(fromTimestamp?: Date): Promise<Event[]>;
}
// Event store implementation
class InMemoryEventStore implements EventStore {
private events: Map<string, Event[]> = new Map();
private allEvents: Event[] = [];
async append(events: Event[]): Promise<void> {
for (const event of events) {
// Store in aggregate stream
if (!this.events.has(event.aggregateId)) {
this.events.set(event.aggregateId, []);
}
this.events.get(event.aggregateId)!.push(event);
// Store in global stream
this.allEvents.push(event);
}
}
async getEvents(
aggregateId: string,
fromVersion: number = 0
): Promise<Event[]> {
const events = this.events.get(aggregateId) || [];
return events.filter(e => e.version > fromVersion);
}
async getAllEvents(fromTimestamp?: Date): Promise<Event[]> {
if (!fromTimestamp) {
return this.allEvents;
}
return this.allEvents.filter(e => e.timestamp >= fromTimestamp);
}
}
// Repository with event sourcing
class EventSourcedAccountRepository {
constructor(private eventStore: EventStore) {}
async save(account: Account): Promise<void> {
const events = account.getUncommittedEvents();
if (events.length > 0) {
await this.eventStore.append(events);
account.markEventsAsCommitted();
}
}
async findById(accountId: string): Promise<Account | null> {
const events = await this.eventStore.getEvents(accountId);
if (events.length === 0) {
return null;
}
return Account.fromEvents(events);
}
}
通过定期保存聚合状态来优化性能:
interface Snapshot {
aggregateId: string;
version: number;
timestamp: Date;
state: any;
}
class SnapshotStore {
private snapshots: Map<string, Snapshot> = new Map();
async save(snapshot: Snapshot): Promise<void> {
this.snapshots.set(snapshot.aggregateId, snapshot);
}
async getLatest(aggregateId: string): Promise<Snapshot | null> {
return this.snapshots.get(aggregateId) || null;
}
}
class EventSourcedAccountRepositoryWithSnapshots {
constructor(
private eventStore: EventStore,
private snapshotStore: SnapshotStore,
private snapshotInterval: number = 100
) {}
async save(account: Account): Promise<void> {
const events = account.getUncommittedEvents();
await this.eventStore.append(events);
account.markEventsAsCommitted();
// 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<Account | null> {
// Try to load from snapshot
const snapshot = await this.snapshotStore.getLatest(accountId);
let account: Account;
let fromVersion = 0;
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;
}
}
将读写操作分离到不同的模型中,针对其特定用例进行优化。
Commands → Command Handlers → Aggregates → Events → Event Store
↓
Event Bus
↓
Projections → Read Models → Queries
// 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<void> {
if (command instanceof CreateAccountCommand) {
await this.handleCreateAccount(command);
} else if (command instanceof DepositMoneyCommand) {
await this.handleDepositMoney(command);
}
}
private async handleCreateAccount(
command: CreateAccountCommand
): Promise<void> {
const account = new Account(command.accountId);
account.open(command.initialBalance);
await this.repository.save(account);
// Publish events
const events = account.getUncommittedEvents();
await this.eventBus.publish(events);
}
private async handleDepositMoney(
command: DepositMoneyCommand
): Promise<void> {
const account = await this.repository.findById(command.accountId);
if (!account) {
throw new Error("Account not found");
}
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<void> {
switch (event.eventType) {
case "AccountOpened":
await this.handleAccountOpened(event);
break;
case "MoneyDeposited":
await this.handleMoneyDeposited(event);
break;
case "MoneyWithdrawn":
await this.handleMoneyWithdrawn(event);
break;
case "AccountClosed":
await this.handleAccountClosed(event);
break;
}
}
private async handleAccountOpened(event: Event): Promise<void> {
await this.db.accounts.insert({
accountId: event.aggregateId,
balance: event.data.initialBalance,
status: "Active",
lastActivity: event.timestamp,
transactionCount: 0
});
}
private async handleMoneyDeposited(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$inc: { balance: event.data.amount, transactionCount: 1 },
$set: { lastActivity: event.timestamp }
}
);
}
private async handleMoneyWithdrawn(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$inc: { balance: -event.data.amount, transactionCount: 1 },
$set: { lastActivity: event.timestamp }
}
);
}
private async handleAccountClosed(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$set: {
status: "Closed",
lastActivity: event.timestamp
}
}
);
}
}
// Query service (read-only)
class AccountQueryService {
constructor(private db: ReadDatabase) {}
async getAccount(accountId: string): Promise<AccountReadModel | null> {
return await this.db.accounts.findOne({ accountId });
}
async getAccountsByStatus(status: string): Promise<AccountSummaryReadModel[]> {
return await this.db.accounts.find(
{ status },
{ projection: { accountId: 1, balance: 1, status: 1 } }
);
}
async getHighBalanceAccounts(
minBalance: number
): Promise<AccountSummaryReadModel[]> {
return await this.db.accounts.find(
{ balance: { $gte: minBalance } },
{ projection: { accountId: 1, balance: 1, status: 1 } }
);
}
}
// Event bus for publishing events
class EventBus {
private subscribers: Map<string, Function[]> = new 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<void> {
for (const event of events) {
// Call specific handlers
const handlers = this.subscribers.get(event.eventType) || [];
await Promise.all(handlers.map(h => h(event)));
// Call wildcard handlers
const allHandlers = this.subscribers.get("*") || [];
await Promise.all(allHandlers.map(h => h(event)));
}
}
}
使用由 Saga 协调的一系列本地事务来管理跨多个服务的分布式事务。
中央编排器协调所有 Saga 参与者。
// Saga state
enum SagaStatus {
Started = "Started",
Completed = "Completed",
Compensating = "Compensating",
Compensated = "Compensated",
Failed = "Failed"
}
interface SagaStep {
name: string;
action: () => Promise<void>;
compensation: () => Promise<void>;
}
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<void> {
try {
// Execute each step
for (let i = 0; i < this.steps.length; i++) {
this.currentStep = i;
const step = this.steps[i];
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<void> {
this.status = SagaStatus.Compensating;
console.log("Starting compensation");
// 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<void> {
await orderService.create({
orderId: this.orderId,
customerId: this.customerId,
items: this.items
});
}
private async cancelOrder(): Promise<void> {
await orderService.cancel(this.orderId);
}
private async reserveInventory(): Promise<void> {
await inventoryService.reserve(this.orderId, this.items);
}
private async releaseInventory(): Promise<void> {
await inventoryService.release(this.orderId);
}
private async processPayment(): Promise<void> {
const total = this.calculateTotal();
await paymentService.charge(this.customerId, total);
}
private async refundPayment(): Promise<void> {
const total = this.calculateTotal();
await paymentService.refund(this.customerId, total);
}
private async arrangeShipment(): Promise<void> {
await shippingService.createShipment(this.orderId);
}
private async cancelShipment(): Promise<void> {
await shippingService.cancelShipment(this.orderId);
}
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<string, OrderSaga> = new Map();
async startOrderSaga(
orderId: string,
customerId: string,
items: OrderItem[]
): Promise<void> {
const saga = new OrderSaga(orderId, customerId, items);
this.activeSagas.set(saga.sagaId, saga);
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;
}
}
服务通过事件进行协调,无需中央编排器。
// 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("InventoryReservation
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.
Use this skill when:
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
Consistency Models
CAP Theorem In distributed systems, you can only guarantee two of three properties:
Distributed Computing Fallacies
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:
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
Customer-Supplier : Downstream context depends on upstream
Conformist : Downstream conforms to upstream model
Anti-Corruption Layer : Translate between contexts
Separate Ways : Contexts are completely independent
Open Host Service : Well-defined protocol for integration
Published Language : Shared, well-documented 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
}
}
Objects with unique identity that persist over time, tracking continuity and lifecycle.
Characteristics:
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;
}
}
Immutable objects defined by their attributes rather than identity, representing descriptive aspects of the domain.
Characteristics:
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;
}
}
Clusters of entities and value objects with clear consistency boundaries, accessed through a single root entity.
Key Principles:
Design Rules:
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);
}
}
Events that represent something significant that happened in the domain, enabling loose coupling and eventual consistency.
Characteristics:
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<string, Function[]> = new 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<void> {
const handlers = this.handlers.get(event.eventType) || [];
await Promise.all(handlers.map(handler => handler(event)));
}
}
// Usage
class OrderService {
constructor(
private orderRepository: OrderRepository,
private eventPublisher: DomainEventPublisher
) {}
async placeOrder(order: Order): Promise<void> {
order.confirm();
await this.orderRepository.save(order);
const event = new OrderPlacedEvent(order);
await this.eventPublisher.publish(event);
}
}
Abstraction for accessing aggregates, providing collection-like interface while hiding persistence details.
Principles:
Implementation:
interface OrderRepository {
save(order: Order): Promise<void>;
findById(orderId: string): Promise<Order | null>;
findByCustomer(customerId: string): Promise<Order[]>;
findByStatus(status: OrderStatus): Promise<Order[]>;
delete(orderId: string): Promise<void>;
}
class OrderRepositoryImpl implements OrderRepository {
constructor(private db: Database) {}
async save(order: Order): Promise<void> {
const data = this.toDataModel(order);
await this.db.orders.upsert(data);
}
async findById(orderId: string): Promise<Order | null> {
const data = await this.db.orders.findOne({ id: orderId });
if (!data) return null;
return this.toDomainModel(data);
}
async findByCustomer(customerId: string): Promise<Order[]> {
const results = await this.db.orders.find({ customerId });
return results.map(data => this.toDomainModel(data));
}
async findByStatus(status: OrderStatus): Promise<Order[]> {
const results = await this.db.orders.find({ status });
return results.map(data => this.toDomainModel(data));
}
async delete(orderId: string): Promise<void> {
await this.db.orders.delete({ id: orderId });
}
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;
}
}
Operations that don't naturally belong to any entity or value object, encapsulating domain logic that involves multiple aggregates.
When to Use:
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 persists the state of a system as a sequence of events rather than storing current state. The current state is derived by replaying events.
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.
// 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<Event>): void {
const event: Event = {
eventId: generateId(),
eventType: eventData.eventType!,
aggregateId: this.accountId,
aggregateType: "Account",
version: this.version + 1,
timestamp: new Date(),
data: eventData.data,
metadata: eventData.metadata
};
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<void>;
getEvents(aggregateId: string, fromVersion?: number): Promise<Event[]>;
getAllEvents(fromTimestamp?: Date): Promise<Event[]>;
}
// Event store implementation
class InMemoryEventStore implements EventStore {
private events: Map<string, Event[]> = new Map();
private allEvents: Event[] = [];
async append(events: Event[]): Promise<void> {
for (const event of events) {
// Store in aggregate stream
if (!this.events.has(event.aggregateId)) {
this.events.set(event.aggregateId, []);
}
this.events.get(event.aggregateId)!.push(event);
// Store in global stream
this.allEvents.push(event);
}
}
async getEvents(
aggregateId: string,
fromVersion: number = 0
): Promise<Event[]> {
const events = this.events.get(aggregateId) || [];
return events.filter(e => e.version > fromVersion);
}
async getAllEvents(fromTimestamp?: Date): Promise<Event[]> {
if (!fromTimestamp) {
return this.allEvents;
}
return this.allEvents.filter(e => e.timestamp >= fromTimestamp);
}
}
// Repository with event sourcing
class EventSourcedAccountRepository {
constructor(private eventStore: EventStore) {}
async save(account: Account): Promise<void> {
const events = account.getUncommittedEvents();
if (events.length > 0) {
await this.eventStore.append(events);
account.markEventsAsCommitted();
}
}
async findById(accountId: string): Promise<Account | null> {
const events = await this.eventStore.getEvents(accountId);
if (events.length === 0) {
return null;
}
return Account.fromEvents(events);
}
}
Optimize performance by periodically saving aggregate state:
interface Snapshot {
aggregateId: string;
version: number;
timestamp: Date;
state: any;
}
class SnapshotStore {
private snapshots: Map<string, Snapshot> = new Map();
async save(snapshot: Snapshot): Promise<void> {
this.snapshots.set(snapshot.aggregateId, snapshot);
}
async getLatest(aggregateId: string): Promise<Snapshot | null> {
return this.snapshots.get(aggregateId) || null;
}
}
class EventSourcedAccountRepositoryWithSnapshots {
constructor(
private eventStore: EventStore,
private snapshotStore: SnapshotStore,
private snapshotInterval: number = 100
) {}
async save(account: Account): Promise<void> {
const events = account.getUncommittedEvents();
await this.eventStore.append(events);
account.markEventsAsCommitted();
// 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<Account | null> {
// Try to load from snapshot
const snapshot = await this.snapshotStore.getLatest(accountId);
let account: Account;
let fromVersion = 0;
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;
}
}
Separate read and write operations into different models, optimizing each for its specific use case.
Commands → Command Handlers → Aggregates → Events → Event Store
↓
Event Bus
↓
Projections → Read Models → Queries
// 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<void> {
if (command instanceof CreateAccountCommand) {
await this.handleCreateAccount(command);
} else if (command instanceof DepositMoneyCommand) {
await this.handleDepositMoney(command);
}
}
private async handleCreateAccount(
command: CreateAccountCommand
): Promise<void> {
const account = new Account(command.accountId);
account.open(command.initialBalance);
await this.repository.save(account);
// Publish events
const events = account.getUncommittedEvents();
await this.eventBus.publish(events);
}
private async handleDepositMoney(
command: DepositMoneyCommand
): Promise<void> {
const account = await this.repository.findById(command.accountId);
if (!account) {
throw new Error("Account not found");
}
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<void> {
switch (event.eventType) {
case "AccountOpened":
await this.handleAccountOpened(event);
break;
case "MoneyDeposited":
await this.handleMoneyDeposited(event);
break;
case "MoneyWithdrawn":
await this.handleMoneyWithdrawn(event);
break;
case "AccountClosed":
await this.handleAccountClosed(event);
break;
}
}
private async handleAccountOpened(event: Event): Promise<void> {
await this.db.accounts.insert({
accountId: event.aggregateId,
balance: event.data.initialBalance,
status: "Active",
lastActivity: event.timestamp,
transactionCount: 0
});
}
private async handleMoneyDeposited(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$inc: { balance: event.data.amount, transactionCount: 1 },
$set: { lastActivity: event.timestamp }
}
);
}
private async handleMoneyWithdrawn(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$inc: { balance: -event.data.amount, transactionCount: 1 },
$set: { lastActivity: event.timestamp }
}
);
}
private async handleAccountClosed(event: Event): Promise<void> {
await this.db.accounts.update(
{ accountId: event.aggregateId },
{
$set: {
status: "Closed",
lastActivity: event.timestamp
}
}
);
}
}
// Query service (read-only)
class AccountQueryService {
constructor(private db: ReadDatabase) {}
async getAccount(accountId: string): Promise<AccountReadModel | null> {
return await this.db.accounts.findOne({ accountId });
}
async getAccountsByStatus(status: string): Promise<AccountSummaryReadModel[]> {
return await this.db.accounts.find(
{ status },
{ projection: { accountId: 1, balance: 1, status: 1 } }
);
}
async getHighBalanceAccounts(
minBalance: number
): Promise<AccountSummaryReadModel[]> {
return await this.db.accounts.find(
{ balance: { $gte: minBalance } },
{ projection: { accountId: 1, balance: 1, status: 1 } }
);
}
}
// Event bus for publishing events
class EventBus {
private subscribers: Map<string, Function[]> = new 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<void> {
for (const event of events) {
// Call specific handlers
const handlers = this.subscribers.get(event.eventType) || [];
await Promise.all(handlers.map(h => h(event)));
// Call wildcard handlers
const allHandlers = this.subscribers.get("*") || [];
await Promise.all(allHandlers.map(h => h(event)));
}
}
}
Manage distributed transactions across multiple services using a sequence of local transactions coordinated by a 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<void>;
compensation: () => Promise<void>;
}
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<void> {
try {
// Execute each step
for (let i = 0; i < this.steps.length; i++) {
this.currentStep = i;
const step = this.steps[i];
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<void> {
this.status = SagaStatus.Compensating;
console.log("Starting compensation");
// 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<void> {
await orderService.create({
orderId: this.orderId,
customerId: this.customerId,
items: this.items
});
}
private async cancelOrder(): Promise<void> {
await orderService.cancel(this.orderId);
}
private async reserveInventory(): Promise<void> {
await inventoryService.reserve(this.orderId, this.items);
}
private async releaseInventory(): Promise<void> {
await inventoryService.release(this.orderId);
}
private async processPayment(): Promise<void> {
const total = this.calculateTotal();
await paymentService.charge(this.customerId, total);
}
private async refundPayment(): Promise<void> {
const total = this.calculateTotal();
await paymentService.refund(this.customerId, total);
}
private async arrangeShipment(): Promise<void> {
await shippingService.createShipment(this.orderId);
}
private async cancelShipment(): Promise<void> {
await shippingService.cancelShipment(this.orderId);
}
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<string, OrderSaga> = new Map();
async startOrderSaga(
orderId: string,
customerId: string,
items: OrderItem[]
): Promise<void> {
const saga = new OrderSaga(orderId, customerId, items);
this.activeSagas.set(saga.sagaId, saga);
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;
}
}
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<void> {
// Create order in pending state
await this.repository.save({
...order,
status: "Pending"
});
// Publish event to trigger next step
await this.eventBus.publish(
new OrderCreatedEvent(order.orderId, order.customerId, order.items)
);
}
private async handleInventoryReservationFailed(
event: InventoryReservationFailedEvent
): Promise<void> {
await this.repository.updateStatus(event.orderId, "Cancelled");
console.log(`Order ${event.orderId} cancelled: ${event.reason}`);
}
private async handlePaymentFailed(event: PaymentFailedEvent): Promise<void> {
await this.repository.updateStatus(event.orderId, "PaymentFailed");
// Trigger inventory release
await this.eventBus.publish(
new ReleaseInventoryCommand(event.orderId)
);
}
}
// 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<void> {
try {
// Reserve inventory
const reservationId = await this.reserveItems(event.items);
// 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<void> {
await this.releaseReservation(command.orderId);
}
}
// 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<void> {
try {
// Process payment
const paymentId = await this.chargeCustomer(event.orderId);
// 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<void> {
await this.refund(command.orderId);
}
}
Single entry point for clients, routing requests to appropriate microservices and handling cross-cutting concerns.
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<void> {
try {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
res.status(401).json({ error: "Unauthorized" });
return;
}
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<void> {
const clientId = req.ip || req.user?.id;
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<void> {
try {
const userId = req.params.id;
// 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<void> {
try {
// Aggregate data from multiple services
const [user, products, inventory] = await Promise.all([
this.callUserService(req.user.id),
this.callProductService(req.body.items),
this.callInventoryService(req.body.items)
]);
// 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<User> {
return await this.circuitBreaker.execute("user-service", async () => {
const instance = this.loadBalancer.getInstance("user-service");
return await instance.getUser(userId);
});
}
private async callProductService(items: any[]): Promise<Product[]> {
return await this.circuitBreaker.execute("product-service", async () => {
const instance = this.loadBalancer.getInstance("product-service");
return await instance.getProducts(items.map(i => i.productId));
});
}
}
// Rate limiter implementation
class RateLimiter {
private requests: Map<string, number[]> = new Map();
constructor(
private maxRequests: number = 100,
private windowMs: number = 60000 // 1 minute
) {}
async isAllowed(clientId: string): Promise<boolean> {
const now = Date.now();
const windowStart = now - this.windowMs;
// 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<string, ServiceInstance[]> = new Map();
private currentIndex: Map<string, number> = new 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;
}
}
// 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<WebHomePageData> {
const [user, recommendations, recentOrders] = await Promise.all([
this.userService.getUser(userId),
this.productService.getRecommendations(userId, 10),
this.orderService.getRecentOrders(userId, 5)
]);
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<MobileHomePageData> {
const [user, recommendations, recentOrders] = await Promise.all([
this.userService.getUser(userId),
this.productService.getRecommendations(userId, 5), // Fewer items
this.orderService.getRecentOrders(userId, 3)
]);
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
}))
};
}
}
Infrastructure layer for service-to-service communication providing observability, traffic management, and security.
// 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
}
};
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<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.Open) {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = CircuitState.HalfOpen;
this.successCount = 0;
} else {
throw new Error("Circuit breaker is OPEN");
}
}
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;
}
}
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<T>(
operation: () => Promise<T>,
isRetryable: (error: Error) => boolean = () => true
): Promise<T> {
let lastError: Error;
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<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 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
);
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<any>;
}> = [];
constructor(
private maxConcurrent: number = 10,
private maxQueueSize: number = 100
) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.activeRequests < this.maxConcurrent) {
return await this.executeOperation(operation);
}
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<T>(
operation: () => Promise<T>
): Promise<T> {
this.activeRequests++;
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);
}
}
}
Prevent indefinite waits by setting time limits.
class TimeoutPolicy {
constructor(private timeoutMs: number = 30000) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
return Promise.race([
operation(),
this.timeout()
]);
}
private timeout(): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${this.timeoutMs}ms`));
}, this.timeoutMs);
});
}
}
Provide alternative response when operation fails.
class FallbackPolicy<T> {
constructor(private fallbackFn: () => Promise<T>) {}
async execute(operation: () => Promise<T>): Promise<T> {
try {
return await operation();
} catch (error) {
console.warn("Operation failed, using fallback:", error);
return await this.fallbackFn();
}
}
}
// 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)
);
class ResilientClient {
private circuitBreaker: CircuitBreaker;
private retryPolicy: RetryPolicy;
private timeoutPolicy: TimeoutPolicy;
private fallbackPolicy: FallbackPolicy<any>;
private bulkhead: Bulkhead;
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<T>(
operation: () => Promise<T>,
options?: {
timeout?: number;
retries?: number;
fallback?: () => Promise<T>;
}
): Promise<T> {
const timeoutPolicy = options?.timeout
? new TimeoutPolicy(options.timeout)
: this.timeoutPolicy;
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);
});
});
});
});
}
}
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<number> {
if (currentCPU > this.targetCPU) {
// Scale up
const desiredInstances = Math.ceil(
currentInstances * (currentCPU / this.targetCPU)
);
return Math.min(desiredInstances, this.maxInstances);
} else if (currentCPU < this.targetCPU * 0.5) {
// Scale down
const desiredInstances = Math.floor(
currentInstances * (currentCPU / this.targetCPU)
);
return Math.max(desiredInstances, this.minInstances);
}
return currentInstances;
}
}
// Cache-aside pattern
class CacheAsideRepository {
constructor(
private cache: Cache,
private database: Database,
private ttl: number = 3600
) {}
async get(id: string): Promise<any> {
// Try cache first
const cached = await this.cache.get(id);
if (cached) {
return cached;
}
// 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<void> {
// Update database
await this.database.update(id, data);
// 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<void> {
// Write to both cache and database
await Promise.all([
this.cache.set(id, data),
this.database.save(id, data)
]);
}
}
// Write-behind cache
class WriteBehindCache {
private writeQueue: Map<string, any> = new Map();
constructor(
private cache: Cache,
private database: Database,
private flushInterval: number = 5000
) {
this.startFlushInterval();
}
async write(id: string, data: any): Promise<void> {
// Write t
Kotlin Ktor 服务器模式指南:构建健壮 HTTP API 的架构与最佳实践
1,000 周安装
主动任务系统:AI助手自主任务管理,从被动响应到主动协作
95 周安装
Spec设计调研技能:AI驱动需求分析,系统化提取未知项并生成研究任务
95 周安装
Playwright 高质量应用截图生成工具 - 一键创建营销级 HiDPI 截图
95 周安装
arch-tsdown-cli:TypeScript CLI项目启动模板,支持ESM、d.ts自动生成与npm可信发布
95 周安装
Confluence 专家指南:空间管理、文档架构、模板与协作知识库搭建
96 周安装
构建完整AI聊天应用指南:Next.js + Neon + AI SDK实现持久化聊天与自动命名
96 周安装