This Repository is intended to be a new pattern based on Clean, Redux and MVVM, so from time to time, i'll update this readme with all implementation samples and testability.
This project includes comprehensive GitHub Actions workflows for continuous integration and deployment.
Triggers: Pull requests to develop, master, or main branches
- Tests: Runs unit tests, UI tests, and snapshot tests in parallel with code coverage
- Builds: Builds the app for both Dev and Prod schemes in Debug and Release configurations
- Code Quality: Checks for TODO/FIXME comments and runs SwiftLint (if configured)
- Artifacts: Uploads test results using explicit
-resultBundlePathfor reliable artifact collection
Triggers: Pushes to master or main branches, or when tags starting with v* are pushed
- Build: Creates production archive and IPA
- Release: Automatically creates GitHub releases for tagged versions
- Artifacts: Uploads production builds and IPA files
Triggers: Daily at 2 AM UTC, or manually via workflow dispatch
- Parallel Execution: Runs unit, UI, and snapshot tests concurrently
- Health Check: Ensures the project stays healthy with daily test runs
- Monitoring: Provides early warning of breaking changes
- Artifact Preservation: Test results stored with 5-day retention
- XcodeGen Integration: Automatically generates the Xcode project from
project.yml - Multi-Scheme Testing: Tests GitHubAppDev, GitHubAppProd, and dedicated test schemes (GitHubAppTests, GitHubAppUITests, GitHubAppSnapshotTests)
- Parallel Test Execution: Unit, UI, and snapshot tests run concurrently for faster feedback
- Code Coverage: Enables code coverage reporting for unit tests
- Swift Testing: Uses modern Swift Testing framework for enhanced test performance
- SwiftData Integration: Tests data persistence layer with in-memory configurations
- Artifact Management: Preserves test results using explicit
-resultBundlePathconfiguration - Resilient Workflows: Gracefully handles missing artifacts with
continue-on-errorandif: always() - Matrix Builds: Tests multiple configurations simultaneously
- Quality Gates: Checks for code quality issues before deployment
To test the workflows locally, you can use the Makefile commands:
# Run all tests (similar to CI pipeline)
make test
# Run specific test types
make test-unit # Unit tests only
make test-ui # UI tests only
make test-snapshot # Snapshot tests only
# Run tests with coverage and print app coverage
make coverage
# Generate/update local coverage badge (badges/coverage.svg)
make coverage-badge
# Generate project (required for CI)
make generate
# Clean generated files
make cleanFor production deployments:
-
Create a release tag:
git tag v1.0.0 git push origin v1.0.0
-
The deploy workflow will automatically:
- Build the production app
- Create a GitHub release
- Upload the IPA file as a release asset
- Team ID: Update
scripts/exportOptions.plistwith your Apple Developer Team ID - Secrets: The workflows use
GITHUB_TOKENwhich is automatically provided - Branches: Ensure your default branch is
masterormain
- SwiftLint: Add SwiftLint configuration for additional code quality checks
- Code Coverage: Configure coverage reporting tools
- Notifications: Add Slack/Discord webhooks for build notifications
This project uses XcodeGen to generate the Xcode project from a YAML specification. This makes the project configuration more maintainable and version control friendly.
- macOS
- Homebrew (for installing XcodeGen)
- Xcode 26.0.1+
- iOS 26.1+ deployment target
-
Install XcodeGen and generate the project:
make setup
Or run the scripts manually:
./scripts/install-xcodegen.sh ./scripts/generate-project.sh
-
Set up API Key (Required):
The app requires an API key from The Movie Database to function.
Option A: Use the provided script:
./scripts/run-dev.sh your_api_key_here
Option B: Use Makefile command:
API_KEY='your_api_key_here' make run-devOption C: Set environment variable manually:
export API_KEY='your_api_key_here' open GitHubApp.xcodeproj
-
Open the generated project:
open GitHubApp.xcodeproj
make help- Show all available commandsmake install-xcodegen- Install XcodeGen using Homebrewmake generate- Generate Xcode project from project.ymlmake clean- Remove generated Xcode projectmake setup- Install XcodeGen and generate projectmake test- Run all unit tests
The project configuration is defined in project.yml:
- Targets: GitHubApp (main app), GitHubAppTests (unit tests), GitHubAppUITests (UI tests), GitHubAppSnapshotTests (snapshot tests)
- Schemes: GitHubAppDev, GitHubAppProd, GitHubAppTests, GitHubAppUITests, GitHubAppSnapshotTests
- Settings: iOS 26.1+ deployment target, Swift 5.0
- Environment Variables: API keys configured per scheme
Feature Structure:
GitHubApp/
├── Home/ # Main movie browsing
├── Search/ # Movie search with Liquid Glass design UI
├── Favorites/ # Saved favorite movies
├── Settings/ # App preferences and configuration
├── Paywall/ # Premium subscription with native StoreKit 2
├── Configs/ # Shared utilities, mocks, and configuration
├── Widgets/ # iOS widget extension with image caching
└── Localization/ # Multi-language support (EN, ES, PT-BR)
To modify the project structure:
- Edit
project.yml - Run
make generateto regenerate the Xcode project - The changes will be reflected in the generated
GitHubApp.xcodeproj
- Version Control Friendly: Project configuration is in YAML format
- Consistent Structure: Enforces consistent project organization
- Easy Maintenance: No more merge conflicts in .pbxproj files
- Team Collaboration: Everyone generates the same project structure
The project includes multiple Xcode schemes optimized for different development scenarios:
| Scheme | Purpose | Configuration | Test Targets |
|---|---|---|---|
| GitHubAppDev | Main development | Debug | All tests (Unit + UI + Snapshot) |
| GitHubAppProd | Production builds | Release | All tests (Unit + UI + Snapshot) |
| GitHubAppTests | Unit testing only | Debug | Unit tests only |
| GitHubAppUITests | UI testing only | Debug | UI tests only |
| GitHubAppSnapshotTests | Snapshot testing only | Debug | Snapshot tests only |
Usage Examples:
- Use
GitHubAppTestsscheme for rapid unit test iterations during development - Use
GitHubAppUITestsscheme when focusing on UI testing and debugging - Use
GitHubAppDevfor comprehensive testing before commits - Use
GitHubAppProdfor release validation
Browse and discover movies. The Home feature serves as the primary reference implementation for Clean Architecture in this project.
Search for movies by title with a modern Liquid Glass design UI. Implements dedicated Clean Architecture with:
- SearchDomainInteractor: Handles search queries and state management
- SearchService: API integration for movie search
- SearchView: Beautiful SwiftUI interface with real-time search
- Comprehensive unit tests and UI snapshot tests
Save and manage your favorite movies with persistent storage using SwiftData.
Configure app preferences including language selection (English, Spanish, Portuguese-BR).
Native SwiftUI StoreKit 2 paywall for premium subscriptions:
- SubscriptionStoreView: Native iOS 17+ paywall with Apple's design guidelines
- Clean Architecture: Full implementation with API, Domain, View, and ViewModel layers
- StoreKit 2: Modern async/await transaction handling
- Subscription Products: Monthly ($4.99) and Yearly ($39.99) options
- Testing Support: StoreKit configuration file for simulator testing
- Multi-language: Localized in English, Spanish, and Portuguese-BR
Full multi-language support:
- 🇬🇧 English
- 🇪🇸 Spanish (Español)
- 🇧🇷 Portuguese - Brazil (Português-BR)
iOS widget with:
- Real-time movie display
- Image caching via App Groups
- Clean Architecture implementation
This app supports deeplinks for navigating directly to specific content. Deeplinks use a custom URL scheme (githubapp://) to provide seamless navigation within the app.
Navigate directly to a movie's details page:
githubapp://movie/{movieId}
Examples:
githubapp://movie/123- Navigate to movie with ID 123githubapp://movie/456- Navigate to movie with ID 456
The deeplink system consists of several components:
- DeeplinkManager: Parses URLs and validates deeplink formats
- DeeplinkRouter: Routes parsed deeplinks to appropriate navigation actions
- URL Scheme: Custom
githubapp://scheme registered in Info.plist
Test deeplink functionality using the Makefile:
# Test only deeplink-related functionality
make deeplink-test
# Run all tests including deeplinks
make testTo add support for new deeplink types:
- Update DeeplinkManager.URLScheme with new cases
- Add parsing logic in
parse(url:)method - Update DeeplinkRouter to handle new deeplink types
- Add unit tests for the new functionality
This app implements Clean Architecture principles to ensure separation of concerns, testability, and maintainability. The Home feature serves as a reference implementation of this architectural pattern.
The Clean Architecture implementation follows these key principles:
- Separation of Concerns: Each layer has a specific responsibility
- Dependency Inversion: Higher-level modules don't depend on lower-level modules
- Reactive Programming: Uses Combine for reactive data flow
- Data Persistence: SwiftData for modern, efficient data storage
- Testing Framework: Swift Testing for enhanced test syntax and performance
- Single Source of Truth: ViewModels maintain a single published state
- Testability: Each component can be tested in isolation
┌─────────────────────────────────────────────────────────────────────────────────┐
│ VIEW LAYER │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ View │ │
│ │ (SwiftUI) │ │
│ │ │ │
│ │ • @StateObject │ │
│ │ • User Actions │ │
│ │ • UI Rendering │ │
│ │ • Reactive UI │ │
│ └─────────────────┘ │
│ │ │
│ │ ViewEvent │
│ │ (User interactions, lifecycle events) │
│ ▼ │
└─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ViewModel │◄──────────────────────►│ ViewState │ │
│ │ │ │ Reducing │ │
│ │ • CombineViewModel │ │ │
│ │ • @Published │ │ • Protocol │ │
│ │ viewState │ │ • Domain→View │ │
│ │ • Single Source │ │ Transformation│ │
│ │ of Truth │ │ • Pure Function │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ ▲ │
│ │ DomainAction │ │
│ │ │ │
│ ▼ │ DomainState │
│ ┌─────────────────┐ │ │
│ │ DomainEvent │ │ │
│ │ ActionMap │─────────────────────────────────┘ │
│ │ │ │
│ │ • Maps ViewEvent│ │
│ │ to DomainAction │
│ │ • Translation │ │
│ │ Layer │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ DomainAction │ │ DomainState │ │
│ │ │ │ │ │
│ │ • Enum Cases │ │ • Complete │ │
│ │ • Business │ │ Feature State │ │
│ │ Operations │ │ • Data Models │ │
│ │ • Pure Values │ │ • Loading/Error │ │
│ │ • Equatable │ │ • Equatable │ │
│ └─────────────────┘ │ • Initial State │ │
│ │ └─────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ DomainInteractor│ (Service Bridge) │ │
│ │ │─────────────────────────────────┘ │
│ │ • CombineInterac│ @Published │
│ │ tor Protocol │ currentState │
│ │ • Business Logic│ │
│ │ • State Machine │ ServiceLocator │
│ │ • Side Effects │ ├─► StorageService │
│ │ • Persistence │ ├─► HomeService │
│ │ • API Orchestr. │ └─► Other Services │
│ │ • Service Coord │ │
│ └─────────────────┘ │
│ │ │
│ │ Service Calls │
│ ▼ │
└─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SERVICE LAYER │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Service │ │ External APIs │ │
│ │ │ │ │ │
│ │ • Protocol │◄──────────────────────►│ • REST APIs │ │
│ │ Conformance │ │ • GraphQL │ │
│ │ • AnyPublisher │ │ • Third-party │ │
│ │ Return Types │ │ Services │ │
│ │ • No Service-to │ │ • Databases │ │
│ │ Service Deps │ │ • File System │ │
│ │ • Data Mapping │ │ │ │
│ │ • Error Handling│ │ │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐
│ NETWORK LAYER │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ APIRequest │ │ APIFetcher │ │
│ │ │ │ │ │
│ │ • Protocol from │ │ • Protocol from │ │
│ │ EntropyCore │ │ EntropyCore │ │
│ │ • Provides │ │ • Endpoint │ │
│ │ fetchRequest()│ │ Configuration │ │
│ │ • Main Thread │ │ • URL Building │ │
│ │ Scheduling │ │ • HTTP Methods │ │
│ │ • Generic Types │ │ • Headers/Body │ │
│ └─────────────────┘ │ • Debug Config │ │
│ │ └─────────────────┘ │
│ │ fetchRequest(target:dataType:) ▲ │
│ ▼ │ conforms to │
│ ┌─────────────────┐ │ │
│ │ LiveHomeService │─────────────────────────────────┘ │
│ │ │ HomeAPI │
│ │ • APIRequest │ │
│ │ + HomeService │ ┌─────────────────┐ │
│ │ • Network Calls │ │ HomeAPI │ │
│ │ • Main Thread │ │ │ │
│ │ Delivery │ │ • APIFetcher │ │
│ │ • Type Safety │ │ Enum │ │
│ └─────────────────┘ │ • Endpoints: │ │
│ │ - fetchMovies │ │
│ │ - searchMovies│ │
│ │ - fetchCredits│ │
│ │ - fetchReviews│ │
│ │ • URL Building │ │
│ │ • API Key │ │
│ │ Injection │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
COMMUNICATION FLOW:
═════════════════
┌─────────────────────────────────────────────────────────────────────────────────┐
│ DATA FLOW DIRECTION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ │
│ +----------------+ +------------------------+ +-------------------+ │
│ | View | ----> | ViewModel | ----> | Interactor | │
│ | | | | | | │
│ | ViewEvents | <---- | ViewState DomainMap | <---- | DomainState | │
│ +----------------+ +------------------------+ +-------------------+ │
│ ^ | │
│ | v │
│ | +---------------------------+ │
│ +----| ViewStateReducing | │
│ +---------------------------+ │
│ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
SYMBOLS LEGEND:
══════════════
──► Synchronous Call/Data Pass │ Dependency/Call Direction
◄── Reactive State Flow ▼ Asynchronous Operation
◄──► Bidirectional Communication ▲ State Observation/Update
- View: SwiftUI view that renders the UI and handles user interactions
- Uses
@StateObjectfor proper reactive updates and lifecycle management - Implements user interaction handling (buttons, search, gestures, etc.)
- Observes
viewStatefor reactive UI updates and re-rendering - Focuses purely on presentation logic and user experience
- ViewModel: Coordinates between view and domain layer using
CombineViewModel - Implements single source of truth with
@Published viewState - Uses
DomainEventActionMapto translate UI events to domain actions - Uses
ViewStateReducingto convert domain state to view state - Acts as the presentation coordinator without business logic
- DomainInteractor: Contains all business logic and state management using
CombineInteractor- Acts as a service bridge when multiple services are needed
- Retrieves all dependencies from ServiceLocator
- Coordinates between multiple services (e.g., StorageService, HomeService)
- DomainAction: Defines all possible business operations as enum cases
- DomainState: Represents the complete feature state with all necessary data
- Handles data persistence, validation, API orchestration, and business rules
- Pure business logic without UI or framework dependencies
- Service: Handles external data sources and API communications
- Returns reactive publishers (
AnyPublisher<Response, Error>) for async operations - Manages network requests, database operations, file system access
- Provides data transformation and error handling
- No Service-to-Service Dependencies: Services are independent and don't depend on other services
- All services registered through ServiceLocator and coordinated by DomainInteractors
The app implements a bridge pattern where DomainInteractors coordinate storage and API operations:
Key Principles:
- Service Independence: Services don't depend on other services
- DomainInteractor as Bridge: Coordinates between multiple services (StorageService, HomeService, etc.)
- ServiceLocator Coordination: Each DomainInteractor retrieves needed services from ServiceLocator
- No Singleton State: Eliminated StorageServiceFactory singleton pattern
Example Structure:
┌──────────────────────────────────┐
│ DomainInteractor (Bridge) │
│ │
│ • Retrieves from ServiceLocator │
│ • Coordinates StorageService │
│ • Orchestrates API calls │
│ • Manages business logic │
└──────────┬───────────────────────┘
│
┌────┴─────────────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ StorageService │ │ HomeService │
│ │ │ │
│ • SwiftData storage │ │ • API calls │
│ • Independent │ │ • Independent │
│ • No cross-service │ │ • No storage knowledge│
│ dependencies │ │ │
└──────────────────────┘ └──────────────────────┘
Testing Benefits:
- Isolated mock services via ServiceLocator
- Each test controls which services are injected
- No shared singleton state between tests
- Easy to test service coordination logic
✅ Separation of Concerns: Each layer has a single responsibility ✅ Testability: All components can be unit tested in isolation ✅ Maintainability: Changes in one layer don't affect others ✅ Scalability: Easy to add new features following the same pattern ✅ Reactive: Uses Combine for efficient state management ✅ Single Source of Truth: Eliminates state synchronization issues ✅ Service Independence: No service-to-service dependencies ✅ Clean Coordination: DomainInteractors handle multi-service orchestration
The architecture enables comprehensive testing at every layer:
- View Tests: Test UI rendering and user interactions with Swift Testing
- ViewModel Tests: Test state transformations and event handling using Swift Testing framework
- Domain Tests: Test business logic and state management with enhanced test syntax
- Service Tests: Test API integrations and data mapping
- Storage Tests: Test SwiftData persistence with in-memory configurations
- Integration Tests: Test complete data flow end-to-end
Any feature can implement this Clean Architecture pattern by following these steps:
- Define Domain Models: Create
DomainAction(enum) andDomainState(struct) for your feature - Implement Business Logic: Create
DomainInteractorconforming toCombineInteractor - Create Translation Layer: Implement
DomainEventActionMapto map UI events to domain actions - Build State Reducer: Create
ViewStateReducingprotocol and implementation - Refactor Presentation Layer: Update
ViewModelto conform toCombineViewModel - Update View Layer: Ensure
Viewuses@StateObjectand observesviewState - Add Comprehensive Testing: Create unit tests for each component in isolation
Reference Implementations:
- Home Feature: Complete implementation with data persistence
- Search Feature: Real-time search with state management and Liquid Glass UI design
For any feature (e.g., UserProfile, Settings, Dashboard):
// 1. Domain Models
enum UserProfileDomainAction { ... }
struct UserProfileDomainState { ... }
// 2. Business Logic
class UserProfileDomainInteractor: CombineInteractor { ... }
// 3. Translation Layer
enum UserProfileDomainEventActionMap { ... }
// 4. State Reducer
protocol UserProfileViewStateReducing { ... }
// 5. Presentation Layer
class UserProfileViewModel: CombineViewModel { ... }
// 6. View Layer
struct UserProfileView: View { ... }This project uses a versioned pre-commit hook to enforce SwiftFormat linting before every commit.
After cloning the repository, run:
sh setup-git-hooks.shThis will install the pre-commit hook locally. The hook will block any commit if SwiftFormat finds files that need to be reformatted. To fix formatting issues, run:
mint run swiftformat .Then stage the changes and commit again.
The app implements a robust network layer using the EntropyCore framework with a clean separation between API configuration and network execution. The network layer is built on top of two key protocols: APIFetcher and APIRequest.
Defines the structure and configuration for API endpoints:
path: String- Complete URL for the API request, including query parametersmethod: HTTPMethod- HTTP method (GET, POST, PUT, DELETE, etc.)task: Codable?- Request body for POST/PUT requests (nil for GET requests)header: Codable?- Custom headers for the request (nil if not needed)debug: Bool- Enables debug logging for development builds
Provides the network execution capabilities:
fetchRequest(target: APIFetcher, dataType: T.Type)- Generic method to execute network requests- Type Safety - Returns strongly-typed responses using Codable models
- Reactive - Returns
AnyPublisher<T, Error>for Combine integration - Main Thread Delivery - Automatically schedules responses on the main thread
enum HomeAPI: APIFetcher {
case fetchMovies
case searchMovies(String)
case fetchCredits(Int)
case fetchReviews(Int)
var path: String {
// URL construction with BaseURLs and query parameters
// Automatic API key injection via APIKeysProvider
}
var method: HTTPMethod { .GET }
var task: Codable? { nil }
var header: Codable? { nil }
var debug: Bool {
#if DEBUG
return true
#else
return false
#endif
}
}final class LiveHomeService: APIRequest, HomeService {
func fetchMovies() -> AnyPublisher<MoviesResponse, Error> {
fetchRequest(target: HomeAPI.fetchMovies, dataType: MoviesResponse.self)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}The network layer uses APIKeysProvider for secure credential management:
- Primary Source:
Secrets.plist(gitignored for security) - Fallback 1: Environment variables (
API_KEY) - Fallback 2: iOS Keychain storage
- Development: Default fallback for testing
enum APIKeysProvider {
static let theMovieAPIKey: String = {
// Hierarchical fallback system
// 1. Secrets.plist → 2. Environment → 3. Keychain → 4. Default
}()
}Uses BaseURLs enum from EntropyCore for centralized URL management:
BaseURLs.theMovie- The Movie Database API base URLBaseURLs.image- Image CDN base URL for movie posters- Environment Specific - Different URLs for dev/staging/production
- No Hardcoded Keys: All API keys managed through secure fallback system
- Keychain Storage: Sensitive data stored in iOS Keychain
- Environment Separation: Different configurations per build environment
- Type Safety: Compile-time validation of request/response models
- Generic Implementation: Reusable network methods with strong typing
- Main Thread Optimization: Automatic UI thread scheduling
- Memory Efficient: Uses Combine publishers for reactive data flow
- Debug Logging: Automatic request/response logging in debug builds
- URL Construction: Automatic query parameter encoding and URL building
- Error Handling: Structured error types with localized descriptions
- Testing Support: Easy mocking through protocol conformance
enum APIError: Error, LocalizedError {
case invalidBaseURL(String)
case urlConstructionFailed
var errorDescription: String? {
// Localized error messages
}
}// In Service Layer
func fetchMovies() -> AnyPublisher<MoviesResponse, Error> {
fetchRequest(target: HomeAPI.fetchMovies, dataType: MoviesResponse.self)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}// Dynamic endpoint with query parameters
func searchMovies(with query: String) -> AnyPublisher<MoviesResponse, Error> {
fetchRequest(target: HomeAPI.searchMovies(query), dataType: MoviesResponse.self)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}// RESTful endpoint with path parameters
func fetchCredits(with id: Int) -> AnyPublisher<MovieCreditsResponse, Error> {
fetchRequest(target: HomeAPI.fetchCredits(id), dataType: MovieCreditsResponse.self)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}The network layer is fully testable through protocol conformance and dependency injection:
// Services registered through ServiceLocator
serviceLocator.register(HomeService.self, instance: MockHomeService())
// Automatic mock injection in test environments
class MockHomeService: HomeService {
func fetchMovies() -> AnyPublisher<MoviesResponse, Error> {
// Return mock data for testing
}
}- URL Construction: Tests with various query parameters and encoding scenarios
- Error Handling: Tests for network failures and invalid responses
- API Key Management: Tests for different credential scenarios
- Large Data: Performance tests with extensive API responses
The network layer seamlessly integrates with the Clean Architecture pattern:
- Domain Layer: Business logic calls service protocols (not network directly)
- Service Layer: Implements protocols using APIRequest + APIFetcher
- Dependency Injection: Services registered via ServiceLocator
- Testing: Mock services injected automatically in test environments