Skip to content

Architecture Overview

MOFA Architecture Diagram

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.

Data flows: Datasource → Domain → Services → UI

Each layer owns its specific concerns and source of truth, with strict boundaries preventing layer-skipping.


Source of Truth:Remote data sources (APIs only)
  • Handles all remote data operations (GraphQL, REST APIs)
  • Implements repository patterns to abstract data access
  • Converts between DTOs and domain models
  • Uses GraphQL with Ferry client and Strategy pattern
  • No local storage - caching handled by dedicated service layer
  • GraphQL Clients: Ferry-based GraphQL operations
  • REST Clients: Dio-based HTTP operations
  • WebSocket Clients: Real-time data connections
  • Concrete classes implementing domain repository interfaces
  • Handle data fetching, caching coordination, and error handling
  • Map DTOs to domain models
  • Request Strategies: Build GraphQL requests avoiding circular dependencies
  • Cache Handler Strategies: Manage optimistic updates and cache invalidation
  • Datasource Modules: Register and configure strategies

Source of Truth:

Domain models, CRUD inputs, filters, sorts, domain exceptions

  • 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
@freezed
class 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);
}
@freezed
class CreateUserInput with _$CreateUserInput {
const factory CreateUserInput({
required String name,
required String email,
String? avatar,
}) = _CreateUserInput;
factory CreateUserInput.fromJson(Map<String, dynamic> json) =>
_$CreateUserInputFromJson(json);
}
@freezed
class UserFilter with _$UserFilter {
const factory UserFilter({
String? nameContains,
String? emailContains,
DateTime? createdAfter,
DateTime? createdBefore,
}) = _UserFilter;
}
enum UserSort { name, email, createdAt }
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');
}
class InvalidEmailException extends DomainException {
const InvalidEmailException(String email)
: super('Invalid email format: $email');
}

Source of Truth:

Application-wide services, business logic, advanced use cases, cross-cutting concerns

  • Owns ALL business logic, advanced use cases, orchestration
  • Provides reusable services for cross-cutting concerns
  • Coordinates multiple repositories and domain models
  • Error handling, logging, analytics, permissions, media
  • Integrates with caching service for all storage scenarios
  • Business logic: [Feature]Service (e.g., UserService)
  • Cross-cutting concerns: [Concern]OrchestratorService (e.g., DataMergeOrchestratorService)

Source of Truth:Presentation state and user interface
  • Manages presentation logic and user interface components
  • Uses notifiers (Riverpod) to manage UI state and react to application logic changes
  • Interacts with the application logic and domain layers to display and update data
  • Contains UI tests to ensure correct behavior

Every feature must handle these states:

  1. Loading: Show appropriate loading indicators
  2. Error: Display user-friendly error messages with retry option
  3. Empty: Show empty state with helpful guidance
  4. Success: Display the actual content

Houses core features that other features depend on:

  • Auth Feature: Authentication logic shared across features
  • Router: Centralized navigation logic and route definitions
  • Service Locators: Riverpod providers for dependency injection
  • Configuration: Optional flavor configuration and app-wide settings

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)

Reusable, business-logic-independent functionality:

  • I18n Package: Internationalization and localization utilities
  • Assets Package: Asset management and resource handling

  • UI → Services (business logic calls)
  • Services → Domain (models & interfaces)
  • Services → Datasource (via domain interfaces)
  • Domain → Datasource (defines repository contracts)
  • 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
  • ❌ UI directly calling Datasource
  • ❌ Datasource importing Services
  • ❌ Domain containing business logic
  • ❌ Local storage in Datasource layer
  • ❌ Feature-to-feature imports (use Core folder instead)

This architecture ensures maintainability, testability, and scalability while providing clear boundaries and responsibilities for each layer.