-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
リファクタリングリファクタリングを行いますリファクタリングを行います
Description
概要
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を実行してコードフォーマットを統一
実装方針
- Protocolベースのアプローチを実装
- 各Usecaseから直接的な相互参照を削除
- Delegate/Protocolを通じた通信に変更
- テストの更新
- (長期)EventBusの導入検討
補足事項
- 既存の動作を壊さないよう、段階的なリファクタリングを実施
- 各Usecaseのテストを十分に実施
- ViewModelへの影響を最小限に抑える
期待される効果
- Clean Architectureの原則遵守
- テスタビリティの向上
- 保守性と拡張性の向上
- コードの可読性向上
🤖 このIssueはClaude Codeによって作成されました
Metadata
Metadata
Assignees
Labels
リファクタリングリファクタリングを行いますリファクタリングを行います