mirror of
https://github.com/otaviocc/Triton.git
synced 2026-01-30 04:04:27 +00:00
89 lines
4.2 KiB
Markdown
89 lines
4.2 KiB
Markdown
# ADR-002: Layered Architecture and Dependency Direction
|
|
|
|
**Status:** Accepted
|
|
|
|
**Date:** 2025-01-11
|
|
|
|
**Context:**
|
|
|
|
When designing the OMG application, I wanted to establish clear architectural boundaries that would prevent common issues like tight coupling, circular dependencies, and difficulty testing. I needed a structure that would:
|
|
|
|
1. Separate concerns cleanly (UI, business logic, data access)
|
|
2. Make dependencies explicit and enforceable
|
|
3. Enable testing at each layer in isolation
|
|
4. Prevent accidental violations of architectural rules
|
|
|
|
**Decision:**
|
|
|
|
I adopted a strict layered architecture with enforced one-way dependency flow. The layers are:
|
|
|
|
```
|
|
Views
|
|
↓
|
|
View Models
|
|
↓
|
|
Repositories
|
|
↓
|
|
Network & Persistence Services
|
|
↓
|
|
Shared Services (OMGAPI, SessionService, DesignSystem)
|
|
↓
|
|
Foundation Modules (FoundationExtensions, Utilities)
|
|
```
|
|
|
|
**Dependency Rules (Enforced):**
|
|
|
|
1. **UI → Repository (NOT → Services):** Views and ViewModels depend on Repository protocols, never directly on NetworkService or PersistenceService
|
|
2. **Repository → Services:** Repositories coordinate between NetworkService and PersistenceService layers
|
|
3. **Services → Shared Utilities:** Services depend only on infrastructure (OMGAPI, SessionService) and utilities
|
|
4. **No upward dependencies:** Lower layers cannot import higher layers
|
|
5. **Cross-cutting packages independent:** Infrastructure packages (DesignSystem, OMGAPI) do not depend on features
|
|
|
|
**Layer Responsibilities:**
|
|
|
|
- **Views (SwiftUI):** Presentation logic only, delegating actions to ViewModels
|
|
- **ViewModels (@Observable):** UI state management, coordinating user actions via Repository protocols
|
|
- **Repositories:** Domain logic, caching strategies, data coordination between network and persistence
|
|
- **NetworkService:** API communication, mapping remote payloads to DTOs (OMGAPI models)
|
|
- **PersistenceService:** Swift Data storage, local data management with domain or DTO representations
|
|
- **Shared Infrastructure:** Cross-cutting concerns (HTTP client, session management, UI components)
|
|
- **Foundation Modules:** Pure utilities with no domain knowledge
|
|
|
|
**How Dependencies are Enforced:**
|
|
|
|
SPM package dependencies in `Package.swift` files make violations impossible at compile time. For example:
|
|
- A View's package can depend on Repository but not NetworkService
|
|
- NetworkService cannot import Repository (would be rejected by SPM)
|
|
- Repositories are often `actor` types, ensuring thread-safe data operations
|
|
|
|
**Consequences:**
|
|
|
|
### Positive
|
|
|
|
- **Testability:** Each layer can be tested independently using protocol mocks/stubs
|
|
- **Compile-time safety:** Architectural violations are caught by the Swift compiler
|
|
- **Clear responsibilities:** Each layer has a well-defined purpose
|
|
- **Flexibility:** Implementations can be swapped without affecting higher layers (e.g., switching persistence strategies)
|
|
- **Reasoning:** Easy to understand where code belongs and how data flows
|
|
- **Concurrency safety:** Actor isolation at repository layer prevents data races
|
|
|
|
### Negative
|
|
|
|
- **Initial complexity:** New features require thinking about multiple layers
|
|
- **Boilerplate:** Protocol definitions and implementations add code volume
|
|
- **Cross-layer changes:** Some features require touching multiple layers sequentially
|
|
|
|
### Neutral
|
|
|
|
- **Layer granularity:** Ongoing decisions about when to introduce intermediate layers (e.g., Service layer between Repository and UI)
|
|
|
|
**Related Decisions:**
|
|
|
|
- [ADR-001: Modular Architecture with Swift Package Manager](ADR-001-modular-architecture-with-spm.md) - SPM enables enforcement
|
|
- [ADR-003: Feature-Based Package Organization](ADR-003-feature-based-package-organization.md) - How layers map to package targets
|
|
- [ADR-009: Protocol-First Repository and Service Boundaries](../Patterns/ADR-009-protocol-first-boundaries.md) - Testing and abstraction approach
|
|
- [ADR-012: DTO-Based Data Flow](../Data-Flow/ADR-012-dto-based-data-flow.md) - How data transforms across layers
|
|
|
|
**Notes:**
|
|
|
|
This strict layering was designed from the start to prevent architectural erosion over time. The one-way dependency flow ensures the codebase remains maintainable as it grows.
|