Skip to content

Areas Needing Team Input

Team Collaboration Needed

These are real-world challenges we’ve encountered in our MOFA architecture implementation. Each area needs team discussion to establish best practices and patterns.

Our hotel bookings cache handler has grown to over 1800 lines and manages:

  • Count query caches (multiple status filters)
  • Single item caches (with optimistic updates)
  • List query caches (current, target status, all status)
  • Cross-status cache invalidation
  • Optimistic update rollback
class HotelBookingsUpsertCacheHandlerStrategy {
@override
UpdateCacheHandler build(RequestContext requestContext) {
return (proxy, response) {
// 1800+ lines of cache management logic
_updateCountQueryCaches(proxy, requestContext, updatedItem);
_updateSingleItemCache(proxy, requestContext, updatedItem);
_updateListQueryCaches(proxy, requestContext, updatedItem, isCreateOperation);
_updateTargetStatusCaches(proxy, requestContext, updatedItem);
_updateAllStatusListCaches(proxy, requestContext, updatedItem);
// ... many more cache update methods
};
}
}
Discussion Points
  1. Should we break complex cache handlers into smaller, focused handlers?
  2. Should we create a CacheHandlerOrchestrator pattern?
  3. How do we handle cache handlers that need to update 10+ different cache variations?
  4. Should we implement automatic cache invalidation strategies?
  5. What’s the maximum acceptable size for a single cache handler?
class HotelBookingsUpsertCacheHandlerStrategy {
final List<CacheHandler> handlers = [
CountCacheHandler(),
ListCacheHandler(),
SingleItemCacheHandler(),
StatusTransitionCacheHandler(),
];
}
class CacheHandlerOrchestrator {
void orchestrate(CacheUpdateContext context) {
for (final handler in getApplicableHandlers(context)) {
handler.handle(context);
}
}
}

We frequently forget to add new fields to conversion extensions and only discover missing fields when requests fail at runtime.

  1. Runtime Discovery: Missing fields only found when executing requests
  2. Scattered Extensions: No clear organization pattern
  3. Manual Maintenance: Easy to forget updating all related extensions
  4. Testing Gaps: Incomplete coverage of conversion scenarios
// Domain model gets new field
class HotelBookingsUpsertVars {
final String? specialDietaryRequirements; // NEW FIELD
// ... other fields
}
// But conversion extension is not updated
extension HotelBookingsUpsertVarsExtension on HotelBookingsUpsertVars {
GMutateHotelBookingsSaveVars get toMutationVars {
return GMutateHotelBookingsSaveVars(
(b) => b
..hotelBookingsArgs.replace(
GHotelBookingsArgs(
(args) => args
..id = id
..guestName = guestName
// Missing: specialDietaryRequirements
),
),
);
}
}
Discussion Points
  1. Can we generate conversion extensions automatically from domain models?
  2. How can we catch missing fields at compile time instead of runtime?
  3. Should we group extensions by entity, operation type, or GraphQL schema?
  4. What testing strategy ensures all fields are properly converted?
  5. Should conversion extensions be self-documenting with field mappings?
// Generate extensions from domain models
@GenerateConversionExtensions()
class HotelBookingsUpsertVars {
// Automatically generates toMutationVars extension
}
// Annotation-based validation
@ValidateConversion(target: GMutateHotelBookingsSaveVars)
extension HotelBookingsUpsertVarsExtension on HotelBookingsUpsertVars {
// Compiler checks all fields are mapped
}
void main() {
test('all domain fields are converted', () {
final domainFields = reflectClass(HotelBookingsUpsertVars).declarations.keys;
final conversionFields = getConvertedFields(HotelBookingsUpsertVarsExtension);
expect(conversionFields, containsAll(domainFields));
});
}

Context parameters are powerful but poorly documented. Team members don’t understand when and how to use them effectively.

class CountRequestParams {
final String conferenceId; // GraphQL variable
final String? statusId; // GraphQL variable
// Context parameters - not part of GraphQL but needed for logic
final String? userRole; // For authorization logic
final String? subscriptionId; // For real-time updates
final Map<String, dynamic>? metadata; // For additional context
}
Discussion Points
  1. What should be a context parameter vs a GraphQL variable?
  2. How do we document the purpose of each context parameter?
  3. Should context parameters have standardized naming conventions?
  4. How do context parameters flow through datasource → repository → service layers?
  5. What’s the best way to handle optional context parameters?
  • When to use context parameters vs GraphQL variables
  • How context parameters affect caching strategies
  • Best practices for parameter naming and organization
  • Examples of common context parameter patterns

🔄 Request Strategy Inheritance Patterns

Section titled “🔄 Request Strategy Inheritance Patterns”

We have successful patterns like the hotel booking count request strategies, but no clear guidelines for when and how to use inheritance.

// Base class with shared logic
abstract class HotelBookingsCountRequestStrategy {
String get statusKey; // Abstract property
GQueryHotelBookingsCountReq createRequest(CountRequestParams params) {
// Shared implementation with status-specific behavior
}
}
// Concrete implementations
class HotelBookingsRequestedCountRequestStrategy
extends HotelBookingsCountRequestStrategy {
@override
String get statusKey => 'requested';
}
Discussion Points
  1. When should we use inheritance vs composition for request strategies?
  2. What’s the maximum depth of inheritance we should allow?
  3. How do we balance code reuse with strategy simplicity?
  4. Should we create base strategy classes for common patterns?
  5. How do we test inherited strategies effectively?

The datasource module pattern works well but needs refinement as we scale to more complex features.

class HotelBookingsDatasourceModule {
HotelBookingsDatasource create(GqlClient client, RequestContext requestContext) {
// Register 15+ strategies
// Register 8+ cache handlers
// Configure complex relationships
}
}
Discussion Points
  1. Should modules be feature-based or operation-based?
  2. How do we handle cross-feature strategy dependencies?
  3. Should we support conditional strategy registration?
  4. What’s the best way to organize strategies within modules?
  5. How do we test module registration without integration tests?

  1. Cache Handler Complexity - Schedule architecture review session
  2. Conversion Extensions - Evaluate code generation options
  3. Context Parameters - Create documentation standards
  4. Strategy Inheritance - Establish guidelines and patterns
  1. Code Generation Tools - Investigate build_runner solutions for extensions
  2. Cache Patterns - Research industry patterns for complex cache management
  3. Testing Strategies - Develop comprehensive testing approaches
  4. Documentation Tools - Evaluate tools for self-documenting code
  • Week 1: Team discussion on cache handler patterns
  • Week 2: Conversion extension strategy decision
  • Week 3: Context parameter documentation standards
  • Week 4: Implementation plan for chosen solutions
Next Steps

These discussions will shape the evolution of our MOFA architecture. Each decision should be documented as an ADR (Architecture Decision Record) once we reach consensus.

  • Bring real examples from your current work
  • Share pain points you’ve encountered
  • Propose solutions you’ve tried
  • Add examples of successful patterns
  • Document anti-patterns you’ve discovered
  • Contribute test cases for complex scenarios
  • Share quick questions and insights
  • Post code snippets for review
  • Discuss implementation challenges

The goal is to evolve our architecture based on real-world experience while maintaining the principles that make MOFA effective for our team.