Triton/Documentation/ADR/ADR-003-feature-based-package-organization.md
Otávio 3e878667a1 Add Triton App
Signed-off-by: Otavio Cordeiro <otaviocc@users.noreply.github.com>
2025-12-15 20:39:07 +01:00

4.6 KiB

ADR-003: Feature-Based Package Organization

Status: Accepted

Date: 2025-01-11

Context:

With a modular architecture established via Swift Package Manager, I needed to define how to organize code within each package. The key questions were:

  1. What constitutes a "feature" worthy of its own package?
  2. How should layers be represented within a feature package?
  3. What should be public vs. private in each package?
  4. How do features integrate with the main application?

Decision:

I adopted a feature-based package organization where each domain feature (Auth, Status, Account, PURLs, Pastebin, etc.) is a separate package with multiple internal targets representing architectural layers.

Package Structure Pattern:

FeatureName/
├── Package.swift                       # Dependencies and targets
├── Sources/
│   ├── FeatureName/                    # Main UI and app integration
│   │   ├── Views/                      # All SwiftUI views
│   │   │   ├── App/                    # Main feature view and view model
│   │   │   └── [Feature Areas]/        # Other views grouped by functionality
│   │   ├── Scenes/                     # Private window scenes (WindowGroup)
│   │   ├── Factories/                  # AppFactory with public integration methods
│   │   └── Environment/                # Feature environment and DI setup
│   ├── FeatureNameService/             # Business logic layer (optional)
│   ├── FeatureNameRepository/          # Data coordination
│   ├── FeatureNameNetworkService/      # API communication
│   └── FeatureNamePersistenceService/  # Swift Data storage
└── Tests/                              # Unit tests for each layer

Public API Surface:

Each feature package exposes a minimal public API through its AppFactory:

  1. makeAppView() - Creates the main feature view for display in the sidebar
  2. makeScene() - Returns window scenes (private to the module) as some Scene
  3. makeSettingsView() - Creates settings views (when applicable)

Everything else (scenes, view models, repositories, services) remains internal to the package.

Feature Environment:

Each feature has its own Environment struct that:

  • Manages a MicroContainer for dependency resolution
  • Registers all layer factories (NetworkService, PersistenceService, Repository)
  • Exposes viewModelFactory and modelContainer properties
  • Is passed to scenes which use it to build views

Feature Categories:

  • Domain Features: Auth, Account, Status, Now, PURLs, Webpage, Pastebin, Weblog, Pics
  • Infrastructure Packages: OMGAPI, DesignSystem, SessionService, AuthSession, Route, Sidebar
  • Utility Packages: FoundationExtensions, Utilities

Integration with Main App:

The main application (TritonApp) uses TritonEnvironment to resolve all feature factories via MicroContainer. Scenes are now private within feature modules and receive the feature Environment to build views.

Consequences:

Positive

  • Feature isolation: Each feature is self-contained with clear boundaries
  • Parallel development: Features can evolve independently without conflicts
  • Selective testing: Can test individual features without loading others
  • Clear ownership: Each feature package owns its complete vertical slice
  • Reduced coupling: Features cannot access each other's internals
  • Minimal public API: Only integration points are exposed, reducing breaking changes

Negative

  • Package proliferation: Many packages to navigate (mitigated by clear naming)
  • Cross-feature sharing: Shared functionality must be extracted to infrastructure packages
  • Discovery: Finding code requires knowing which feature it belongs to

Neutral

  • Feature granularity: Judgment calls about feature boundaries (e.g., should PURLs and Status be one package?)
  • Shared UI components: Decision about when to move components to DesignSystem vs. keeping them in features

Related Decisions:

Notes:

This structure was designed from the start to support clean feature boundaries and vertical slice architecture. Each package represents a complete feature with all its layers.