システム設計
アーキテクチャレベルでの責任分離と大規模システムの構築
システムレベルの責任分離
アーキテクチャパターンによる責任の構造化
大規模システムでは、アーキテクチャレベルで責任を明確に分離することが、保守性とスケーラビリティの鍵となります。
モノリシック
単一デプロイ単位
レイヤーで責任分離
→
モジュラーモノリス
モジュール境界明確
将来の分割準備
→
マイクロサービス
独立デプロイ
サービス単位で責任
レイヤードアーキテクチャ
垂直方向の責任分離
システムを論理的な層に分割し、各層に明確な責任を割り当てます。
// レイヤー構造の例
src/
├── presentation/ # プレゼンテーション層
│ ├── controllers/ # HTTPリクエスト処理
│ ├── views/ # UIテンプレート
│ └── dto/ # データ転送オブジェクト
│
├── application/ # アプリケーション層
│ ├── usecases/ # ユースケース実装
│ ├── services/ # アプリケーションサービス
│ └── interfaces/ # 外部サービスインターフェース
│
├── domain/ # ドメイン層
│ ├── models/ # ドメインモデル
│ ├── valueObjects/ # 値オブジェクト
│ ├── repositories/ # リポジトリインターフェース
│ └── services/ # ドメインサービス
│
└── infrastructure/ # インフラストラクチャ層
├── persistence/ # データ永続化実装
├── external/ # 外部サービス連携
└── config/ # 設定・初期化
// 各層の責任の実装例
// domain/models/Order.ts
export class Order {
private items: OrderItem[] = [];
private status: OrderStatus;
constructor(
private id: OrderId,
private customerId: CustomerId
) {
this.status = OrderStatus.DRAFT;
}
// ドメインロジック
addItem(product: Product, quantity: number): void {
if (this.status !== OrderStatus.DRAFT) {
throw new Error('Cannot add items to confirmed order');
}
const item = new OrderItem(product, quantity);
this.items.push(item);
}
calculateTotal(): Money {
return this.items.reduce(
(total, item) => total.add(item.getSubtotal()),
Money.zero()
);
}
confirm(): void {
if (this.items.length === 0) {
throw new Error('Cannot confirm empty order');
}
this.status = OrderStatus.CONFIRMED;
}
}
// application/usecases/CreateOrderUseCase.ts
export class CreateOrderUseCase {
constructor(
private orderRepository: IOrderRepository,
private productRepository: IProductRepository,
private inventoryService: IInventoryService,
private paymentService: IPaymentService
) {}
async execute(command: CreateOrderCommand): Promise {
// 在庫確認
for (const item of command.items) {
const available = await this.inventoryService
.checkAvailability(item.productId, item.quantity);
if (!available) {
throw new InsufficientInventoryError(item.productId);
}
}
// ドメインオブジェクトの構築
const order = new Order(
OrderId.generate(),
command.customerId
);
for (const item of command.items) {
const product = await this.productRepository
.findById(item.productId);
order.addItem(product, item.quantity);
}
// 支払い処理
const total = order.calculateTotal();
await this.paymentService.charge(command.paymentMethod, total);
// 注文確定と保存
order.confirm();
await this.orderRepository.save(order);
return new OrderResult(order.id, total);
}
}
ヘキサゴナルアーキテクチャ
ポートとアダプターによる責任分離
ビジネスロジックを中心に据え、外部との接続を抽象化することで、技術的な詳細からビジネスロジックを保護します。
// ヘキサゴナルアーキテクチャの実装
// core/ports/input/OrderService.ts (入力ポート)
export interface OrderService {
createOrder(order: CreateOrderRequest): Promise;
getOrder(orderId: string): Promise;
cancelOrder(orderId: string): Promise;
}
// core/ports/output/OrderRepository.ts (出力ポート)
export interface OrderRepository {
save(order: Order): Promise;
findById(id: OrderId): Promise;
update(order: Order): Promise;
}
// core/ports/output/PaymentGateway.ts (出力ポート)
export interface PaymentGateway {
charge(amount: Money, method: PaymentMethod): Promise;
refund(transactionId: string): Promise;
}
// core/domain/Order.ts (ビジネスロジック)
export class Order {
// ピュアなビジネスロジックのみ
// 外部システムへの依存なし
}
// adapters/input/rest/OrderController.ts (入力アダプター)
@Controller('/orders')
export class OrderController {
constructor(private orderService: OrderService) {}
@Post('/')
async createOrder(@Body() request: CreateOrderDTO): Promise {
try {
const result = await this.orderService.createOrder(
this.mapToRequest(request)
);
return Response.ok(result);
} catch (error) {
return this.handleError(error);
}
}
}
// adapters/output/persistence/PostgresOrderRepository.ts (出力アダプター)
export class PostgresOrderRepository implements OrderRepository {
constructor(private db: Database) {}
async save(order: Order): Promise {
const data = this.mapToDbModel(order);
await this.db.query(
'INSERT INTO orders ...',
data
);
}
private mapToDbModel(order: Order): DbOrder {
// ドメインモデルをDBモデルに変換
}
}
// adapters/output/payment/StripePaymentGateway.ts (出力アダプター)
export class StripePaymentGateway implements PaymentGateway {
constructor(private stripe: Stripe) {}
async charge(amount: Money, method: PaymentMethod): Promise {
const charge = await this.stripe.charges.create({
amount: amount.toCents(),
currency: amount.currency,
source: method.token
});
return new PaymentResult(charge.id, charge.status);
}
}
マイクロサービスアーキテクチャ
サービス境界による責任分離
各サービスが独自のデータストアを持ち、明確に定義されたAPIを通じて通信します。
// マイクロサービスの構成例
// order-service/
export class OrderService {
private orderRepo: OrderRepository;
async createOrder(request: CreateOrderRequest): Promise {
// 在庫サービスに在庫確認
const stockAvailable = await this.inventoryClient
.checkStock(request.items);
if (!stockAvailable) {
throw new InsufficientStockError();
}
const order = Order.create(request);
await this.orderRepo.save(order);
// イベントを発行
await this.eventBus.publish(
new OrderCreatedEvent(order)
);
return order;
}
}
// inventory-service/
export class InventoryService {
private stockRepo: StockRepository;
// 同期API
async checkStock(items: StockCheckRequest[]): Promise {
for (const item of items) {
const stock = await this.stockRepo.findByProductId(item.productId);
if (stock.quantity < item.requestedQuantity) {
return false;
}
}
return true;
}
// イベントハンドラー
@EventHandler(OrderCreatedEvent)
async handleOrderCreated(event: OrderCreatedEvent): Promise {
// 在庫を減らす
for (const item of event.order.items) {
await this.stockRepo.decrementStock(
item.productId,
item.quantity
);
}
}
}
// payment-service/
export class PaymentService {
private paymentGateway: PaymentGateway;
@EventHandler(OrderCreatedEvent)
async handleOrderCreated(event: OrderCreatedEvent): Promise {
try {
const payment = await this.paymentGateway.charge(
event.order.total,
event.order.paymentMethod
);
await this.eventBus.publish(
new PaymentCompletedEvent(event.order.id, payment)
);
} catch (error) {
await this.eventBus.publish(
new PaymentFailedEvent(event.order.id, error)
);
}
}
}
マイクロサービスでの責任分離の原則
- 各サービスは単一の境界づけられたコンテキストを持つ
- サービス間の通信は明確に定義されたAPIを通じて行う
- 各サービスは独自のデータストアを持つ(共有DBなし)
- イベント駆動で疎結合を実現
- サービスは独立してデプロイ・スケール可能
イベント駆動アーキテクチャ
イベントによる責任の分離
イベントを使用することで、システムの各部分が疎結合に連携できます。
// イベント定義
export class OrderCreatedEvent {
constructor(
public readonly orderId: string,
public readonly customerId: string,
public readonly items: OrderItem[],
public readonly total: Money,
public readonly timestamp: Date = new Date()
) {}
}
// イベント駆動の実装例
// Event Store
export class EventStore {
async append(streamId: string, events: DomainEvent[]): Promise {
// イベントを永続化
}
async getEvents(streamId: string): Promise {
// イベントを読み込み
}
}
// Saga (長時間実行プロセス)
export class OrderFulfillmentSaga {
private state: SagaState = 'started';
@EventHandler(OrderCreatedEvent)
async handleOrderCreated(event: OrderCreatedEvent): Promise {
// 在庫予約
await this.commandBus.send(
new ReserveInventoryCommand(event.orderId, event.items)
);
}
@EventHandler(InventoryReservedEvent)
async handleInventoryReserved(event: InventoryReservedEvent): Promise {
// 支払い処理
await this.commandBus.send(
new ProcessPaymentCommand(event.orderId)
);
}
@EventHandler(PaymentProcessedEvent)
async handlePaymentProcessed(event: PaymentProcessedEvent): Promise {
// 配送手配
await this.commandBus.send(
new ArrangeShippingCommand(event.orderId)
);
this.state = 'completed';
}
// 補償トランザクション
@EventHandler(PaymentFailedEvent)
async handlePaymentFailed(event: PaymentFailedEvent): Promise {
// 在庫予約を解除
await this.commandBus.send(
new ReleaseInventoryCommand(event.orderId)
);
this.state = 'failed';
}
}
システム設計のベストプラクティス
大規模システムでの責任分離
- 境界の明確化:ドメイン境界とサービス境界を一致させる
- 自律性:各サービスは独立して動作可能
- 疎結合:サービス間の依存を最小限に
- データの所有権:各サービスが自身のデータを管理
- 障害の隔離:一部の障害が全体に波及しない
- スケーラビリティ:必要な部分だけをスケール