-
Notifications
You must be signed in to change notification settings - Fork 0
Description
概要
現在 homete/ 配下にフラットに配置されているコードを、Swift Package Manager(SPM)のローカルパッケージとして Feature ベースの複数モジュール に分割する。ビルド時間の短縮・モジュール境界の明確化・Previewの軽量化・テスタビリティの向上を目的とする。
背景・目的
現在のプロジェクト構造(Swift ファイル 131 本)では、すべてのコードがメインターゲット homete 内に存在しており、以下の課題が生じている。
- 変更箇所が少ない場合でもターゲット全体が再コンパイルされ、ビルド時間が長い
- レイヤー間の依存方向がコードレベルで強制されず、誤った依存が生まれやすい
- テストターゲットがメインターゲット全体に依存するため、ユニットテストの実行が遅い
- Feature単体でのPreviewビルドができず、プレビューが重い
- 将来的にチームが拡大した場合、同一ターゲット内の変更が衝突しやすい
アーキテクチャ方針
モジュール構成
HometeDomain/
├── Domain Models (Account, HouseworkItem, CohabitantData...)
├── Client Protocols + previewValue
├── Stores (AccountStore, HouseworkListStore, CohabitantStore, AccountAuthStore)
└── AppRoute + RouteResolver
HometeUI/
├── DesignSystem (色、フォント、共通スタイル)
├── 共通コンポーネント (ボタン、カード等)
└── ViewUtilities (Alert、Navigation等)
→ HometeDomain に依存
AuthFeature/ → HometeDomain + HometeUI に依存
HouseworkFeature/ → HometeDomain + HometeUI に依存
CohabitantFeature/ → HometeDomain + HometeUI に依存
SettingFeature/ → HometeDomain + HometeUI に依存
HomeFeature/ → HometeDomain + HometeUI に依存
homete (メインターゲット)/
├── Client liveValue 実装
├── Services (FirestoreService, SignInWithAppleService...)
├── RouteResolver 実態
├── RootView / AppTabView / DependenciesInjectLayer
└── Store の初期化・Environment注入
依存方向
homete (メインターゲット) → Feature modules → HometeUI → HometeDomain
- 全 Feature モジュールは HometeDomain + HometeUI のみに依存(Feature 間の直接依存は禁止)
- Feature モジュールは純粋に View 層のみを持つ
- HometeUI は HometeDomain に依存(ドメインモデルを表示に使う場合)
Feature 間の画面遷移(RouteResolver パターン)
Feature 間で直接依存せずに画面遷移を実現するため、HometeDomain に AppRoute enum と RouteResolver を定義し、メインターゲットで実態を DI する。
// HometeDomain 側
enum AppRoute: Hashable {
case houseworkDetail(HouseworkItem)
case houseworkApproval(HouseworkItem)
case registerHousework(CohabitantData)
case cohabitantRegistration
case setting
}
struct RouteResolver {
var resolve: @MainActor (AppRoute) -> AnyView
}
extension EnvironmentValues {
@Entry var routeResolver: RouteResolver = .preview
}
// Preview 用
extension RouteResolver {
static let preview = RouteResolver { route in
AnyView(Text("Preview: \(String(describing: route))"))
}
}// Feature 側(例: HomeFeature)
struct HomeView: View {
@Environment(\.routeResolver) private var router
var body: some View {
.sheet(isPresented: $isShowSetting) {
router.resolve(.setting)
}
}
}// メインターゲット側
let resolver = RouteResolver { route in
switch route {
case .houseworkDetail(let item):
AnyView(HouseworkDetailView(item: item))
case .setting:
AnyView(SettingView())
// ...
}
}
RootView()
.environment(\.routeResolver, resolver)Store の配置方針
Store は HometeDomain に配置する。理由:
- Store は特定の画面に依存しない、全画面で再利用可能なビジネスルール・ドメインモデルを提供するオブジェクト
- 複数 Feature から共有される(例: HouseworkListStore は HouseworkBoardView, HomeView 等で利用)
- Client Protocol に依存するが、これも HometeDomain 内にあるため整合性が取れる
- メインターゲットで Store を初期化し、Environment 経由で各 Feature に注入
実装タスク
- Phase 1: HometeDomain パッケージの切り出し(Domain Models + Client Protocols + Stores + AppRoute)
- Phase 3: Feature パッケージの切り出し(AuthFeature, HouseworkFeature, CohabitantFeature, SettingFeature, HomeFeature)
- Phase 4: メインターゲットの整理(Services + liveValue実装 + RouteResolver実態 + RootView)
- Phase 5: テストターゲットの整理(モジュールごとのテストターゲット追加 or 既存の hometeTests/ を更新)
- Phase 6: CI ビルド時間・テスト実行時間の計測・比較
補足
- 段階的移行(Phase 1 から順に PR を分けてマージ)を推奨。1 PR での全移行はリスクが高い。
- ProjectTools(SwiftLint / Danger)は現行のローカルパッケージ形式のまま変更不要。
- Firebase iOS SDK 等のサードパーティ依存はメインターゲット(Services 層)が保持する。
- Swift 6 strict concurrency との整合性(actor 分離、Sendable 等)は各 Phase で確認する。
- Feature 間の循環依存を防ぐため、Feature 間の画面遷移は必ず RouteResolver パターンを使用する。
Reactions are currently unavailable
Sub-issues
Metadata
Metadata
Assignees
Labels
No labels