システムレベルの責任分離

アーキテクチャパターンによる責任の構造化

大規模システムでは、アーキテクチャレベルで責任を明確に分離することが、保守性とスケーラビリティの鍵となります。

モノリシック

単一デプロイ単位
レイヤーで責任分離

モジュラーモノリス

モジュール境界明確
将来の分割準備

マイクロサービス

独立デプロイ
サービス単位で責任

レイヤードアーキテクチャ

垂直方向の責任分離

システムを論理的な層に分割し、各層に明確な責任を割り当てます。

// レイヤー構造の例
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';
    }
}

システム設計のベストプラクティス

大規模システムでの責任分離

  • 境界の明確化:ドメイン境界とサービス境界を一致させる
  • 自律性:各サービスは独立して動作可能
  • 疎結合:サービス間の依存を最小限に
  • データの所有権:各サービスが自身のデータを管理
  • 障害の隔離:一部の障害が全体に波及しない
  • スケーラビリティ:必要な部分だけをスケール