Modular Architecture & Swift Packages
Relevant source files
The following files were used as context for generating this wiki page:
Trackurr utilizes a highly modular architecture designed for maintainability, testability, and clear separation of concerns. The project is structured around self-contained feature modules and shared logic residing in Swift Packages, ensuring that the main application target remains lightweight and focused on orchestration.
The architecture follows Domain-Driven Design principles, separating domain models from persistence logic, and utilizes a centralized Dependency Injection (DI) system via the Factory framework. This allows for easy swapping of implementations, particularly useful for providing mocks during testing.
Sources: CLAUDE.md:8-12, README.md:65-72
Project Structure and Layering
The codebase is organized into three primary directories that define the dependency hierarchy: Trackurr/ (App Target), Features/, and Packages/.
Core Layers
| Layer | Path | Description |
|---|---|---|
| App Target | Trackurr/ | The main entry point containing app-level configuration, persistence setup, and the global DI container. |
| Feature Modules | Features/ | Domain-specific modules (e.g., CategoriesFeature, TagsFeature) that contain UI logic and ViewModels. |
| Shared Packages | Packages/ | Fundamental building blocks including Domain models, Interfaces, Services, and SharedUI. |
Sources: CLAUDE.md:27-33, README.md:75-84
Dependency Flow
The following diagram illustrates the high-level relationship between the main app target, feature modules, and shared packages.
The App Target acts as the orchestrator, while Feature Modules depend on shared interfaces and domain models to perform business logic without directly depending on concrete service implementations.
Sources: CLAUDE.md:8-12, README.md:65-72
Feature Modules
Feature modules are self-contained Swift Packages located in the Features/ directory. Each module follows a standardized structure: Features/<FeatureName>Feature/. These modules are responsible for specific user-facing functionality and adhere to the MV pattern using @Observable ViewModels.
Key characteristics of feature modules include:
- Isolation: Features should avoid direct dependencies on other features to prevent circular dependencies.
- Testing: Swift Testing (
import Testing) is used for unit tests, which are kept near the owning module (e.g.,Features/TagsFeature/Tests/). - Localization: User-facing strings are managed via String Catalogs (
Localizable.xcstrings) within each package.
Sources: CLAUDE.md:27-28, README.md:144-150, AGENTS.md:27-28
Shared Packages
Shared logic and components are distributed across several packages in the Packages/ directory to facilitate code reuse and clear boundaries.
Package Breakdown
- Domain: Contains pure business logic and models. It is intentionally kept free of localization and persistence details.
- Interfaces: Defines service protocols. This layer is critical for the DI system, allowing features to depend on abstractions.
- Services: Concrete implementations of protocols defined in the
Interfacespackage. - SharedUI: Reusable SwiftUI components and design system elements (e.g.,
MoneyText,PlaceholderIcon). - Mocks: Test doubles used across different test suites to simulate service behavior.
Sources: CLAUDE.md:31-33, README.md:78-84, Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift:1-10
Dependency Injection (DI)
Trackurr uses the Factory library for compile-safe dependency injection. The DI container is centralized in Trackurr/Dependencies/Container/.
This pattern allows ViewModels to remain decoupled from service instantiation. In testing environments, the makeSUT() helper functions typically register mock implementations into the Container.shared before initializing the ViewModel.
Sources: CLAUDE.md:22-23, README.md:67-68, Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift:293-305
Persistence and Migrations
Persistence is handled via SwiftData, with the core setup, stores, and migration plans living within the main app target under Trackurr/Dependencies/Persistence/.
Migration Strategy
The project implements a structured SchemaMigrationPlan to handle changes to the data model across versions.
| Version | Key Changes | Type |
|---|---|---|
| V1 to V2 | Added Preferences entity. | Lightweight |
| V2 to V3 | Removed @Attribute(.unique) for CloudKit compatibility. | Lightweight |
| V7 to V8 | Separated system entities (Local) from CloudKit-synced entities. | Custom |
Sources: Trackurr/Dependencies/Persistence/Migration/AppMigrationPlan.swift:15-60
System vs. Custom Entities
Starting in Schema V8, the architecture distinguishes between local-only system entities (seeded from JSON) and user-created entities synced via CloudKit.
- SystemCategory / SystemPaymentMethod: Local-only, non-synced.
- Subscription: References system entities via stable IDs (e.g.,
systemCategoryId) or custom entities via standard SwiftData relationships.
Sources: Trackurr/Dependencies/Persistence/Migration/AppMigrationPlan.swift:65-75, Trackurr/Dependencies/Persistence/Migration/SchemaV8.swift:42-50
Summary
The modular architecture of Trackurr ensures a scalable environment where features can be developed and tested in isolation. By leveraging Swift Packages for domain logic, interfaces, and UI components, and utilizing Factory for DI, the project maintains a clear separation between business rules, UI representation, and data persistence.