enterprise-architecture-patterns について
分散システム、DDD、イベント駆動設計、スケーラブルなアプリケーションを扱う、アーキテクトや開発者向けの企業アーキテクチャパターンのスキルです。戦略・戦術パターンと実装例をまとめています。
主な機能
- 01システム設計とスケーラビリティの基礎
- 02DDDの戦略・戦術パターン
- 03イベントソーシングとCQRS
- 04Saga、API Gateway、Service Mesh
- 05レジリエンスと障害対応パターン
活用シーン
- 01マイクロサービスやクラウドネイティブ構成の計画
- 02複雑なドメインと境界づけられたコンテキストのモデル化
- 03分散トランザクションとイベントストリームの設計
- 04モノリスからマイクロサービスへの移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill enterprise-architecture-patternsインストール後、Claude Code で/enterprise-architecture-patternsを入力して読み込まれたか確認します。
同梱ファイルEXAMPLES.mdREADME.md
詳細
エンタープライズアーキテクチャパターン
エンタープライズアーキテクチャパターン、分散システム設計、スケーラブルなアプリケーション開発を習得するための包括的なスキルです。このスキルでは、堅牢で保守しやすく、スケーラブルなエンタープライズシステムを構築するための戦略的パターンと戦術的パターンを扱います。
このスキルを使用する場面
次のような場面で、このスキルを使用してください。
- 分散システム向けにマイクロサービスアーキテクチャを設計するとき
- 複雑なビジネスドメインにドメイン駆動設計(DDD)を実装するとき
- イベントソーシングとCQRSを用いたイベント駆動型アーキテクチャを構築するとき
- 分散トランザクション向けにサーガパターンを設計するとき
- APIゲートウェイとサービスメッシュアーキテクチャを実装するとき
- アプリケーションを水平・垂直方向にスケーリングするとき
- 耐障害性を備えた堅牢なシステムを構築するとき
- モノリスをマイクロサービスに移行するとき
- マルチテナントのSaaSアーキテクチャを設計するとき
- バックエンド・フォー・フロントエンド(BFF)パターンを実装するとき
- イベントストリーミングを使用したリアルタイムシステムを構築するとき
- クラウドネイティブアプリケーションを設計するとき
アーキテクチャの基本概念
システム設計の基礎
関心の分離 システムを個別のセクションに分割し、各セクションが別々の関心事を扱うようにすることで、結合度を下げ、凝集度を高めます。
モジュール性 個別に開発、テスト、デプロイできる独立したモジュールの集合としてシステムを設計します。
抽象化 複雑な実装の詳細をシンプルなインターフェースの背後に隠し、システムを理解し、変更しやすくします。
スケーラビリティの方向性
- 水平スケーリング:マシン/インスタンスを追加します
- 垂直スケーリング:既存のマシンにリソースを追加します
- データスケーリング:複数のストアにデータを分割します
- 機能スケーリング:ビジネスケイパビリティごとに分割します
整合性モデル
- 強整合性:すべてのノードが同時に同じデータを参照します
- 結果整合性:すべてのノードが最終的に同じデータを参照します
- 因果整合性:関連する操作が一貫した状態を参照します
- Read-your-writes整合性:ユーザーは自身の更新をすぐに確認できます
CAP定理 分散システムでは、次の3つの特性のうち保証できるのは2つだけです。
- 一貫性:すべてのノードが同じデータを参照します
- 可用性:すべてのリクエストに応答します
- 分断耐性:ネットワーク障害が発生してもシステムが動作を続けます
分散コンピューティングの誤謬
- ネットワークは信頼できる
- レイテンシはゼロである
- 帯域幅は無限である
- ネットワークは安全である
- トポロジーは変化しない
- 管理者は1人である
- 通信コストはゼロである
- ネットワークは均質である
ドメイン駆動設計(DDD)
戦略的設計パターン
境界づけられたコンテキスト
境界づけられたコンテキストとは、ドメインモデルの一貫性と妥当性が保たれる明確な境界です。特定の用語、定義、ルールが適用される範囲を定めます。
主な原則:
- 各境界づけられたコンテキストには、独自のユビキタス言語があります
- コンテキスト内のモデルには一貫性があります
- コンテキスト間の統合には翻訳が必要です
- コンテキストはビジネスケイパビリティに対応します
実装:
// 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
コンテキストマッピングのパターン:
-
共有カーネル:2つのコンテキストがドメインモデルの一部を共有します
- 使用する場面:チーム間で緊密に連携している場合
- リスク:変更が複数のコンテキストに影響します
-
顧客・供給者:下流のコンテキストが上流に依存します
- 使用する場面:依存関係の方向が明確な場合
- パターン:上流が定義済みの API を提供します
-
順応者:下流が上流のモデルに順応します
- 使用する場面:上流が外部のシステムである、または変更できない場合
- パターン:外部 API に適応します
-
腐敗防止層:コンテキスト間を翻訳します
- 使用する場面:レガシーシステムや外部システムから保護する場合
- パターン:モデルを翻訳するアダプター/ファサード
-
別々の道:コンテキストが完全に独立しています
- 使用する場面:統合が不要な場合
- パターン:必要に応じて機能を重複して実装します
-
オープンホストサービス:統合のための明確に定義されたプロトコル
- 使用する場面:複数の利用者がアクセスを必要とする場合
- パターン:REST API、GraphQL、gRPC
-
公開言語:共有され、十分に文書化された言語
- 使用する場面:業界標準が存在する場合
- パターン:XML スキーマ、JSON スキーマ、OpenAPI
ユビキタス言語
開発者とドメインエキスパートが共有し、コード、ドキュメント、会話で一貫して使用する語彙です。
ユビキタス言語の構築:
// 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
}
}
戦術的設計パターン
エンティティ
一意の識別性を持ち、時間の経過とともに存続し、連続性とライフサイクルを追跡するオブジェクトです。
特性:
- 一意の識別子(ID)
- 可変の状態
- ライフサイクル(作成、変更、削除)
- 属性ではなく識別性に基づく等価性
実装:
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;
}
}
集約
明確な整合性境界を持つエンティティと値オブジェクトのまとまりで、単一のルートエンティティを通じてアクセスします。
主な原則:
- 1つの集約 = 1つのトランザクション境界
- 外部からの参照は集約ルートのみに限定します
- ルートがすべての不変条件を適用します
- 小さな集約ほどパフォーマンスが向上します
- 集約間では結果整合性を保ちます
設計ルール:
- 整合性境界内で真の不変条件をモデル化します
- 小さな集約を設計します
- 他の集約は識別性のみで参照します
- 結果整合性を使用して他の集約を更新します
- リポジトリを使用して集約を取得します
実装:
// 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);
}
}
ドメインイベント
ドメイン内で起きた重要な出来事を表すイベントで、疎結合と結果整合性を実現します。
特性:
- 過去形の名前(OrderPlaced、PaymentProcessed)
- 不変
- 必要な情報をすべて含みます
- タイムスタンプ付き
- 集約 ID を含むことがよくあります
実装:
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);
}
}
リポジトリ
集約にアクセスするための抽象化で、永続化の詳細を隠しながらコレクションのようなインターフェースを提供します。
原則:
- 集約ルートごとに1つのリポジトリ
- コレクション指向のインターフェース
- データベースの実装を隠します
- 完全に構築された集約を返します
- ID とビジネス上の条件による検索をサポートします
実装:
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;
}
}
CQRS(コマンドクエリ責務分離)
読み取り操作と書き込み操作を異なるモデルに分離し、それぞれを特定のユースケースに合わせて最適化します。
アーキテクチャ
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
中央のオーケストレーターがすべての 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;
}
}
コレオグラフィーベースの Saga
中央のオーケストレーターを置かず、サービスがイベントを介して連携します。
// 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);
}
}
API Gateway パターン
クライアント向けの単一のエントリーポイントとして、リクエストを適切なマイクロサービスにルーティングし、横断的関心事に対応します。
実装
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;
}
}
Backend for Frontend(BFF)パターン
// 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
}))
};
}
}
サービスメッシュパターン
可観測性、トラフィック管理、セキュリティを提供する、サービス間通信のためのインフラストラクチャ層です。
主な機能
// 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
}
};
回復性パターン
サーキットブレーカーパターン
障害が発生しているサービスへのリクエストを停止し、障害の連鎖を防ぎます。
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;
}
}
リトライパターン
指数バックオフを使用して、失敗した操作を自動的に再試行します。
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
);
バルクヘッドパターン
リソースを分離し、障害がシステム全体に影響するのを防ぎます。
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);
}
}
}
タイムアウトパターン
時間制限を設けて、無期限に待機するのを防ぎます。
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);
});
}
}
フォールバックパターン
操作が失敗した場合に、代替の応答を提供します。
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: "[email protected]"
}));
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);
});
});
});
});
}
}
スケーラビリティパターン
水平スケーリング(スケールアウト)
インスタンスを追加して、増加した負荷に対応します。
// 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 to cache immediately
await this.cache.set(id, data);
// Queue for database write
this.writeQueue.set(id, data);
}
private startFlushInterval(): void {
setInterval(async () => {
await this.flush();
}, this.flushInterval);
}
private async flush(): Promise<void> {
const entries = Array.from(this.writeQueue.entries());
this.writeQueue.clear();
await Promise.all(
entries.map(([id, data]) => this.database.save(id, data))
);
}
}
データベースシャーディング
// Shard key-based routing
class ShardRouter {
private shards: Map<number, Database> = new Map();
private totalShards: number;
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<void> {
const shard = this.getShard(key);
await shard.save(key, data);
}
async find(key: string): Promise<any> {
const shard = this.getShard(key);
return await shard.findById(key);
}
}
ベストプラクティス
アーキテクチャ設計
- ドメインから始める:パターンを選ぶ前にビジネス要件を理解します
- シンプルに保つ:過剰な設計を避け、必要な場合にのみ複雑さを加えます
- 障害を前提に設計する:サービスは障害を起こすものと想定し、回復性を組み込みます
- 疎結合にする:サービスは独立し、個別にデプロイできるようにします
- 高凝集にする:関連する機能をまとめます
- API ファーストで設計する:実装の前に契約を定義します
- バージョニング戦略を立てる:最初から API の進化に備えます
- 可観測性を確保する:初日からログ、メトリクス、トレーシングを組み込みます
ドメイン駆動設計
- ドメインエキスパートと協力する:共通理解を築きます
- ユビキタス言語を使う:どこでも一貫した用語を使用します
- 境界づけられたコンテキストをモデル化する:明確な境界によってモデルの混乱を防ぎます
- 集約を小さく保つ:パフォーマンスが向上し、境界も明確になります
- ID で参照する:集約は他の集約を識別子で参照します
- 不変条件を保護する:集約ルートがすべてのビジネスルールを適用します
- ドメインイベントを使う:重要なビジネス上の出来事を記録します
- 集約ごとにリポジトリを用意する:集約ルートごとにリポジトリを1つ用意します
イベントソーシングと CQRS
- イベントの命名:過去形を使い、ビジネス上の意味が伝わる名前にします
- イベントの不変性:公開済みのイベントは決して変更しません
- イベントのバージョニング:イベントスキーマの進化を見越して計画します
- スナップショット:イベントストリームが長い場合に使用します
- 冪等なハンドラー:イベントは複数回処理される可能性があります
- 関心事の分離:読み取り用と書き込み用で異なるモデルを使用します
- 結果整合性:遅延を許容し、その旨を伝えます
- モニタリング:プロジェクションの遅延とイベント処理を追跡します
マイクロサービス
- サービスの規模:理解できる程度に小さく、価値を提供できる程度に大きくします
- データの所有権:各サービスが自身のデータを所有します
- 非同期通信:同期呼び出しよりイベントを優先します
- サービスディスカバリー:サービスの場所を動的に特定します
- 構成管理:環境ごとの構成を一元管理します
- デプロイの独立性:各サービスは他のサービスとの調整なしにデプロイできるようにします
- 障害の分離:サーキットブレーカーとバルクヘッドを使用します
- 分散トレーシング:サービス呼び出しをまたいで相関 ID を使用します
パフォーマンスとスケーラビリティ
- まず測定する:最適化の前にプロファイリングします
- 戦略的にキャッシュする:適切なレイヤー、適切なデータ、適切な TTL を選びます
- 非同期処理:時間のかかる処理をバックグラウンドに移します
- コネクションプーリング:データベース/HTTP 接続を再利用します
- ページネーション:上限のない結果セットは決して返しません
- 圧縮:ネットワーク転送量を削減します
- CDN の利用:エッジロケーションから静的アセットを配信します
- データベースインデックス:すべての列ではなく、クエリパターンに合わせてインデックスを作成します
セキュリティ
- 多層防御:複数のセキュリティレイヤーを設けます
- 最小権限:必要最小限の権限を付与します
- 通信中の暗号化:すべてのネットワーク通信に TLS を使用します
- 保存データの暗号化:機密データをストレージ内で暗号化します
- 入力検証:すべての入力を検証し、サニタイズします
- 認証:本人確認を行います(JWT、OAuth)
- 認可:権限を確認します(RBAC、ABAC)
- 監査ログ:セキュリティに関わるイベントを追跡します
テスト
- テストピラミッド:単体テストを多く、結合テストを少なく、E2E テストをわずかにします
- 振る舞いをテストする:実装ではなく、ビジネスロジックに焦点を当てます
- コントラクトテスト:サービス間の API コントラクトを検証します
- カオスエンジニアリング:障害シナリオをテストします
- パフォーマンステスト:本番環境へのリリース前に負荷テストを行います
- セキュリティテスト:脆弱性の自動スキャンを行います
- スモークテスト:デプロイ後に迅速な検証を行います
- カナリアデプロイ:問題を検出できるよう、段階的にロールアウトします
モニタリングと可観測性
- 構造化ログ:コンテキストを含む JSON ログを使用します
- メトリクス収集:RED メトリクス(レート、エラー、継続時間)を収集します
- 分散トレーシング:サービスをまたいだリクエストの流れを追跡します
- ヘルスチェック:ライブネスとレディネスのエンドポイントを用意します
- アラート:原因ではなく症状に対してアラートを発します
- ダッシュボード:主要なメトリクスをひと目で確認できるようにします
- SLO/SLA:サービスレベルを定義し、追跡します
- インシデント対応:よくある問題に備えてランブックを用意します
リソース
書籍
- Eric Evans 著「"Domain-Driven Design"」
- Vaughn Vernon 著「"Implementing Domain-Driven Design"」
- Chris Richardson 著「"Microservices Patterns"」
- Sam Newman 著「"Building Microservices"」
- Martin Kleppmann 著「"Designing Data-Intensive Applications"」
オンラインリソース
- https://microservices.io/patterns - マイクロサービスのパターンカタログ
- https://martinfowler.com - アーキテクチャに関する記事とパターン
- https://learn.microsoft.com/en-us/azure/architecture - Azure Architecture Center
- https://aws.amazon.com/architecture - AWS アーキテクチャのリソース
- https://cloud.google.com/architecture - Google Cloud アーキテクチャ
スキルバージョン: 1.0.0 最終更新日: 2025年10月 スキルカテゴリ: エンタープライズアーキテクチャ、システム設計、分散システム