EventModule for Microfrontends: Technical Documentation
A comprehensive guide for implementing microfrontend architecture using EventModule for inter-module communication.
Table of Contents
- Overview
- Microfrontend Architecture
- EventModule Benefits
- Implementation Patterns
- Communication Strategies
- Real-World Examples
- Performance Optimization
- Deployment Strategies
- Monitoring & Debugging
- Best Practices
- Migration Guide
- Case Studies
Overview
What are Microfrontends?
Microfrontends extend the concept of microservices to frontend development, allowing teams to:
- Develop independently using different technologies
- Deploy autonomously without affecting other parts
- Scale teams horizontally with clear ownership boundaries
- Maintain resilience through isolation and fault tolerance
EventModule's Role
EventModule provides the communication backbone for microfrontends, enabling:
┌─────────────────────────────────────────────────────────────┐
│ EventModule Bus │
├─────────────────────────────────────────────────────────────┤
│ 🛍️ E-commerce 💰 Payment 👤 Auth 📊 Analytics │
│ Module Module Module Module │
│ (React) (Vue) (Angular) (Flutter) │
└─────────────────────────────────────────────────────────────┘
Key Capabilities:
- ✅ Technology Agnostic - Works with any Flutter-based microfrontend
- ✅ Type-Safe Communication - Compile-time verification of event contracts
- ✅ Loose Coupling - Modules communicate without direct dependencies
- ✅ Event Sourcing - Audit trail of all inter-module communications
- ✅ Real-time Synchronization - Immediate state updates across modules
Microfrontend Architecture
Traditional Monolith vs Microfrontends
Monolithic Frontend
// ❌ Single large application
class MonolithApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
ECommerceSection(), // 50,000 lines
PaymentSection(), // 30,000 lines
UserProfileSection(), // 25,000 lines
AnalyticsSection(), // 15,000 lines
// Total: 120,000 lines in single app
],
),
),
);
}
}
Problems:
- 🐌 Slow builds (5-10 minutes)
- 🚫 Technology lock-in (single framework)
- 👥 Team conflicts (merge hell)
- 🐛 Ripple effects (one bug affects everything)
- 📦 Large bundles (slow loading)
Microfrontend Architecture
// ✅ Modular, independent applications
class ShellApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: GoRouterModular.routerConfig,
);
}
}
// Independent modules
class ECommerceModule extends EventModule { } // Team A - 10,000 lines
class PaymentModule extends EventModule { } // Team B - 8,000 lines
class UserModule extends EventModule { } // Team C - 6,000 lines
class AnalyticsModule extends EventModule { } // Team D - 4,000 lines
Benefits:
- ⚡ Fast builds (30 seconds per module)
- 🎯 Technology freedom (best tool for the job)
- 👥 Team autonomy (independent development)
- 🛡️ Fault isolation (resilient architecture)
- 📦 Lazy loading (load on demand)
EventModule Communication Layer
// Communication backbone
class MicrofrontendBus {
// Cross-module events
static void broadcastUserLogin(User user) {
ModularEvent.fire(UserLoginEvent(
userId: user.id,
email: user.email,
timestamp: DateTime.now(),
));
}
static void notifyPurchaseCompleted(Purchase purchase) {
ModularEvent.fire(PurchaseCompletedEvent(
orderId: purchase.id,
userId: purchase.userId,
amount: purchase.total,
items: purchase.items,
));
}
}
EventModule Benefits
1. Decoupled Architecture
Without EventModule (Tight Coupling)
// ❌ Direct dependencies between modules
class ECommerceModule {
final PaymentModule paymentModule;
final UserModule userModule;
final AnalyticsModule analyticsModule;
ECommerceModule({
required this.paymentModule, // Direct dependency
required this.userModule, // Direct dependency
required this.analyticsModule, // Direct dependency
});
void processOrder(Order order) {
// Tightly coupled calls
userModule.validateUser(order.userId);
paymentModule.processPayment(order.payment);
analyticsModule.trackPurchase(order);
}
}
Problems:
- 🔗 Tight coupling - modules know about each other
- 🐛 Fragile - changes break other modules
- 🧪 Hard to test - requires all dependencies
- 📦 Bundle bloat - everything loads together
With EventModule (Loose Coupling)
// ✅ Event-driven, loosely coupled
class ECommerceModule extends EventModule {
@override
void listen() {
on<UserValidatedEvent>((UserValidatedEvent event, BuildContext? context) {
_proceedWithOrder(event.orderId);
});
on<PaymentProcessedEvent>((PaymentProcessedEvent event, BuildContext? context) {
_fulfillOrder(event.orderId);
});
}
void processOrder(Order order) {
// Fire events - no direct dependencies
ModularEvent.fire(ValidateUserEvent(userId: order.userId, orderId: order.id));
ModularEvent.fire(ProcessPaymentEvent(payment: order.payment, orderId: order.id));
ModularEvent.fire(TrackPurchaseEvent(order: order));
}
}
Benefits:
- 🔓 Loose coupling - modules don't know each other
- 🛡️ Resilient - modules can fail independently
- 🧪 Easy testing - mock events, not dependencies
- 📦 Lazy loading - load modules on demand
2. Independent Development
// Team A: E-commerce (develops independently)
class ECommerceModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/products', child: (context, state) => ProductsPage()),
ChildRoute('/cart', child: (context, state) => CartPage()),
];
@override
void listen() {
// Only cares about events it needs
on<UserLoginEvent>((UserLoginEvent event, BuildContext? context) {
_loadUserCart(event.userId);
});
}
}
// Team B: Payment (develops independently)
class PaymentModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/payment', child: (context, state) => PaymentPage()),
ChildRoute('/billing', child: (context, state) => BillingPage()),
];
@override
void listen() {
// Different events, different concerns
on<InitiatePaymentEvent>((InitiatePaymentEvent event, BuildContext? context) {
_processPayment(event.amount, event.method);
});
}
}
3. Technology Diversity
// Different modules can use different approaches
class ModernECommerceModule extends EventModule {
// Uses latest State Management (Riverpod)
@override
Widget buildPage(BuildContext context, String route) {
return ProviderScope(
child: ModernProductsPage(),
);
}
}
class LegacyPaymentModule extends EventModule {
// Uses older State Management (Provider)
@override
Widget buildPage(BuildContext context, String route) {
return ChangeNotifierProvider(
create: (_) => PaymentProvider(),
child: LegacyPaymentPage(),
);
}
}
// Events work seamlessly between different architectures
4. Fault Tolerance
class ResilientModule extends EventModule {
@override
void listen() {
// Graceful degradation
on<CriticalServiceDownEvent>((CriticalServiceDownEvent event, BuildContext? context) {
_enableFallbackMode();
});
on<PaymentFailedEvent>((PaymentFailedEvent event, BuildContext? context) {
// Other modules continue working
_showErrorToUser(event.error);
_suggestAlternativePayment();
});
}
void _enableFallbackMode() {
// Module continues with reduced functionality
// instead of complete failure
}
}
Implementation Patterns
1. Shell Application Pattern
// Shell App - Orchestrates microfrontends
class ShellApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Microfrontend Shell',
routerConfig: GoRouterModular.routerConfig,
);
}
}
// Shell Module - Coordinates everything
class ShellModule extends Module {
@override
List<Module> get imports => [
// Microfrontend modules
ECommerceModule(),
PaymentModule(),
UserManagementModule(),
AnalyticsModule(),
NotificationModule(),
];
@override
List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => DashboardPage()),
ChildRoute('/shell', child: (context, state) => ShellPage()),
];
}
2. Event Contract Pattern
// Shared event contracts between modules
library microfrontend_events;
// Authentication Events
class UserLoginEvent {
final String userId;
final String email;
final String role;
final DateTime loginTime;
UserLoginEvent({
required this.userId,
required this.email,
required this.role,
required this.loginTime,
});
}
class UserLogoutEvent {
final String userId;
final DateTime logoutTime;
final String reason;
UserLogoutEvent({
required this.userId,
required this.logoutTime,
required this.reason,
});
}
// E-commerce Events
class ProductAddedToCartEvent {
final String userId;
final String productId;
final int quantity;
final double price;
ProductAddedToCartEvent({
required this.userId,
required this.productId,
required this.quantity,
required this.price,
});
}
class OrderCompletedEvent {
final String orderId;
final String userId;
final double totalAmount;
final List<OrderItem> items;
final DateTime completedAt;
OrderCompletedEvent({
required this.orderId,
required this.userId,
required this.totalAmount,
required this.items,
required this.completedAt,
});
}
3. Module Composition Pattern
// Composable modules for different business units
class RetailPlatformModule extends Module {
@override
List<Module> get imports => [
// Customer-facing modules
ProductCatalogModule(),
ShoppingCartModule(),
CheckoutModule(),
// Support modules
CustomerServiceModule(),
ReviewsModule(),
RecommendationsModule(),
];
}
class AdminPlatformModule extends Module {
@override
List<Module> get imports => [
// Admin modules
InventoryModule(),
OrderManagementModule(),
UserManagementModule(),
AnalyticsModule(),
// Reporting modules
SalesReportModule(),
CustomerReportModule(),
];
}
// Main app composes platforms
class MainAppModule extends Module {
@override
List<Module> get imports => [
RetailPlatformModule(),
AdminPlatformModule(),
SharedServicesModule(),
];
}
Communication Strategies
1. Synchronous Communication
// Request-Response pattern using events
class DataRequestModule extends EventModule {
final Map<String, Completer<dynamic>> _pendingRequests = {};
Future<UserProfile> getUserProfile(String userId) async {
final requestId = _generateRequestId();
final completer = Completer<UserProfile>();
_pendingRequests[requestId] = completer;
// Fire request event
ModularEvent.fire(UserProfileRequestEvent(
userId: userId,
requestId: requestId,
));
return completer.future;
}
@override
void listen() {
on<UserProfileResponseEvent>((UserProfileResponseEvent event, BuildContext? context) {
final completer = _pendingRequests.remove(event.requestId);
completer?.complete(event.profile);
});
}
}
2. Asynchronous Communication
// Fire-and-forget pattern
class AnalyticsModule extends EventModule {
@override
void listen() {
// Listen to various events for analytics
on<UserLoginEvent>((UserLoginEvent event, BuildContext? context) {
_trackUserLogin(event.userId, event.loginTime);
});
on<ProductViewedEvent>((ProductViewedEvent event, BuildContext? context) {
_trackProductView(event.productId, event.userId);
});
on<PurchaseCompletedEvent>((PurchaseCompletedEvent event, BuildContext? context) {
_trackPurchase(event.orderId, event.amount);
});
}
void _trackUserLogin(String userId, DateTime loginTime) {
// Send to analytics service
analyticsService.track('user_login', {
'user_id': userId,
'timestamp': loginTime.toIso8601String(),
});
}
}
3. State Synchronization
// Shared state across modules
class SharedStateModule extends EventModule {
static UserState? _currentUser;
static CartState _cart = CartState.empty();
@override
void listen() {
// Update shared state based on events
on<UserLoginEvent>((UserLoginEvent event, BuildContext? context) {
_currentUser = UserState(
id: event.userId,
email: event.email,
role: event.role,
);
// Broadcast state change
ModularEvent.fire(UserStateUpdatedEvent(user: _currentUser!));
});
on<CartUpdatedEvent>((CartUpdatedEvent event, BuildContext? context) {
_cart = event.cart;
// Broadcast to interested modules
ModularEvent.fire(CartStateUpdatedEvent(cart: _cart));
});
}
// State accessors
static UserState? get currentUser => _currentUser;
static CartState get cart => _cart;
}
Real-World Examples
1. E-commerce Platform
// Product Catalog Module
class ProductCatalogModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/products', child: (context, state) => ProductListPage()),
ChildRoute('/product/:id', child: (context, state) => ProductDetailPage()),
];
@override
void listen() {
on<SearchProductsEvent>((SearchProductsEvent event, BuildContext? context) {
_performSearch(event.query, event.filters);
});
on<ProductRecommendationRequestEvent>((ProductRecommendationRequestEvent event, BuildContext? context) {
_generateRecommendations(event.userId, event.context);
});
}
void _performSearch(String query, Map<String, dynamic> filters) {
// Search logic
final results = productService.search(query, filters);
// Broadcast results
ModularEvent.fire(ProductSearchResultsEvent(
query: query,
results: results,
timestamp: DateTime.now(),
));
}
}
// Shopping Cart Module
class ShoppingCartModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/cart', child: (context, state) => ShoppingCartPage()),
];
@override
void listen() {
on<AddToCartEvent>((AddToCartEvent event, BuildContext? context) {
_addToCart(event.productId, event.quantity);
});
on<RemoveFromCartEvent>((RemoveFromCartEvent event, BuildContext? context) {
_removeFromCart(event.cartItemId);
});
on<UserLoginEvent>((UserLoginEvent event, BuildContext? context) {
_loadUserCart(event.userId);
});
}
void _addToCart(String productId, int quantity) {
cartService.addItem(productId, quantity);
// Notify other modules
ModularEvent.fire(CartUpdatedEvent(
userId: cartService.userId,
itemCount: cartService.totalItems,
total: cartService.total,
));
// Analytics
ModularEvent.fire(ProductAddedToCartEvent(
userId: cartService.userId,
productId: productId,
quantity: quantity,
price: cartService.getItemPrice(productId),
));
}
}
// Payment Module
class PaymentModule extends EventModule {
@override
List<ModularRoute> get routes => [
ChildRoute('/checkout', child: (context, state) => CheckoutPage()),
ChildRoute('/payment', child: (context, state) => PaymentPage()),
];
@override
void listen() {
on<InitiateCheckoutEvent>((InitiateCheckoutEvent event, BuildContext? context) {
_startCheckoutProcess(event.cartItems, event.userId);
});
on<ProcessPaymentEvent>((ProcessPaymentEvent event, BuildContext? context) {
_processPayment(event.paymentMethod, event.amount);
});
}
Future<void> _processPayment(PaymentMethod method, double amount) async {
try {
final result = await paymentService.process(method, amount);
if (result.success) {
ModularEvent.fire(PaymentSuccessEvent(
transactionId: result.transactionId,
amount: amount,
method: method,
));
} else {
ModularEvent.fire(PaymentFailedEvent(
error: result.error,
amount: amount,
method: method,
));
}
} catch (error) {
ModularEvent.fire(PaymentErrorEvent(
error: error.toString(),
amount: amount,
));
}
}
}
2. Banking Application
// Account Management Module
class AccountModule extends EventModule {
@override
void listen() {
on<TransferMoneyEvent>((TransferMoneyEvent event, BuildContext? context) {
_processTransfer(event.fromAccount, event.toAccount, event.amount);
});
on<DepositEvent>((DepositEvent event, BuildContext? context) {
_processDeposit(event.accountId, event.amount);
});
}
Future<void> _processTransfer(String fromAccount, String toAccount, double amount) async {
// Business logic
final result = await accountService.transfer(fromAccount, toAccount, amount);
if (result.success) {
// Notify interested modules
ModularEvent.fire(TransferCompletedEvent(
transactionId: result.transactionId,
fromAccount: fromAccount,
toAccount: toAccount,
amount: amount,
timestamp: DateTime.now(),
));
}
}
}
// Fraud Detection Module
class FraudDetectionModule extends EventModule {
@override
void listen() {
// Monitor all financial transactions
on<TransferCompletedEvent>((TransferCompletedEvent event, BuildContext? context) {
_analyzeFraudRisk(event);
});
on<LoginAttemptEvent>((LoginAttemptEvent event, BuildContext? context) {
_checkSuspiciousLogin(event);
});
}
void _analyzeFraudRisk(TransferCompletedEvent event) {
final riskScore = fraudService.calculateRisk(event);
if (riskScore > 0.8) {
ModularEvent.fire(SuspiciousActivityEvent(
userId: event.fromAccount,
transactionId: event.transactionId,
riskScore: riskScore,
reason: 'Unusual transfer pattern',
));
}
}
}
// Notification Module
class NotificationModule extends EventModule {
@override
void listen() {
on<TransferCompletedEvent>((TransferCompletedEvent event, BuildContext? context) {
_sendTransferNotification(event);
});
on<SuspiciousActivityEvent>((SuspiciousActivityEvent event, BuildContext? context) {
_sendSecurityAlert(event);
});
on<PaymentFailedEvent>((PaymentFailedEvent event, BuildContext? context) {
_sendFailureNotification(event);
});
}
void _sendTransferNotification(TransferCompletedEvent event) {
notificationService.send(
userId: event.fromAccount,
title: 'Transfer Completed',
message: 'Your transfer of \$${event.amount} was successful',
type: NotificationType.info,
);
}
}
Performance Optimization
1. Lazy Loading Modules
// Load modules on demand
class LazyModuleLoader {
static final Map<String, Module> _loadedModules = {};
static Future<Module> loadModule(String moduleName) async {
if (_loadedModules.containsKey(moduleName)) {
return _loadedModules[moduleName]!;
}
Module module;
switch (moduleName) {
case 'ecommerce':
module = await _loadECommerceModule();
break;
case 'payment':
module = await _loadPaymentModule();
break;
case 'analytics':
module = await _loadAnalyticsModule();
break;
default:
throw Exception('Unknown module: $moduleName');
}
_loadedModules[moduleName] = module;
return module;
}
static Future<ECommerceModule> _loadECommerceModule() async {
// Simulate loading time
await Future.delayed(Duration(milliseconds: 500));
return ECommerceModule();
}
}
2. Event Batching
// Batch events for better performance
class EventBatcher {
static final Map<Type, List<dynamic>> _batchedEvents = {};
static Timer? _batchTimer;
static void batchEvent<T>(T event) {
_batchedEvents[T] ??= [];
_batchedEvents[T]!.add(event);
// Process batch after delay
_batchTimer?.cancel();
_batchTimer = Timer(Duration(milliseconds: 100), _processBatch);
}
static void _processBatch() {
for (final entry in _batchedEvents.entries) {
ModularEvent.fire(BatchedEventsEvent(
eventType: entry.key,
events: entry.value,
));
}
_batchedEvents.clear();
}
}
3. Event Filtering
// Efficient event filtering
class SmartEventModule extends EventModule {
@override
void listen() {
// Only listen to events we care about
on<UserEvent>((UserEvent event, BuildContext? context) {
// Filter by user role
if (event.userRole == UserRole.admin) {
_handleAdminEvent(event);
}
});
// Debounced search
on<SearchEvent>((SearchEvent event, BuildContext? context) {
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 300), () {
_performSearch(event.query);
});
});
}
Timer? _debounceTimer;
}
Deployment Strategies
1. Independent Deployment
# CI/CD Pipeline for each module
name: Deploy ECommerce Module
on:
push:
paths:
- 'modules/ecommerce/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Build Module
run: flutter build web
- name: Deploy to CDN
run: aws s3 sync build/web s3://cdn/ecommerce-module
- name: Update Module Registry
run: curl -X POST api/modules/ecommerce/deploy
2. Version Management
// Module versioning
class ModuleVersion {
final String name;
final String version;
final List<String> compatibleVersions;
ModuleVersion({
required this.name,
required this.version,
required this.compatibleVersions,
});
}
class ModuleRegistry {
static final Map<String, ModuleVersion> _modules = {
'ecommerce': ModuleVersion(
name: 'ecommerce',
version: '2.1.0',
compatibleVersions: ['2.0.0', '2.1.0'],
),
'payment': ModuleVersion(
name: 'payment',
version: '1.5.2',
compatibleVersions: ['1.5.0', '1.5.1', '1.5.2'],
),
};
static bool isCompatible(String module1, String module2) {
final mod1 = _modules[module1];
final mod2 = _modules[module2];
return mod1?.compatibleVersions.contains(mod2?.version) ?? false;
}
}
3. Blue-Green Deployment
// Route traffic between module versions
class ModuleRouter {
static String _currentEnvironment = 'blue';
static Module getModule(String moduleName) {
switch (_currentEnvironment) {
case 'blue':
return _getBlueModule(moduleName);
case 'green':
return _getGreenModule(moduleName);
default:
throw Exception('Unknown environment: $_currentEnvironment');
}
}
static void switchEnvironment() {
_currentEnvironment = _currentEnvironment == 'blue' ? 'green' : 'blue';
// Notify all modules about environment switch
ModularEvent.fire(EnvironmentSwitchedEvent(
newEnvironment: _currentEnvironment,
timestamp: DateTime.now(),
));
}
}
Monitoring & Debugging
1. Event Tracing
// Event tracing for debugging
class EventTracer {
static final List<EventTrace> _traces = [];
static void traceEvent<T>(T event, String source) {
_traces.add(EventTrace(
event: event,
eventType: T,
source: source,
timestamp: DateTime.now(),
stackTrace: StackTrace.current,
));
// Keep only recent traces
if (_traces.length > 1000) {
_traces.removeAt(0);
}
}
static List<EventTrace> getTraces({Type? eventType, String? source}) {
return _traces.where((trace) {
if (eventType != null && trace.eventType != eventType) return false;
if (source != null && trace.source != source) return false;
return true;
}).toList();
}
}
class EventTrace {
final dynamic event;
final Type eventType;
final String source;
final DateTime timestamp;
final StackTrace stackTrace;
EventTrace({
required this.event,
required this.eventType,
required this.source,
required this.timestamp,
required this.stackTrace,
});
}
2. Performance Monitoring
// Monitor module performance
class ModulePerformanceMonitor extends EventModule {
static final Map<String, List<Duration>> _moduleResponseTimes = {};
@override
void listen() {
on<ModuleRequestEvent>((ModuleRequestEvent event, BuildContext? context) {
_startTimer(event.moduleId, event.requestId);
});
on<ModuleResponseEvent>((ModuleResponseEvent event, BuildContext? context) {
_recordResponseTime(event.moduleId, event.requestId);
});
}
static void _startTimer(String moduleId, String requestId) {
// Implementation details...
}
static void _recordResponseTime(String moduleId, String requestId) {
// Calculate and store response time
final responseTime = _calculateResponseTime(moduleId, requestId);
_moduleResponseTimes[moduleId] ??= [];
_moduleResponseTimes[moduleId]!.add(responseTime);
// Alert if response time is too high
if (responseTime > Duration(milliseconds: 1000)) {
ModularEvent.fire(SlowModuleResponseEvent(
moduleId: moduleId,
responseTime: responseTime,
));
}
}
}
3. Health Checks
// Module health monitoring
class ModuleHealthChecker extends EventModule {
@override
void listen() {
// Regular health checks
Timer.periodic(Duration(minutes: 1), (_) => _performHealthChecks());
on<ModuleErrorEvent>((ModuleErrorEvent event, BuildContext? context) {
_handleModuleError(event);
});
}
void _performHealthChecks() {
final modules = ['ecommerce', 'payment', 'user', 'analytics'];
for (final moduleId in modules) {
ModularEvent.fire(HealthCheckRequestEvent(
moduleId: moduleId,
timestamp: DateTime.now(),
));
}
}
void _handleModuleError(ModuleErrorEvent event) {
// Log error
logger.error('Module ${event.moduleId} error: ${event.error}');
// Notify monitoring system
ModularEvent.fire(ModuleUnhealthyEvent(
moduleId: event.moduleId,
error: event.error,
timestamp: DateTime.now(),
));
}
}
Best Practices
1. Event Design
// ✅ GOOD: Well-designed events
class UserRegistrationCompletedEvent {
final String userId;
final String email;
final UserProfile profile;
final DateTime registrationDate;
final String source; // web, mobile, api
UserRegistrationCompletedEvent({
required this.userId,
required this.email,
required this.profile,
required this.registrationDate,
required this.source,
});
// Add useful methods
bool get isEmailVerified => profile.emailVerified;
bool get isFromMobile => source == 'mobile';
}
// ❌ BAD: Poorly designed events
class GenericUserEvent {
Map<String, dynamic>? data; // Untyped
String? action; // Unclear
// Missing timestamp, unclear purpose
}
2. Module Boundaries
// ✅ GOOD: Clear module boundaries
class OrderModule extends EventModule {
// Responsible for: order lifecycle, order validation, order status
@override
void listen() {
on<CreateOrderEvent>((CreateOrderEvent event, BuildContext? context) {
_createOrder(event);
});
on<CancelOrderEvent>((CancelOrderEvent event, BuildContext? context) {
_cancelOrder(event);
});
}
}
class PaymentModule extends EventModule {
// Responsible for: payment processing, payment methods, refunds
@override
void listen() {
on<ProcessPaymentEvent>((ProcessPaymentEvent event, BuildContext? context) {
_processPayment(event);
});
}
}
// ❌ BAD: Unclear boundaries
class OrderPaymentModule extends EventModule {
// Too many responsibilities - should be split
// Orders + Payments + Inventory + Shipping
}
3. Error Handling
// ✅ GOOD: Comprehensive error handling
class RobustModule extends EventModule {
@override
void listen() {
on<ProcessDataEvent>((ProcessDataEvent event, BuildContext? context) async {
try {
await _processData(event.data);
ModularEvent.fire(DataProcessedEvent(
dataId: event.dataId,
result: 'success',
));
} on ValidationException catch (e) {
ModularEvent.fire(DataValidationFailedEvent(
dataId: event.dataId,
validationErrors: e.errors,
));
} on NetworkException catch (e) {
ModularEvent.fire(NetworkErrorEvent(
operation: 'process_data',
error: e.message,
retryable: true,
));
} catch (e, stackTrace) {
// Unexpected error
ModularEvent.fire(UnexpectedErrorEvent(
operation: 'process_data',
error: e.toString(),
stackTrace: stackTrace.toString(),
));
}
});
}
}
4. Testing Strategies
// Microfrontend testing
class ECommerceModuleTest {
late ECommerceModule module;
late List<dynamic> firedEvents;
@override
void setUp() {
module = ECommerceModule();
firedEvents = [];
// Mock event firing
ModularEvent.fire = (event) => firedEvents.add(event);
}
@override
void testAddToCart() {
// Arrange
final event = AddToCartEvent(
userId: 'user123',
productId: 'product456',
quantity: 2,
);
// Act
module.handleAddToCart(event);
// Assert
expect(firedEvents, hasLength(2));
expect(firedEvents[0], isA<CartUpdatedEvent>());
expect(firedEvents[1], isA<ProductAddedToCartEvent>());
}
}
Migration Guide
From Monolith to Microfrontends
Phase 1: Identify Boundaries
// Current monolith
class MonolithApp {
// User management: 15,000 lines
// Product catalog: 25,000 lines
// Shopping cart: 10,000 lines
// Payment processing: 20,000 lines
// Order management: 18,000 lines
// Analytics: 12,000 lines
}
// Target microfrontends
// UserModule: 15,000 lines
// ProductModule: 25,000 lines
// CartModule: 10,000 lines
// PaymentModule: 20,000 lines
// OrderModule: 18,000 lines
// AnalyticsModule: 12,000 lines
Phase 2: Extract Modules
// Step 1: Create EventModule structure
class UserModule extends EventModule {
// Move user-related code here
}
// Step 2: Replace direct calls with events
// Before:
userService.login(email, password);
// After:
ModularEvent.fire(UserLoginRequestEvent(
email: email,
password: password,
));
Phase 3: Gradual Migration
// Hybrid approach during migration
class HybridModule extends EventModule {
final LegacyUserService legacyService; // Keep during transition
final UserService newService; // New event-driven service
@override
void listen() {
on<UserLoginEvent>((UserLoginEvent event, BuildContext? context) {
// Use new service
newService.login(event.email, event.password);
});
}
// Legacy method - remove after migration
@deprecated
void legacyLogin(String email, String password) {
legacyService.login(email, password);
}
}
Case Studies
Case Study 1: E-commerce Platform
Company: TechMart (fictional) Challenge: Monolithic e-commerce platform with 8 teams
Before Microfrontends
- Build Time: 15 minutes
- Deployment: Weekly (risky)
- Team Dependencies: High
- Bug Impact: Entire platform down
Implementation
// Module structure
class TechMartShell extends Module {
@override
List<Module> get imports => [
ProductCatalogModule(), // Team A
UserAccountModule(), // Team B
ShoppingCartModule(), // Team C
CheckoutModule(), // Team D
OrderTrackingModule(), // Team E
CustomerServiceModule(), // Team F
AdminPanelModule(), // Team G
AnalyticsModule(), // Team H
];
}
// Event flow example
class PurchaseFlow {
static void simulateFlow() {
// User adds product to cart
ModularEvent.fire(AddToCartEvent(
userId: 'user123',
productId: 'laptop001',
quantity: 1,
));
// Cart module updates
// Recommendation module suggests accessories
// Analytics module tracks behavior
// User proceeds to checkout
ModularEvent.fire(InitiateCheckoutEvent(
userId: 'user123',
cartItems: ['laptop001'],
));
// Checkout module handles payment
// Order module creates order
// Inventory module updates stock
// Email module sends confirmation
}
}
Results After Implementation
- Build Time: 2 minutes per module
- Deployment: Daily (safe)
- Team Independence: 95%
- Bug Isolation: Module-specific
Case Study 2: Banking Platform
Company: SecureBank (fictional) Challenge: Legacy banking system modernization
Module Architecture
class BankingPlatform extends Module {
@override
List<Module> get imports => [
AccountManagementModule(),
TransactionModule(),
LoanModule(),
InvestmentModule(),
FraudDetectionModule(),
ComplianceModule(),
CustomerSupportModule(),
];
}
// Critical event flows
class BankingEvents {
// High-value transaction monitoring
static void monitorTransaction(Transaction transaction) {
ModularEvent.fire(TransactionInitiatedEvent(
transactionId: transaction.id,
amount: transaction.amount,
fromAccount: transaction.fromAccount,
toAccount: transaction.toAccount,
));
// Automatic triggers:
// - FraudDetectionModule analyzes risk
// - ComplianceModule checks regulations
// - NotificationModule alerts user
}
}
Security Benefits
- Isolated failures: One module compromise doesn't affect others
- Audit trails: All inter-module communication logged
- Compliance: Each module meets specific regulations
- Monitoring: Real-time health checks and alerts
Conclusion
EventModule provides a robust foundation for microfrontend architectures, enabling:
- 🏗️ Scalable Architecture: Independent development and deployment
- 🔗 Loose Coupling: Modules communicate through well-defined events
- 🛡️ Fault Tolerance: Isolated failures with graceful degradation
- 👥 Team Autonomy: Clear ownership boundaries and responsibilities
- 📈 Performance: Lazy loading and optimized resource utilization
By following the patterns and practices outlined in this guide, development teams can build maintainable, scalable microfrontend applications that grow with their business needs.
"Microfrontends with EventModule: Where independent modules create harmonious applications." 🎼