Why MOFA Architecture?
MOFA architecture is designed to be our shared foundationâmaking everyoneâs life easier, not adding friction.
The Problem We Solved
Section titled âThe Problem We SolvedâBefore MOFA Architecture
Section titled âBefore MOFA ArchitectureâOur team faced several challenges when building Flutter applications:
- Inconsistent Patterns: Each developer followed their own approach, leading to unpredictable code structure
- Repetitive Work: Constantly reinventing solutions for common problems like state management, data fetching, and caching
- Difficult Code Reviews: Similar features resulted in vastly different implementations, making PRs hard to review
- Onboarding Friction: New team members struggled to understand different patterns across projects
- Maintenance Burden: Long-term projects became increasingly difficult to maintain and extend
The Turning Point
Section titled âThe Turning PointâWe realized we needed a systematic approach when:
- Feature Development Slowed: Simple features took longer because developers had to make architectural decisions from scratch
- Bug Introduction Increased: Lack of testing standards led to regressions when adding new features
- Knowledge Silos Formed: Each project had its own âexpertâ who understood its unique patterns
- Cross-Project Movement Became Difficult: Developers couldnât easily switch between projects
Our Solution: MOFA Architecture
Section titled âOur Solution: MOFA ArchitectureâCore Benefits
Section titled âCore BenefitsâConsistent Development
Section titled â Consistent DevelopmentâProblem: Each project had different folder structures, naming conventions, and architectural patterns.
Solution: Standardized four-layer architecture with clear responsibilities and boundaries.
Result: Developers can move between projects confidently, knowing exactly where to find and place code.
Reduced Repetitive Work
Section titled â Reduced Repetitive WorkâProblem: Teams constantly reimplemented GraphQL clients, state management, caching, and error handling.
Solution: Reusable patterns and generic packages that solve common problems once.
Result: Focus shifts from infrastructure to business logic and user features.
Predictable Pull Requests
Section titled â Predictable Pull RequestsâProblem: Similar features resulted in completely different implementations, making code reviews inconsistent.
Solution: Standardized patterns for services, notifiers, repositories, and UI components.
Result: Code reviews focus on business logic rather than architectural decisions.
Disciplined Workflow
Section titled â Disciplined WorkflowâProblem: Without clear boundaries, business logic leaked into UI components and data access mixed with presentation.
Solution: Strict layer boundaries with defined responsibilities and data flow.
Result: Clean separation of concerns thatâs easy to test and maintain.
Faster Feedback Cycles
Section titled â Faster Feedback CyclesâProblem: Tightly coupled code made testing difficult and deployment risky.
Solution: Modular architecture with comprehensive testing strategy focused on critical areas.
Result: Rapid iteration with confidence in code quality.
Long-term Maintainability
Section titled â Long-term MaintainabilityâProblem: Projects became increasingly difficult to maintain as they grew in complexity.
Solution: Clear architectural boundaries, comprehensive documentation, and evolution-friendly patterns.
Result: Projects remain maintainable and extensible over years of development.
Key Architectural Decisions
Section titled âKey Architectural DecisionsâFour-Layer Architecture
Section titled âFour-Layer ArchitectureâWhy Four Layers?
We experimented with different approaches:
- No Architecture: Fast initially, but became unmaintainable
- Three Layers: Lacked clear separation between business logic and data access
- Five+ Layers: Over-engineered for Flutter applications, added unnecessary complexity
Four layers provide the sweet spot: Clear separation without over-engineering.
GraphQL Convertor Pattern
Section titled âGraphQL Convertor PatternâThe Problem: Ferryâs documentation examples create circular dependencies with Riverpod:
client -> cache handlers -> requests -> clientdependency cycle- Ferry assumes global definitions, but real-world needs dynamic state
- Cannot use Riverpod providers with Ferryâs trivial examples
Our Solution: The Convertor pattern provides composable, chainable data pipelines that:
- Eliminate circular dependencies through functional composition
- Enable context-aware cache operations via
CacheHandlerSpecs - Support dynamic state through typed Convertor input parameters
- Maintain type safety with covariant generics throughout the chain
- Dramatically reduce boilerplate compared to the original Strategy/DatasourceModule approach
The Convertor pattern evolved from an earlier Strategy pattern. See ADR-005 for the evolution rationale.
Caching Service Instead of Local Storage Datasource
Section titled âCaching Service Instead of Local Storage DatasourceâThe Problem: Traditional approaches mix data access concerns:
- Local storage logic scattered across repositories
- Difficult to implement different caching strategies
- Testing becomes complex with mixed responsibilities
Our Solution: Dedicated caching service with three types:
- Secure Cache: For sensitive data (tokens, credentials)
- Simple Cache: For basic data (preferences, settings)
- Complex Cache: For advanced scenarios (offline sync, TTL)
Core Folder vs Core Package
Section titled âCore Folder vs Core PackageâThe Problem: Generic âcoreâ packages become dumping grounds:
- Mixed concerns in a single package
- Tight coupling between unrelated utilities
- Difficult to test and maintain
Our Solution: App-specific core folder containing only:
- Features that other features depend on (Auth)
- Shared infrastructure (Router, Service Locators)
- Configuration (Flavors, App Settings)
Real-World Impact
Section titled âReal-World ImpactâQualitative Improvements
Section titled âQualitative Improvementsâ- Increased Confidence: Developers feel confident making changes across projects
- Better Collaboration: Shared vocabulary and patterns improve team communication
- Reduced Stress: Clear guidelines eliminate decision paralysis
- Higher Quality: Consistent patterns lead to more robust applications
Evolution and Adaptation
Section titled âEvolution and AdaptationâLiving Architecture
Section titled âLiving ArchitectureâMOFA architecture isnât staticâit evolves based on:
- Team Experience: Lessons learned from real projects
- Technology Changes: Updates to Flutter, Dart, and ecosystem packages
- Project Requirements: New challenges that require architectural adaptations
- Community Feedback: Insights from other teams and open source contributions
Continuous Improvement
Section titled âContinuous ImprovementâWe regularly review and update our architecture through:
- Weekly Architecture Reviews: Discuss patterns and improvements
- Monthly Deep Dives: Analyze specific architectural decisions
- Quarterly Planning: Major architectural changes and technology evaluation
- Project Retrospectives: Learn from successes and challenges
When MOFA Architecture Fits
Section titled âWhen MOFA Architecture FitsâIdeal Use Cases
Section titled âIdeal Use CasesâMOFA architecture works best for:
- Team Development: Multiple developers working on the same codebase
- Complex Applications: Apps with significant business logic and data management
- Long-term Projects: Applications that will be maintained and extended over time
- Multiple Projects: Teams building several related applications
When to Consider Alternatives
Section titled âWhen to Consider AlternativesâMOFA architecture might be overkill for:
- Prototypes: Quick experiments or proof-of-concepts
- Short-term Projects: Applications with limited lifespan
Getting Started
Section titled âGetting StartedâIf MOFA architecture sounds right for your team:
- Start with Tutorials: Learn the fundamentals through hands-on examples
- Read the Explanations: Understand the reasoning behind each decision
- Use How-to Guides: Implement specific patterns in your projects
- Reference Documentation: Look up details as you build
Remember: Architecture should serve your team, not the other way around. Adapt these patterns to fit your specific needs and context.
This architecture evolves with our teamâs experience. Found something that doesnât work? Have a better approach? Letâs discuss it! Every improvement makes our shared foundation stronger.