Architecture Overview
Architecture Overview
Section titled âArchitecture OverviewâMOFA architecture is organized into four primary layers, each with clear responsibilities and defined sources of truth. This reference provides complete details for each layer.
Layer Hierarchy
Section titled âLayer HierarchyâData flows through three model layers: GQL Raw Models -> Domain Models -> Presentation State
The Convertor pattern provides composable pipelines for transforming data across these layers.
1. Datasource Layer
Section titled â1. Datasource LayerâResponsibilities
Section titled âResponsibilitiesâ- Handles all remote data operations (GraphQL, REST APIs)
- Implements repository patterns to abstract data access
- Transforms GQL raw models into domain models via Convertor-based model convertors
- Uses GraphQL with Ferry client and the Convertor pattern
- No local storage - caching handled by dedicated service layer or Ferryâs built-in cache
Key Components
Section titled âKey ComponentsâRemote Data Sources
Section titled âRemote Data Sourcesâ- GraphQL Clients: Ferry-based GraphQL operations
- REST Clients: Dio-based HTTP operations
- WebSocket Clients: Real-time data connections
Repository Implementations
Section titled âRepository Implementationsâ- Concrete classes implementing domain repository interfaces
- Handle data fetching, cache coordination, and error handling
- Transform GQL raw models to clean domain models using model convertors
- Data cleaning happens here (null handling, default values, type mapping)
Convertor Pattern Components
Section titled âConvertor Pattern Componentsâ- Request Convertors: Build Ferry operation requests from typed parameters
- GraphQLRequestExecutor: Execute operations via Ferry client stream
- GraphQLStreamConvertor: Unwrap Ferry response streams into clean data streams
- CacheHandlerSpecs + UpdateCacheTypedLink: Declarative cache management
- Model Convertors: Transform GQL raw types into domain models
2. Domain Layer
Section titled â2. Domain LayerâDomain models, CRUD inputs, filters, sorts, domain exceptions
Responsibilities
Section titled âResponsibilitiesâ- Defines core business entities as plain models (no business logic)
- Contains domain models, value objects, CRUD inputs, filters, sorts
- Provides interfaces for repositories
- Domain-specific exceptions and validation
Key Components
Section titled âKey ComponentsâDomain Models
Section titled âDomain Modelsâ@freezedclass UserModel with _$UserModel { const factory UserModel({ required String id, required String name, required String email, String? avatar, required DateTime createdAt, }) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);}CRUD Input Types
Section titled âCRUD Input Typesâ@freezedclass CreateUserInput with _$CreateUserInput { const factory CreateUserInput({ required String name, required String email, String? avatar, }) = _CreateUserInput;
factory CreateUserInput.fromJson(Map<String, dynamic> json) => _$CreateUserInputFromJson(json);}Filters and Sorts
Section titled âFilters and Sortsâ@freezedclass UserFilter with _$UserFilter { const factory UserFilter({ String? nameContains, String? emailContains, DateTime? createdAfter, DateTime? createdBefore, }) = _UserFilter;}
enum UserSort { name, email, createdAt }Domain Exceptions
Section titled âDomain Exceptionsâabstract class DomainException implements Exception { const DomainException(this.message); final String message;}
class UserNotFoundException extends DomainException { const UserNotFoundException(String userId) : super('User with ID $userId not found');}3. Service Layer (Application Logic)
Section titled â3. Service Layer (Application Logic)âApplication-wide services, business logic, advanced use cases, cross-cutting concerns
When Services Are Required
Section titled âWhen Services Are RequiredâServices are only required for complex logic. Simple CRUD flows go directly from Notifier to Repository. A service is required when:
- Orchestrating multiple repositories in a single operation
- Implementing cross-cutting concerns (error handling, analytics, permissions)
- Coordinating complex business workflows (multi-step, rollback, state machines)
- Managing caching strategies beyond simple read/write
- Integrating external services (push notifications, file uploads)
See ADR-006: Service Layer Refinement for the full criteria.
Service Naming Convention
Section titled âService Naming Conventionâ- Business logic:
[Feature]Service(e.g.,UserService) - Cross-cutting concerns:
[Concern]OrchestratorService(e.g.,DataMergeOrchestratorService)
4. UI Layer
Section titled â4. UI LayerâResponsibilities
Section titled âResponsibilitiesâ- Manages presentation logic and user interface components
- Uses notifiers (
@riverpodcodegen) to manage UI state and react to data changes - For simple CRUD, notifiers access repositories directly (no service needed)
- For complex flows, notifiers delegate to services
- Contains UI tests to ensure correct behavior
Key Components
Section titled âKey ComponentsâEvery feature must handle these states:
- Loading: Show appropriate loading indicators
- Error: Display user-friendly error messages with retry option
- Empty: Show empty state with helpful guidance
- Success: Display the actual content
Three-Model Layer Pipeline
Section titled âThree-Model Layer PipelineâData flows through three distinct model types:
1. GQL Raw Models
Section titled â1. GQL Raw Modelsâ- Generated by Ferry via
build.yamlcustom type mapping - Mapped to Dart classes (not built_value) for direct use
- Live in the datasource layer alongside GraphQL operations
2. Domain Models
Section titled â2. Domain Modelsâ- Clean models defined with
@freezed - Repository-processed: null handling, default values, type conversions
- Live in the domain layer, consumed by services and UI
3. Presentation State
Section titled â3. Presentation Stateâ- AsyncValue wrappers, pagination state, form state
- Managed by notifiers in the UI layer
Transformation Responsibilities
Section titled âTransformation Responsibilitiesâ| From | To | Who | How |
|---|---|---|---|
| GQL Raw | Domain | Repository | Model Convertor (.thenMap() / .thenEach()) |
| Domain | Presentation | Notifier | AsyncValue wrapping, state composition |
| Domain Input | GQL Mutation Vars | Repository | Filter/Sort/Input Convertors |
Supporting Components
Section titled âSupporting ComponentsâCore Folder
Section titled âCore FolderâHouses core features that other features depend on:
- Auth Feature: Authentication logic shared across features
- Router: Centralized navigation logic and route definitions
- Service Locators:
@riverpodproviders for dependency injection - Configuration: Optional flavor configuration and app-wide settings
Caching Service
Section titled âCaching ServiceâHandles all caching scenarios:
- Secure Cache: Sensitive data storage (tokens, credentials)
- Simple Cache: Basic data caching (preferences, settings)
- Complex Cache: Advanced scenarios (offline data, sync strategies)
Generic Packages
Section titled âGeneric PackagesâReusable, business-logic-independent functionality:
- I18n Package: Internationalization and localization utilities
- Assets Package: Asset management and resource handling
- Convertor Package: Shared Convertor pattern implementation
Data Flow Rules
Section titled âData Flow RulesâSimple CRUD Flow (No Service)
Section titled âSimple CRUD Flow (No Service)âNotifier -> Repository -> Datasource (Convertor pipeline)Complex Flow (With Service)
Section titled âComplex Flow (With Service)âNotifier -> Service -> Multiple Repositories -> Multiple DatasourcesCross-Layer Integration
Section titled âCross-Layer Integrationâ- Caching Service: Integrates with Services layer for all storage needs
- Core Folder: Provides shared features, service locators, and configuration to all layers
- Generic Packages: Used by UI and Services layers as needed
Prohibited Patterns
Section titled âProhibited Patternsâ- UI directly calling Datasource
- Datasource importing Services
- Domain containing business logic
- Local storage in Datasource layer
- Feature-to-feature imports (use Core folder instead)
- Passthrough services that add no logic (use direct Notifier -> Repository instead)
This architecture ensures maintainability, testability, and scalability while providing clear boundaries and responsibilities for each layer.