Skip to content

Usecase間の循環依存を解消 #34

@harutiro

Description

@harutiro

概要

Domain層のUsecase間で循環依存が発生しており、アーキテクチャ上の問題となっています。イベント駆動アーキテクチャまたはMediatorパターンを導入して解消します。

背景

  • Clean Architectureでは、Usecase間の直接的な相互依存は推奨されない
  • 現在、ConnectionManagementUsecase、RealtimeDataUsecase、SensingControlUsecaseの間で循環依存が発生
  • テストが困難になり、保守性が低下している
  • 将来的な拡張性を阻害する可能性がある

現在の循環依存構造

ConnectionManagementUsecase
  ↓ (依存)
RealtimeDataUsecase
  ↓ (依存)
SensingControlUsecase
  ↓ (依存)
ConnectionManagementUsecase  ← ⚠️ 循環

詳細な依存関係

ConnectionManagementUsecase

  • RealtimeDataUsecaseへの参照を保持(Optional)
  • デバイス接続時にRealtimeDataUsecaseを呼び出し

RealtimeDataUsecase

  • SensingControlUsecaseへの参照を保持(Optional)
  • データ受信時にSensingControlUsecaseを呼び出し

SensingControlUsecase

  • ConnectionManagementUsecaseへの参照を保持
  • センシング制御時にConnectionManagementUsecaseを呼び出し

解決案

案1: イベント駆動アーキテクチャ(推奨)

各Usecaseは直接参照せず、EventBusを通じて通信:

実装例:

// 新規: Domain/Event/UsecaseEvent.swift
protocol UsecaseEvent {
    var eventType: String { get }
    var timestamp: Date { get }
}

// 具体的なイベント定義
struct DeviceConnectedEvent: UsecaseEvent {
    let eventType = "DeviceConnected"
    let timestamp = Date()
    let deviceId: String
}

struct DataReceivedEvent: UsecaseEvent {
    let eventType = "DataReceived"
    let timestamp = Date()
    let data: RealtimeData
}

// 新規: Domain/Event/EventBus.swift
class EventBus {
    static let shared = EventBus()
    
    private var subscriptions: [String: [(Any) -> Void]] = [:]
    
    func publish<T: UsecaseEvent>(_ event: T) {
        // イベントを購読者に配信
    }
    
    func subscribe<T: UsecaseEvent>(
        _ handler: @escaping (T) -> Void
    ) -> AnyCancellable {
        // イベントを購読
    }
}

利点:

  • Usecase間の疎結合化
  • 新しいUsecaseの追加が容易
  • テストが容易(イベントのモック化)

欠点:

  • イベントフローの追跡が複雑になる可能性
  • EventBus自体がSingletonとなる

案2: Mediatorパターン

調整役となるCoordinatorを導入:

実装例:

// 新規: Domain/Usecase/SensingCoordinator.swift
class SensingCoordinator {
    private let connectionUsecase: ConnectionManagementUsecase
    private let realtimeDataUsecase: RealtimeDataUsecase
    private let sensingControlUsecase: SensingControlUsecase
    
    init(
        connectionUsecase: ConnectionManagementUsecase,
        realtimeDataUsecase: RealtimeDataUsecase,
        sensingControlUsecase: SensingControlUsecase
    ) {
        self.connectionUsecase = connectionUsecase
        self.realtimeDataUsecase = realtimeDataUsecase
        self.sensingControlUsecase = sensingControlUsecase
        
        // 各Usecaseのイベントを購読し、調整
        setupCoordination()
    }
    
    private func setupCoordination() {
        // デバイス接続時の処理
        connectionUsecase.onDeviceConnected = { [weak self] deviceId in
            self?.handleDeviceConnected(deviceId)
        }
        
        // データ受信時の処理
        realtimeDataUsecase.onDataReceived = { [weak self] data in
            self?.handleDataReceived(data)
        }
    }
}

利点:

  • 依存関係が明確
  • デバッグが容易
  • ビジネスロジックの調整が一元化

欠点:

  • Coordinatorが複雑になる可能性
  • 新しいUsecaseの追加時にCoordinatorの修正が必要

案3: Protocolによる依存の逆転(DIP)

Protocol化して依存方向を制御:

実装例:

// 新規: Domain/Usecase/Protocols/DeviceConnectionDelegate.swift
protocol DeviceConnectionDelegate: AnyObject {
    func didConnectDevice(deviceId: String)
    func didDisconnectDevice(deviceId: String)
}

// 新規: Domain/Usecase/Protocols/DataReceiverDelegate.swift
protocol DataReceiverDelegate: AnyObject {
    func didReceiveData(_ data: RealtimeData)
}

// ConnectionManagementUsecaseの修正
class ConnectionManagementUsecase {
    weak var delegate: DeviceConnectionDelegate?
    
    func connectDevice() {
        // 接続処理
        delegate?.didConnectDevice(deviceId: deviceId)
    }
}

利点:

  • 依存方向が明確
  • テストが容易(Delegateのモック化)

欠点:

  • Delegate数が増えると管理が複雑
  • ViewModelでDelegate実装が必要になる可能性

推奨アプローチ

Phase 1: Protocolによる依存の逆転(短期)

  • 既存コードへの影響が最小
  • 段階的な移行が可能

Phase 2: イベント駆動アーキテクチャへの移行(中長期)

  • より柔軟な設計
  • 将来的な拡張性の確保

受け入れ条件

  • Usecase間の循環依存が解消されること
  • 既存の機能がすべて動作すること
  • すべてのビルドが成功すること
  • 既存のテストがすべて通ること
  • 新しいアーキテクチャに対応したテストが追加されること
  • SwiftFormatを実行してコードフォーマットを統一

実装方針

  1. Protocolベースのアプローチを実装
  2. 各Usecaseから直接的な相互参照を削除
  3. Delegate/Protocolを通じた通信に変更
  4. テストの更新
  5. (長期)EventBusの導入検討

補足事項

  • 既存の動作を壊さないよう、段階的なリファクタリングを実施
  • 各Usecaseのテストを十分に実施
  • ViewModelへの影響を最小限に抑える

期待される効果

  • Clean Architectureの原則遵守
  • テスタビリティの向上
  • 保守性と拡張性の向上
  • コードの可読性向上

🤖 このIssueはClaude Codeによって作成されました

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions