Chuyển đến nội dung chính

Lesson 14: Cross-MFE State Management & Communication

State management patterns for Micro Frontend. Custom Events, Event Bus, URL-based state, Browser Storage. Avoid shared global state. Practical patterns with React Context, CustomEvent.

🏗️ Architecture — Lesson 14 Lesson 14: Cross-MFE State Management & Communication

Microservices & Micro Frontend system design — From basics to Production

Part 5: Building a practical Micro Frontend

xdev.asia

Introduction

Cross-MFE communication is the biggest challenge of Micro Frontend. Each MFE runs in its own runtime, without shared memory. This article introduces secure communication patterns.


1. Golden rule

MFEs should be loose coupled — communicate via contracts, not shared state.

❌ Shared Global State:
window.globalStore = { cart: { items: [...] } }
→ Coupling nightmare, race conditions

✅ Event-based Communication:
MFE A publishes: "cart:item-added" { productId, qty }
MFE B subscribes: update cart badge
→ Loosely coupled, observable

2. Communication Patterns

2.1 Custom Events (Browser Native)

// Product MFE - Publish
window.dispatchEvent(new CustomEvent('cart:item-added', {
  detail: { productId: '123', name: 'Laptop', price: 999 }
}));

// Cart MFE - Subscribe
useEffect(() => {
  const handler = (e) => {
    setCartItems(prev => [...prev, e.detail]);
  };
  window.addEventListener('cart:item-added', handler);
  return () => window.removeEventListener('cart:item-added', handler);
}, []);

2.2 Event Bus (Custom)

class EventBus {
  private handlers: Map<string, Set<Function>> = new Map();
  
  on<T>(event: string, handler: (payload: T) => void) {
    if (!this.handlers.has(event)) this.handlers.set(event, new Set());
    this.handlers.get(event)!.add(handler);
    return () => this.handlers.get(event)?.delete(handler);
  }
  
  emit<T>(event: string, payload: T) {
    this.handlers.get(event)?.forEach(h => h(payload));
  }
}

export const eventBus = new EventBus();

2.3 URL-based State

/products?category=electronics&sort=price
→ URL là "shared state" tự nhiên: bookmarkable, shareable, no coupling

2.4 Browser Storage

// Shared preferences via localStorage
localStorage.setItem('user:preferences', JSON.stringify({
  currency: 'VND', language: 'vi', theme: 'dark'
}));

3. State Ownership Rules

StateOwnerHow MFEs Access
Auth stateShellContext / Events
Cart itemsCart MFEEvents
Product dataProduct MFENot shared
User prefsShelllocalStorage + Events
URL paramsShell (Router)URL
UI themeShellCSS Variables

4. Contract-first Communication

// shared-types/events.ts
interface CartItemAddedEvent {
  type: 'cart:item-added';
  payload: { productId: string; name: string; price: number; quantity: number; };
}

interface CartUpdatedEvent {
  type: 'cart:updated';
  payload: { itemCount: number; total: number; };
}

5. Best Practices

Mỗi MFE nên:
✅ Fetch dữ liệu riêng (own API calls)
✅ Manage state riêng (local state)
✅ Render independently
✅ Graceful degradation

Mỗi MFE KHÔNG nên:
❌ Import components từ MFE khác
❌ Access DOM của MFE khác
❌ Share runtime state qua window/global
❌ Assume MFE khác đã loaded

Summary

  • Custom Events — simple, browser native
  • Event Bus — typed, testable
  • URL — best for navigation state
  • localStorage — user preferences
  • Rule: Each MFE manages its own state, communicates via events

Next article: Lesson 15: Design System & Shared UI — Keep UX consistent