56 Microfrontend-Integration

Moderne Webanwendungen bestehen oft aus verschiedenen Frontend-Modulen, die von unterschiedlichen Teams entwickelt werden. Ein E-Commerce-System könnte separate Microfrontends für Produktkatalog, Warenkorb, Checkout und Kundenkonto haben. Wie synchronisieren Sie diese unabhängigen Frontend-Komponenten mit Events aus Ihrem Backend?

Event-driven Microfrontend-Integration schafft eine konsistente Benutzererfahrung ohne enge Kopplung zwischen Frontend-Modulen.

56.1 Event-driven UI Updates

Anstatt Frontend-Komponenten direkt miteinander kommunizieren zu lassen, propagieren Sie Backend-Events bis in die UI. Jedes Microfrontend reagiert autonom auf relevante Events und aktualisiert seine Darstellung entsprechend.

Denken Sie an folgendes Szenario: Ein Kunde fügt ein Produkt zum Warenkorb hinzu. Welche UI-Komponenten müssen sich aktualisieren?

Microfrontend Event-Interesse UI-Update
Warenkorb-Widget CartItemAdded Badge-Zähler aktualisieren
Produktseite InventoryChanged Verfügbarkeit anzeigen
Checkout CartChanged Gesamtsumme neuberechnen
Empfehlungen CartItemAdded Ähnliche Produkte anzeigen

Frontend Event Bus Pattern mit JavaScript:

// Zentraler Event Bus für Microfrontend-Kommunikation
class MicrofrontendEventBus {
    constructor() {
        this.eventListeners = new Map();
        this.backendEventSource = null;
        this.initializeBackendConnection();
    }
    
    initializeBackendConnection() {
        // Server-Sent Events für Backend-Integration
        this.backendEventSource = new EventSource('/api/events/stream');
        
        this.backendEventSource.onmessage = (event) => {
            const eventData = JSON.parse(event.data);
            this.emit(eventData.eventType, eventData);
        };
        
        this.backendEventSource.onerror = (error) => {
            console.error('Backend event connection lost:', error);
            this.reconnectToBackend();
        };
    }
    
    subscribe(eventType, callback) {
        if (!this.eventListeners.has(eventType)) {
            this.eventListeners.set(eventType, []);
        }
        this.eventListeners.get(eventType).push(callback);
        
        return () => this.unsubscribe(eventType, callback);
    }
    
    emit(eventType, eventData) {
        const listeners = this.eventListeners.get(eventType) || [];
        listeners.forEach(callback => {
            try {
                callback(eventData);
            } catch (error) {
                console.error(`Error in event listener for ${eventType}:`, error);
            }
        });
    }
    
    reconnectToBackend() {
        setTimeout(() => {
            this.initializeBackendConnection();
        }, 5000);
    }
}

// Globaler Event Bus für alle Microfrontends
window.microfrontendEventBus = new MicrofrontendEventBus();

// Warenkorb Microfrontend
class CartMicrofrontend {
    constructor() {
        this.cartItems = [];
        this.initialize();
    }
    
    initialize() {
        // Backend Events abonnieren
        window.microfrontendEventBus.subscribe('CartItemAdded', (event) => {
            this.handleItemAdded(event.data);
        });
        
        window.microfrontendEventBus.subscribe('CartItemRemoved', (event) => {
            this.handleItemRemoved(event.data);
        });
        
        window.microfrontendEventBus.subscribe('OrderPlaced', (event) => {
            this.handleOrderPlaced(event.data);
        });
    }
    
    handleItemAdded(eventData) {
        const item = {
            productId: eventData.productId,
            quantity: eventData.quantity,
            price: eventData.price
        };
        
        this.cartItems.push(item);
        this.updateCartDisplay();
        
        // Lokales Event für andere UI-Komponenten
        window.microfrontendEventBus.emit('CartDisplayUpdated', {
            itemCount: this.cartItems.length,
            totalAmount: this.calculateTotal()
        });
    }
    
    updateCartDisplay() {
        const cartBadge = document.querySelector('.cart-badge');
        if (cartBadge) {
            cartBadge.textContent = this.cartItems.length;
            cartBadge.classList.add('updated');
            
            setTimeout(() => {
                cartBadge.classList.remove('updated');
            }, 300);
        }
    }
}

// Produktseite Microfrontend
class ProductPageMicrofrontend {
    constructor() {
        this.currentProductId = this.extractProductIdFromUrl();
        this.initialize();
    }
    
    initialize() {
        window.microfrontendEventBus.subscribe('InventoryChanged', (event) => {
            if (event.data.productId === this.currentProductId) {
                this.updateAvailabilityDisplay(event.data);
            }
        });
        
        window.microfrontendEventBus.subscribe('CartDisplayUpdated', (event) => {
            this.updateAddToCartButton(event);
        });
    }
    
    updateAvailabilityDisplay(inventoryData) {
        const availabilityElement = document.querySelector('.product-availability');
        if (availabilityElement) {
            if (inventoryData.quantity > 0) {
                availabilityElement.textContent = `${inventoryData.quantity} verfügbar`;
                availabilityElement.className = 'product-availability available';
            } else {
                availabilityElement.textContent = 'Ausverkauft';
                availabilityElement.className = 'product-availability out-of-stock';
            }
        }
    }
}

React-basierte Microfrontend-Integration:

// React Hook für Event Bus Integration
import { useEffect, useState } from 'react';

function useBackendEvents(eventTypes) {
    const [events, setEvents] = useState({});
    
    useEffect(() => {
        const unsubscribers = eventTypes.map(eventType => {
            return window.microfrontendEventBus.subscribe(eventType, (eventData) => {
                setEvents(prev => ({
                    ...prev,
                    [eventType]: eventData
                }));
            });
        });
        
        return () => {
            unsubscribers.forEach(unsubscribe => unsubscribe());
        };
    }, [eventTypes]);
    
    return events;
}

// Cart Component
function CartWidget() {
    const [cartState, setCartState] = useState({ items: [], total: 0 });
    
    const events = useBackendEvents(['CartItemAdded', 'CartItemRemoved', 'OrderPlaced']);
    
    useEffect(() => {
        if (events.CartItemAdded) {
            setCartState(prev => ({
                items: [...prev.items, events.CartItemAdded.data],
                total: prev.total + events.CartItemAdded.data.price
            }));
        }
        
        if (events.CartItemRemoved) {
            setCartState(prev => ({
                items: prev.items.filter(item => 
                    item.productId !== events.CartItemRemoved.data.productId
                ),
                total: prev.total - events.CartItemRemoved.data.price
            }));
        }
        
        if (events.OrderPlaced) {
            setCartState({ items: [], total: 0 });
        }
    }, [events]);
    
    return (
        <div className="cart-widget">
            <div className="cart-icon">
                🛒
                {cartState.items.length > 0 && (
                    <span className="cart-badge">{cartState.items.length}</span>
                )}
            </div>
            <div className="cart-total">€{cartState.total.toFixed(2)}</div>
        </div>
    );
}

// Product Availability Component
function ProductAvailability({ productId }) {
    const [availability, setAvailability] = useState(null);
    
    const events = useBackendEvents(['InventoryChanged', 'InventoryReserved']);
    
    useEffect(() => {
        if (events.InventoryChanged?.data.productId === productId) {
            setAvailability(events.InventoryChanged.data);
        }
        
        if (events.InventoryReserved?.data.productId === productId) {
            setAvailability(prev => ({
                ...prev,
                quantity: prev.quantity - events.InventoryReserved.data.quantity
            }));
        }
    }, [events, productId]);
    
    if (!availability) return <div>Verfügbarkeit wird geladen...</div>;
    
    return (
        <div className={`availability ${availability.quantity > 0 ? 'in-stock' : 'out-of-stock'}`}>
            {availability.quantity > 0 
                ? `${availability.quantity} auf Lager`
                : 'Ausverkauft'
            }
        </div>
    );
}

56.2 Client-side Event Handling

Wie entscheiden Sie, welche Events lokal im Frontend behandelt werden und welche an das Backend propagiert werden? Client-side Event Handling optimiert die Benutzererfahrung durch sofortige UI-Updates bei vorhersagbaren Aktionen.

Hybrid Event Handling Strategie:

class HybridEventHandler {
    constructor() {
        this.optimisticUpdates = new Map();
        this.eventQueue = [];
        this.isOnline = navigator.onLine;
        
        this.setupNetworkMonitoring();
    }
    
    setupNetworkMonitoring() {
        window.addEventListener('online', () => {
            this.isOnline = true;
            this.flushEventQueue();
        });
        
        window.addEventListener('offline', () => {
            this.isOnline = false;
        });
    }
    
    async handleUserAction(action, data) {
        const actionId = this.generateActionId();
        
        // Sofortige UI-Aktualisierung (optimistic update)
        const rollbackFunction = this.applyOptimisticUpdate(action, data);
        this.optimisticUpdates.set(actionId, rollbackFunction);
        
        try {
            if (this.isOnline) {
                // Backend-Call
                const result = await this.sendToBackend(action, data, actionId);
                
                // Optimistic Update bestätigen
                this.confirmOptimisticUpdate(actionId, result);
            } else {
                // Offline-Modus: In Queue einreihen
                this.eventQueue.push({ action, data, actionId, timestamp: Date.now() });
            }
        } catch (error) {
            // Rollback bei Fehler
            this.rollbackOptimisticUpdate(actionId);
            this.showErrorMessage(action, error);
        }
    }
    
    applyOptimisticUpdate(action, data) {
        switch (action) {
            case 'ADD_TO_CART':
                return this.optimisticallyAddToCart(data);
            case 'REMOVE_FROM_CART':
                return this.optimisticallyRemoveFromCart(data);
            case 'UPDATE_QUANTITY':
                return this.optimisticallyUpdateQuantity(data);
            default:
                return () => {}; // No-op rollback
        }
    }
    
    optimisticallyAddToCart(data) {
        const cartWidget = document.querySelector('.cart-widget');
        const currentCount = parseInt(cartWidget.dataset.itemCount || '0');
        const newCount = currentCount + 1;
        
        cartWidget.dataset.itemCount = newCount;
        cartWidget.querySelector('.cart-badge').textContent = newCount;
        cartWidget.classList.add('pending');
        
        // Rollback-Funktion zurückgeben
        return () => {
            cartWidget.dataset.itemCount = currentCount;
            cartWidget.querySelector('.cart-badge').textContent = currentCount;
            cartWidget.classList.remove('pending', 'confirmed');
        };
    }
    
    confirmOptimisticUpdate(actionId, backendResult) {
        const rollback = this.optimisticUpdates.get(actionId);
        this.optimisticUpdates.delete(actionId);
        
        // UI-Feedback für erfolgreiche Bestätigung
        const elements = document.querySelectorAll('.pending');
        elements.forEach(el => {
            el.classList.remove('pending');
            el.classList.add('confirmed');
            setTimeout(() => el.classList.remove('confirmed'), 500);
        });
    }
    
    rollbackOptimisticUpdate(actionId) {
        const rollbackFunction = this.optimisticUpdates.get(actionId);
        if (rollbackFunction) {
            rollbackFunction();
            this.optimisticUpdates.delete(actionId);
        }
        
        // Fehler-UI anzeigen
        const elements = document.querySelectorAll('.pending');
        elements.forEach(el => {
            el.classList.remove('pending');
            el.classList.add('error');
            setTimeout(() => el.classList.remove('error'), 2000);
        });
    }
    
    async flushEventQueue() {
        for (const queuedEvent of this.eventQueue) {
            try {
                await this.sendToBackend(
                    queuedEvent.action, 
                    queuedEvent.data, 
                    queuedEvent.actionId
                );
            } catch (error) {
                console.error('Failed to sync queued event:', error);
            }
        }
        this.eventQueue = [];
    }
}

56.3 State Synchronization

Komplexe Microfrontend-Anwendungen benötigen koordinierte Zustandssynchronisation. Redux-ähnliche State-Management-Patterns lassen sich elegant mit Event-Streams verbinden.

Event-driven State Management:

// Zentraler State Store für alle Microfrontends
class MicrofrontendStateStore {
    constructor() {
        this.state = {
            user: null,
            cart: { items: [], total: 0 },
            inventory: {},
            orders: [],
            ui: { loading: false, errors: [] }
        };
        
        this.subscribers = new Set();
        this.eventHandlers = new Map();
        
        this.setupEventHandlers();
        this.connectToBackendEvents();
    }
    
    setupEventHandlers() {
        this.eventHandlers.set('OrderPlaced', (event) => {
            this.updateState(prevState => ({
                ...prevState,
                cart: { items: [], total: 0 },
                orders: [...prevState.orders, event.data]
            }));
        });
        
        this.eventHandlers.set('CartItemAdded', (event) => {
            this.updateState(prevState => ({
                ...prevState,
                cart: {
                    items: [...prevState.cart.items, event.data],
                    total: prevState.cart.total + event.data.price
                }
            }));
        });
        
        this.eventHandlers.set('InventoryChanged', (event) => {
            this.updateState(prevState => ({
                ...prevState,
                inventory: {
                    ...prevState.inventory,
                    [event.data.productId]: event.data.quantity
                }
            }));
        });
        
        this.eventHandlers.set('UserLoggedIn', (event) => {
            this.updateState(prevState => ({
                ...prevState,
                user: event.data.user,
                cart: event.data.cart || prevState.cart
            }));
        });
    }
    
    connectToBackendEvents() {
        window.microfrontendEventBus.subscribe('*', (event) => {
            const handler = this.eventHandlers.get(event.eventType);
            if (handler) {
                handler(event);
            }
        });
    }
    
    updateState(updater) {
        const newState = updater(this.state);
        const hasChanged = !this.deepEqual(this.state, newState);
        
        if (hasChanged) {
            const previousState = this.state;
            this.state = newState;
            
            this.notifySubscribers(newState, previousState);
        }
    }
    
    subscribe(callback) {
        this.subscribers.add(callback);
        
        // Aktuellen State sofort senden
        callback(this.state);
        
        return () => this.subscribers.delete(callback);
    }
    
    notifySubscribers(newState, previousState) {
        this.subscribers.forEach(callback => {
            try {
                callback(newState, previousState);
            } catch (error) {
                console.error('Error in state subscriber:', error);
            }
        });
    }
    
    getState() {
        return this.state;
    }
    
    // Action-Dispatcher für lokale State-Änderungen
    dispatch(action) {
        switch (action.type) {
            case 'SET_LOADING':
                this.updateState(state => ({
                    ...state,
                    ui: { ...state.ui, loading: action.payload }
                }));
                break;
                
            case 'ADD_ERROR':
                this.updateState(state => ({
                    ...state,
                    ui: { 
                        ...state.ui, 
                        errors: [...state.ui.errors, action.payload] 
                    }
                }));
                break;
                
            case 'CLEAR_ERRORS':
                this.updateState(state => ({
                    ...state,
                    ui: { ...state.ui, errors: [] }
                }));
                break;
        }
    }
    
    deepEqual(obj1, obj2) {
        return JSON.stringify(obj1) === JSON.stringify(obj2);
    }
}

// Globaler State Store
window.microfrontendStore = new MicrofrontendStateStore();

// React Hook für State-Integration
function useMicrofrontendState(selector) {
    const [state, setState] = useState(() => {
        const globalState = window.microfrontendStore.getState();
        return selector ? selector(globalState) : globalState;
    });
    
    useEffect(() => {
        const unsubscribe = window.microfrontendStore.subscribe((newState) => {
            const selectedState = selector ? selector(newState) : newState;
            setState(selectedState);
        });
        
        return unsubscribe;
    }, [selector]);
    
    return state;
}

// Beispiel-Komponente mit State-Integration
function OrderSummary() {
    const cart = useMicrofrontendState(state => state.cart);
    const user = useMicrofrontendState(state => state.user);
    
    const handlePlaceOrder = async () => {
        window.microfrontendStore.dispatch({ 
            type: 'SET_LOADING', 
            payload: true 
        });
        
        try {
            const orderData = {
                customerId: user.id,
                items: cart.items,
                totalAmount: cart.total
            };
            
            // Backend-Call über Event Bus
            await window.hybridEventHandler.handleUserAction('PLACE_ORDER', orderData);
            
        } catch (error) {
            window.microfrontendStore.dispatch({
                type: 'ADD_ERROR',
                payload: 'Bestellung konnte nicht aufgegeben werden'
            });
        } finally {
            window.microfrontendStore.dispatch({ 
                type: 'SET_LOADING', 
                payload: false 
            });
        }
    };
    
    return (
        <div className="order-summary">
            <h3>Bestellübersicht</h3>
            {cart.items.map(item => (
                <div key={item.productId} className="order-item">
                    {item.name} - €{item.price}
                </div>
            ))}
            <div className="order-total">
                Gesamt: €{cart.total.toFixed(2)}
            </div>
            <button onClick={handlePlaceOrder}>
                Bestellung aufgeben
            </button>
        </div>
    );
}

State Synchronization Patterns:

Pattern Konsistenz Performance Komplexität Anwendungsfall
Shared State Store Stark Mittel Hoch Komplexe UI-Interaktionen
Event-only Sync Eventual Hoch Niedrig Lose gekoppelte MF
Hybrid Approach Konfigurierbar Optimal Mittel Production Systems

Die Event-driven Microfrontend-Integration ermöglicht es, unabhängige Frontend-Module zu entwickeln, die trotzdem eine konsistente Benutzererfahrung bieten. Events fungieren als universelle Kommunikationsschicht zwischen Backend-Services und Frontend-Komponenten.

Was sind Ihre Erfahrungen mit Microfrontend-Architekturen? Welche Herausforderungen sehen Sie bei der Event-Synchronisation in verteilten Frontend-Systemen?