Micro Frontend'ler: İleri Düzey Pattern'ler, Performance ve Production Dersleri

Micro frontend'lerde ileri düzey pattern'ler, hata ayıklama teknikleri ve production deneyimleri. Module federation, event bus tasarımı ve gerçek dünya çözümleri.

Micro Frontend Serisi Navigasyon#

  • Kısım 1: Mimari temelleri ve uygulama türleri
  • Kısım 2: Module Federation, iletişim kalıpları ve entegrasyon stratejileri
  • Kısım 3 (Burada bulunuyorsunuz): İleri düzey kalıplar, performans optimizasyonu ve production hata ayıklama

Ön Koşullar: Bu yazı Kısım 1 temellerine ve Kısım 2 uygulama kalıplarına aşinalık varsayar.


Micro frontend serimizin son bölümünde, production'da büyük ölçekte micro frontend'leri çalıştırırken ortaya çıkan ileri düzey zorlukları ele alacağız. Bunlar production olaylarını hata ayıklama, yük altında performansı optimize etme ve dağıtık frontend mimarilerinin karmaşıklığını kaldırabilecek dayanıklı sistemler kurma deneyimlerinden öğrenilen dersler.

Bu yazı, milyonlarca kullanıcıya hizmet veren micro frontend sistemlerini yönetmekten elde edilen zor kazanılmış bilgileri kapsıyor - ayrıca kritik mimari içgörülere yol açan bazı acı verici hata ayıklama hikayelerini de içeriyor.

İleri Düzey State Management Kalıpları#

Micro frontend mimarilerindeki en karmaşık zorluklardan biri, bağımsız olarak deploy edilen uygulamalar arasında paylaşılan state'i yönetmektir. İşte production'da başarılı olmuş kalıplar:

1. Event Sourcing ile Dağıtık State Management#

TypeScript
// @company/shared-state - Micro frontend'ler için ileri düzey state management
interface StateEvent {
  id: string;
  type: string;
  payload: any;
  timestamp: number;
  version: number;
  microfrontend: string;
}

interface StateSnapshot {
  version: number;
  data: any;
  timestamp: number;
}

class DistributedStateManager {
  private eventStore: StateEvent[] = [];
  private snapshots: Map<string, StateSnapshot> = new Map();
  private subscribers: Map<string, Set<(state: any) => void>> = new Map();
  private maxEventHistory = 1000;
  private snapshotInterval = 100; // Her 100 event'te snapshot oluştur

  constructor(private persistenceAdapter?: PersistenceAdapter) {
    // Persistence katmanından başlangıç state'ini yükle
    this.loadInitialState();
  }

  // Sadece ekleme yapılan event store
  dispatch(event: Omit<StateEvent, 'id' | 'timestamp' | 'version'>) {
    const stateEvent: StateEvent = {
      ...event,
      id: generateId(),
      timestamp: Date.now(),
      version: this.eventStore.length + 1,
    };

    this.eventStore.push(stateEvent);

    // Performans için periyodik snapshot'lar oluştur
    if (stateEvent.version % this.snapshotInterval === 0) {
      this.createSnapshot(event.type.split(':')[0]); // event type'dan namespace
    }

    // Harici storage'a kaydet
    this.persistenceAdapter?.persistEvent(stateEvent);

    // Subscriber'ları bilgilendir
    this.notifySubscribers(event.type, this.getState(event.type.split(':')[0]));

    // Event geçmişi boyutunu koru
    if (this.eventStore.length > this.maxEventHistory) {
      this.eventStore = this.eventStore.slice(-this.maxEventHistory);
    }
  }

  getState(namespace: string): any {
    // Önce en son snapshot'ı kullanmaya çalış
    const snapshot = this.snapshots.get(namespace);
    const eventsAfterSnapshot = snapshot
      ? this.eventStore.filter(e => e.version > snapshot.version && e.type.startsWith(namespace))
      : this.eventStore.filter(e => e.type.startsWith(namespace));

    let state = snapshot?.data || {};

    // Snapshot'tan sonraki event'leri uygula
    eventsAfterSnapshot.forEach(event => {
      state = this.applyEvent(state, event);
    });

    return state;
  }

  subscribe(eventType: string, callback: (state: any) => void): () => void {
    if (!this.subscribers.has(eventType)) {
      this.subscribers.set(eventType, new Set());
    }

    this.subscribers.get(eventType)!.add(callback);

    // Mevcut state ile hemen çağır
    const namespace = eventType.split(':')[0];
    callback(this.getState(namespace));

    // Unsubscribe fonksiyonu döndür
    return () => {
      this.subscribers.get(eventType)?.delete(callback);
    };
  }

  private applyEvent(state: any, event: StateEvent): any {
    switch (event.type) {
      case 'user:login':
        return { ...state, user: event.payload, isAuthenticated: true };

      case 'user:logout':
        return { ...state, user: null, isAuthenticated: false };

      case 'cart:add-item':
        const items = state.items || [];
        return {
          ...state,
          items: [...items, event.payload],
          total: calculateTotal([...items, event.payload])
        };

      case 'cart:remove-item':
        const filteredItems = (state.items || []).filter(
          (item: any) => item.id !== event.payload.id
        );
        return {
          ...state,
          items: filteredItems,
          total: calculateTotal(filteredItems)
        };

      default:
        return state;
    }
  }

  private createSnapshot(namespace: string) {
    const state = this.getState(namespace);
    const snapshot: StateSnapshot = {
      version: this.eventStore.length,
      data: state,
      timestamp: Date.now(),
    };

    this.snapshots.set(namespace, snapshot);
    this.persistenceAdapter?.persistSnapshot(namespace, snapshot);
  }

  private notifySubscribers(eventType: string, state: any) {
    const callbacks = this.subscribers.get(eventType);
    if (callbacks) {
      callbacks.forEach(callback => {
        try {
          callback(state);
        } catch (error) {
          console.error(`Error in state subscriber for ${eventType}:`, error);
        }
      });
    }
  }

  private async loadInitialState() {
    if (this.persistenceAdapter) {
      try {
        const { events, snapshots } = await this.persistenceAdapter.loadInitialState();
        this.eventStore = events;
        snapshots.forEach((snapshot, namespace) => {
          this.snapshots.set(namespace, snapshot);
        });
      } catch (error) {
        console.error('Failed to load initial state:', error);
      }
    }
  }

  // Debug yardımcıları
  getEventHistory(namespace?: string): StateEvent[] {
    if (namespace) {
      return this.eventStore.filter(e => e.type.startsWith(namespace));
    }
    return [...this.eventStore];
  }

  replayEventsFrom(version: number): void {
    const eventsToReplay = this.eventStore.filter(e => e.version >= version);
    console.log(`${version} versiyonundan ${eventsToReplay.length} event'i yeniden oynatılıyor`);

    eventsToReplay.forEach(event => {
      console.log(`Yeniden oynatılıyor: ${event.type}`, event.payload);
    });
  }
}

// Persistence adapter arayüzü
interface PersistenceAdapter {
  persistEvent(event: StateEvent): Promise<void>;
  persistSnapshot(namespace: string, snapshot: StateSnapshot): Promise<void>;
  loadInitialState(): Promise<{
    events: StateEvent[];
    snapshots: Map<string, StateSnapshot>;
  }>;
}

// Daha kolay kullanım için React hook'ları
export const useDistributedState = <T>(namespace: string): [T, (event: any) => void] => {
  const [state, setState] = useState<T>({} as T);
  const stateManager = useContext(StateManagerContext);

  useEffect(() => {
    const unsubscribe = stateManager.subscribe(`${namespace}:*`, setState);
    return unsubscribe;
  }, [namespace, stateManager]);

  const dispatch = useCallback((event: any) => {
    stateManager.dispatch({
      type: `${namespace}:${event.type}`,
      payload: event.payload,
      microfrontend: event.source || 'unknown',
    });
  }, [namespace, stateManager]);

  return [state, dispatch];
};

2. Çakışma Çözümü ile Optimistic Update'ler#

TypeScript
// Micro frontend'ler için gelişmiş optimistic update kalıbı
interface OptimisticUpdate {
  id: string;
  type: string;
  payload: any;
  timestamp: number;
  microfrontend: string;
  status: 'pending' | 'confirmed' | 'failed';
}

class OptimisticStateManager {
  private pendingUpdates: Map<string, OptimisticUpdate> = new Map();
  private baseState: any = {};
  private stateManager: DistributedStateManager;

  constructor(stateManager: DistributedStateManager) {
    this.stateManager = stateManager;
  }

  optimisticDispatch(event: any): string {
    const updateId = generateId();
    const optimisticUpdate: OptimisticUpdate = {
      id: updateId,
      type: event.type,
      payload: event.payload,
      timestamp: Date.now(),
      microfrontend: event.source,
      status: 'pending',
    };

    this.pendingUpdates.set(updateId, optimisticUpdate);

    // Optimistic update'i hemen uygula
    this.applyOptimisticUpdate(optimisticUpdate);

    // Sunucuya gönder
    this.sendToServer(event)
      .then(() => {
        // Update'i onayla
        const update = this.pendingUpdates.get(updateId);
        if (update) {
          update.status = 'confirmed';
          // Onaylanmış state'i uygula
          this.stateManager.dispatch(event);
        }
      })
      .catch((error) => {
        // Optimistic update'i geri al
        const update = this.pendingUpdates.get(updateId);
        if (update) {
          update.status = 'failed';
          this.revertOptimisticUpdate(updateId);

          // Kullanıcıya hata göster
          this.showConflictResolution(error, event);
        }
      })
      .finally(() => {
        this.pendingUpdates.delete(updateId);
      });

    return updateId;
  }

  private applyOptimisticUpdate(update: OptimisticUpdate) {
    // Update'i UI'ya optimistic olarak uygula
    const event = {
      type: update.type,
      payload: { ...update.payload, _optimistic: true },
      microfrontend: update.microfrontend,
    };

    this.stateManager.dispatch(event);
  }

  private revertOptimisticUpdate(updateId: string) {
    const update = this.pendingUpdates.get(updateId);
    if (!update) return;

    // Geri alma event'ini gönder
    this.stateManager.dispatch({
      type: `${update.type}:revert`,
      payload: { originalPayload: update.payload },
      microfrontend: update.microfrontend,
    });
  }

  private async sendToServer(event: any): Promise<any> {
    const response = await fetch('/api/state/update', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event),
    });

    if (!response.ok) {
      throw new Error(`Sunucu update'i reddetti: ${response.statusText}`);
    }

    return response.json();
  }

  private showConflictResolution(error: Error, originalEvent: any) {
    // Çakışma çözümü için UI göster
    this.stateManager.dispatch({
      type: 'ui:show-conflict-resolution',
      payload: {
        error: error.message,
        originalEvent,
        timestamp: Date.now(),
      },
      microfrontend: 'system',
    });
  }
}

Performans Optimizasyon Stratejileri#

1. İleri Düzey Bundle Analizi ve Optimizasyon#

TypeScript
// webpack-bundle-analyzer-reporter.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

class MicroFrontendBundleReporter {
  constructor(options = {}) {
    this.options = {
      reportDir: './bundle-reports',
      threshold: 50000, // 50KB
      ...options,
    };
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync('MicroFrontendBundleReporter', (compilation, callback) => {
      const stats = compilation.getStats().toJson();
      const analysis = this.analyzeBundle(stats);

      // Rapor oluştur
      this.generateReport(analysis);

      // Büyük bundle'lar hakkında uyarı ver
      this.checkBundleSize(analysis);

      callback();
    });
  }

  analyzeBundle(stats) {
    const analysis = {
      totalSize: 0,
      sharedDependencies: {},
      duplicatedDependencies: [],
      largeDependencies: [],
      unusedExports: [],
    };

    stats.chunks.forEach(chunk => {
      chunk.modules.forEach(module => {
        const size = module.size || 0;
        analysis.totalSize += size;

        // Paylaşılan bağımlılıkları tanımla
        if (module.name && module.name.includes('node_modules')) {
          const dep = this.extractDependencyName(module.name);
          if (!analysis.sharedDependencies[dep]) {
            analysis.sharedDependencies[dep] = { size: 0, count: 0 };
          }
          analysis.sharedDependencies[dep].size += size;
          analysis.sharedDependencies[dep].count++;
        }

        // Büyük bağımlılıkları işaretle
        if (size > this.options.threshold) {
          analysis.largeDependencies.push({
            name: module.name,
            size,
            reasons: module.reasons || [],
          });
        }
      });
    });

    // Duplike bağımlılıkları bul
    Object.entries(analysis.sharedDependencies).forEach(([dep, info]) => {
      if (info.count > 1) {
        analysis.duplicatedDependencies.push({ name: dep, ...info });
      }
    });

    return analysis;
  }

  generateReport(analysis) {
    const report = {
      timestamp: new Date().toISOString(),
      microfrontend: process.env.MICRO_FRONTEND_NAME || 'unknown',
      ...analysis,
    };

    const fs = require('fs');
    const path = require('path');

    if (!fs.existsSync(this.options.reportDir)) {
      fs.mkdirSync(this.options.reportDir, { recursive: true });
    }

    const reportPath = path.join(
      this.options.reportDir,
      `bundle-report-${Date.now()}.json`
    );

    fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
    console.log(`Bundle raporu oluşturuldu: ${reportPath}`);
  }

  checkBundleSize(analysis) {
    const maxSize = 500000; // 500KB uyarı eşiği

    if (analysis.totalSize > maxSize) {
      console.warn(`⚠️  Bundle boyutu (${analysis.totalSize} byte) önerilen eşiği (${maxSize} byte) aşıyor`);
    }

    if (analysis.duplicatedDependencies.length > 0) {
      console.warn('⚠️  Duplike bağımlılıklar bulundu:');
      analysis.duplicatedDependencies.forEach(dep => {
        console.warn(`  - ${dep.name}: ${dep.size} byte (${dep.count} kopya)`);
      });
    }
  }

  extractDependencyName(moduleName) {
    const match = moduleName.match(/node_modules[\/\\](@[^\/\\]+[\/\\][^\/\\]+|[^\/\\]+)/);
    return match ? match[1] : moduleName;
  }
}

module.exports = MicroFrontendBundleReporter;

2. Performans İzleme ve Optimizasyon#

TypeScript
// @company/performance-monitor
interface PerformanceMetrics {
  microfrontend: string;
  loadTime: number;
  renderTime: number;
  bundleSize: number;
  memoryUsage: number;
  errorCount: number;
}

class MicroFrontendPerformanceMonitor {
  private metrics: Map<string, PerformanceMetrics> = new Map();
  private observers: PerformanceObserver[] = [];

  constructor(private reportingEndpoint?: string) {
    this.setupPerformanceObservers();
  }

  startMeasurement(microfrontendName: string) {
    const startTime = performance.now();

    return {
      recordLoadComplete: () => {
        const loadTime = performance.now() - startTime;
        this.updateMetrics(microfrontendName, { loadTime });
      },

      recordRenderComplete: () => {
        const renderTime = performance.now() - startTime;
        this.updateMetrics(microfrontendName, { renderTime });
      },

      recordError: () => {
        const current = this.metrics.get(microfrontendName);
        this.updateMetrics(microfrontendName, {
          errorCount: (current?.errorCount || 0) + 1
        });
      }
    };
  }

  private setupPerformanceObservers() {
    // Resource yüklemeyi izle
    const resourceObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.name.includes('remoteEntry.js')) {
          const microfrontendName = this.extractMicrofrontendName(entry.name);
          this.updateMetrics(microfrontendName, {
            bundleSize: entry.transferSize || 0,
          });
        }
      });
    });
    resourceObserver.observe({ entryTypes: ['resource'] });
    this.observers.push(resourceObserver);

    // Uzun task'ları izle
    const longTaskObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.duration > 50) { // 50ms'den uzun task'lar
          console.warn(`Uzun task algılandı: ${entry.duration}ms`);
          this.reportLongTask(entry);
        }
      });
    });
    longTaskObserver.observe({ entryTypes: ['longtask'] });
    this.observers.push(longTaskObserver);

    // Bellek kullanımını izle
    this.startMemoryMonitoring();
  }

  private startMemoryMonitoring() {
    setInterval(() => {
      if ('memory' in performance) {
        const memInfo = (performance as any).memory;
        this.metrics.forEach((_, microfrontendName) => {
          this.updateMetrics(microfrontendName, {
            memoryUsage: memInfo.usedJSHeapSize,
          });
        });
      }
    }, 10000); // Her 10 saniyede
  }

  private updateMetrics(microfrontendName: string, updates: Partial<PerformanceMetrics>) {
    const current = this.metrics.get(microfrontendName) || {
      microfrontend: microfrontendName,
      loadTime: 0,
      renderTime: 0,
      bundleSize: 0,
      memoryUsage: 0,
      errorCount: 0,
    };

    const updated = { ...current, ...updates };
    this.metrics.set(microfrontendName, updated);

    // İzleme servisine rapor et
    this.reportMetrics(updated);
  }

  private reportMetrics(metrics: PerformanceMetrics) {
    if (this.reportingEndpoint) {
      fetch(this.reportingEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(metrics),
      }).catch(error => {
        console.error('Metrik raporlama başarısız:', error);
      });
    }

    // Geliştirme ortamında console'a da logla
    if (process.env.NODE_ENV === 'development') {
      console.log(`[Performance] ${metrics.microfrontend}:`, metrics);
    }
  }

  private reportLongTask(entry: PerformanceEntry) {
    if (this.reportingEndpoint) {
      fetch(`${this.reportingEndpoint}/long-tasks`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          duration: entry.duration,
          startTime: entry.startTime,
          timestamp: Date.now(),
        }),
      }).catch(error => {
        console.error('Uzun task raporlama başarısız:', error);
      });
    }
  }

  getMetrics(microfrontendName?: string): PerformanceMetrics | Map<string, PerformanceMetrics> {
    if (microfrontendName) {
      return this.metrics.get(microfrontendName)!;
    }
    return new Map(this.metrics);
  }

  cleanup() {
    this.observers.forEach(observer => observer.disconnect());
  }

  private extractMicrofrontendName(url: string): string {
    const match = url.match(/\/\/([^.]+)\./);
    return match ? match[1] : 'unknown';
  }
}

// Performans izleme için React hook
export const usePerformanceMonitor = (microfrontendName: string) => {
  const monitor = useContext(PerformanceMonitorContext);

  useEffect(() => {
    const measurement = monitor.startMeasurement(microfrontendName);

    // Component mount olduğunda kaydet (render tamamlandı)
    measurement.recordRenderComplete();

    return () => {
      // Gerekirse temizlik
    };
  }, [microfrontendName, monitor]);

  const recordError = useCallback(() => {
    const measurement = monitor.startMeasurement(microfrontendName);
    measurement.recordError();
  }, [microfrontendName, monitor]);

  return { recordError };
};

Production Hata Ayıklama Hikayeleri ve Kalıpları#

Büyük Bellek Sızıntısı Avı#

En zorlu production sorunlarımızdan biri, yalnızca kullanıcılar uygulamayı birkaç saat kullandıktan sonra ortaya çıkan bir bellek sızıntısıydı. Belirtiler süptildi - uygulama yavaş yavaş yavaşlıyor ve sonunda bazı micro frontend'ler tamamen yanıt vermeyi durduruyordu.

Araştırma, bir referans döngüsü oluşturan micro frontend'ler arasında karmaşık bir etkileşim ortaya çıkardı:

TypeScript
// Bellek sızıntısına neden olan problemli kod
const ProductList: React.FC = () => {
  const eventBus = useContext(EventBusContext);

  useEffect(() => {
    // Bu, tüm component'i referans olarak tutan bir closure oluşturuyordu
    const handler = (event: any) => {
      // Handler closure tüm component scope'unu yakalar
      setProducts(prevProducts => {
        // Eski state'i tutan karmaşık state güncellemeleri
        return updateProductsWithComplexLogic(prevProducts, event);
      });
    };

    eventBus.subscribe('cart:updated', handler);

    // Bazı kod yollarında erken return'ler nedeniyle unsubscribe hiç çağrılmıyordu
    return () => eventBus.unsubscribe('cart:updated', handler);
  }, []); // Eksik bağımlılıklar stale closure'lara neden oldu

Düzeltme için sistematik bir bellek yönetimi yaklaşımı gerekti:

TypeScript
// Uygun bellek yönetimi ile düzeltilmiş versiyon
const ProductList: React.FC = () => {
  const eventBus = useContext(EventBusContext);
  const [products, setProducts] = useState<Product[]>([]);

  // Gereksiz yeniden oluşturmaları önlemek için useCallback kullan
  const handleCartUpdate = useCallback((event: CartUpdateEvent) => {
    setProducts(prevProducts => {
      // Referans döngülerini önlemek için immutable güncellemeler kullan
      return prevProducts.map(product =>
        product.id === event.productId
          ? { ...product, inCart: event.inCart }
          : product
      );
    });
  }, []); // Fonksiyonel güncellemeler kullandığımız için bağımlılık gerekmez

  useEffect(() => {
    const unsubscribe = eventBus.subscribe('cart:updated', handleCartUpdate);

    // Her zaman temizliğin gerçekleşmesini sağla
    return () => {
      unsubscribe();
    };
  }, [eventBus, handleCartUpdate]);

  // Geliştirme ortamında bellek kullanımını izle
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      const interval = setInterval(() => {
        if ('memory' in performance) {
          const memInfo = (performance as any).memory;
          console.log(`ProductList bellek kullanımı: ${memInfo.usedJSHeapSize / 1024 / 1024} MB`);
        }
      }, 5000);

      return () => clearInterval(interval);
    }
  }, []);

  return (
    <div className="product-list">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
};

// Bellek sızıntısı tespit yardımcısı
class MemoryLeakDetector {
  private snapshots: any[] = [];
  private interval: NodeJS.Timeout;

  constructor(private intervalMs: number = 30000) {
    this.interval = setInterval(() => {
      this.takeSnapshot();
    }, intervalMs);
  }

  private takeSnapshot() {
    if ('memory' in performance) {
      const memInfo = (performance as any).memory;
      const snapshot = {
        timestamp: Date.now(),
        usedJSHeapSize: memInfo.usedJSHeapSize,
        totalJSHeapSize: memInfo.totalJSHeapSize,
        jsHeapSizeLimit: memInfo.jsHeapSizeLimit,
      };

      this.snapshots.push(snapshot);

      // Son 20 snapshot'ı tut
      if (this.snapshots.length > 20) {
        this.snapshots.shift();
      }

      this.analyzeMemoryTrend();
    }
  }

  private analyzeMemoryTrend() {
    if (this.snapshots.length &lt;5) return;

    const recent = this.snapshots.slice(-5);
    const isIncreasing = recent.every((snapshot, index) => {
      if (index === 0) return true;
      return snapshot.usedJSHeapSize > recent[index - 1].usedJSHeapSize;
    });

    if (isIncreasing) {
      const growth = recent[recent.length - 1].usedJSHeapSize - recent[0].usedJSHeapSize;
      const growthMB = growth / 1024 / 1024;

      if (growthMB > 10) { // 10MB'den fazla büyüme
        console.warn(`Potansiyel bellek sızıntısı algılandı. Büyüme: ${growthMB.toFixed(2)} MB`);
        this.reportMemoryLeak(recent);
      }
    }
  }

  private reportMemoryLeak(snapshots: any[]) {
    // İzleme servisine rapor et
    if (typeof window !== 'undefined' && (window as any).analytics) {
      (window as any).analytics.track('Memory Leak Detected', {
        snapshots,
        userAgent: navigator.userAgent,
        timestamp: Date.now(),
      });
    }
  }

  cleanup() {
    if (this.interval) {
      clearInterval(this.interval);
    }
  }
}

Cross-Origin İletişim Kabusu#

Bir diğer production sorunu, farklı subdomain'lerde deploy edilen micro frontend'ler arasında aralıklı cross-origin iletişim başarısızlıklarıydı. Belirtiler çıldırtıcıydı - bazen çalışıyor, bazen çalışmıyor, açık bir kalıp yoktu.

TypeScript
// Çözüm: Sağlam cross-origin iletişim
class CrossOriginMessenger {
  private trustedOrigins: Set<string> = new Set();
  private messageQueue: Array<{ data: any; targetOrigin: string; retry: number }> = [];
  private maxRetries = 3;

  constructor(trustedOrigins: string[]) {
    trustedOrigins.forEach(origin => this.trustedOrigins.add(origin));
    this.setupMessageListener();
    this.startRetryProcessor();
  }

  private setupMessageListener() {
    window.addEventListener('message', (event) => {
      // Sıkı origin kontrolü
      if (!this.trustedOrigins.has(event.origin)) {
        console.warn(`Güvenilmeyen origin'den mesaj reddedildi: ${event.origin}`);
        return;
      }

      try {
        const message = JSON.parse(event.data);
        this.handleMessage(message, event.origin);
      } catch (error) {
        console.error('Cross-origin mesajı ayrıştırılamadı:', error);
      }
    });
  }

  sendMessage(data: any, targetOrigin: string, retries: number = 0): boolean {
    if (!this.trustedOrigins.has(targetOrigin)) {
      console.error(`Güvenilmeyen origin'e mesaj gönderme denemesi: ${targetOrigin}`);
      return false;
    }

    try {
      const serializedData = JSON.stringify({
        ...data,
        timestamp: Date.now(),
        sender: window.location.origin,
        messageId: generateId(),
      });

      // Hedef window'u bulmaya çalış
      const targetWindow = this.findTargetWindow(targetOrigin);

      if (targetWindow) {
        targetWindow.postMessage(serializedData, targetOrigin);
        return true;
      } else {
        // Daha sonra tekrar denemek için kuyruğa al
        this.messageQueue.push({ data, targetOrigin, retry: retries });
        return false;
      }
    } catch (error) {
      console.error('Cross-origin mesaj gönderme başarısız:', error);
      return false;
    }
  }

  private findTargetWindow(targetOrigin: string): Window | null {
    // Tüm iframe'leri kontrol et
    const iframes = document.querySelectorAll('iframe');
    for (const iframe of iframes) {
      try {
        if (iframe.src.startsWith(targetOrigin)) {
          return iframe.contentWindow;
        }
      } catch (error) {
        // Erişim reddedildi - muhtemelen cross-origin
        continue;
      }
    }

    // Parent window olup olmadığını kontrol et
    if (window.parent !== window) {
      try {
        if (document.referrer.startsWith(targetOrigin)) {
          return window.parent;
        }
      } catch (error) {
        // Erişim reddedildi
      }
    }

    return null;
  }

  private startRetryProcessor() {
    setInterval(() => {
      const toRetry = this.messageQueue.splice(0); // Tüm kuyruktaki mesajları al

      toRetry.forEach(({ data, targetOrigin, retry }) => {
        if (retry < this.maxRetries) {
          const success = this.sendMessage(data, targetOrigin, retry + 1);
          if (!success) {
            // Artan retry sayısı ile yeniden kuyruğa al
            this.messageQueue.push({ data, targetOrigin, retry: retry + 1 });
          }
        } else {
          console.error(`${this.maxRetries} denemeden sonra mesaj gönderilemedi:`, data);
        }
      });
    }, 1000); // Her saniye tekrar dene
  }

  private handleMessage(message: any, origin: string) {
    // Farklı mesaj türlerini ele al
    switch (message.type) {
      case 'state-update':
        this.handleStateUpdate(message.payload);
        break;
      case 'navigation':
        this.handleNavigation(message.payload);
        break;
      case 'error':
        this.handleError(message.payload);
        break;
      default:
        console.warn(`Bilinmeyen mesaj türü: ${message.type}`);
    }
  }

  private handleStateUpdate(payload: any) {
    // Paylaşılan state'i güncelle
    if (typeof window !== 'undefined' && (window as any).stateManager) {
      (window as any).stateManager.dispatch({
        type: payload.type,
        payload: payload.data,
        source: 'cross-origin',
      });
    }
  }

  private handleNavigation(payload: any) {
    // Navigasyon isteklerini ele al
    if (payload.path && typeof window !== 'undefined') {
      window.history.pushState({}, '', payload.path);
    }
  }

  private handleError(payload: any) {
    console.error('Cross-origin hata alındı:', payload);
    // İzleme servisine rapor et
  }
}

Micro Frontend'ler için Güvenlik Düşünceleri#

1. Dinamik Yükleme için Content Security Policy (CSP)#

TypeScript
// Micro frontend uygulamaları için CSP header üretici
class MicroFrontendCSPGenerator {
  constructor(
    private allowedOrigins: string[],
    private isDevelopment: boolean = false
  ) {}

  generateCSP(): string {
    const directives = [];

    // Script kaynakları - kendi origin ve micro frontend origin'lerini izin ver
    const scriptSrc = [
      "'self'",
      ...this.allowedOrigins,
    ];

    if (this.isDevelopment) {
      scriptSrc.push("'unsafe-eval'"); // Geliştirme araçları için
    }

    directives.push(`script-src ${scriptSrc.join(' ')}`);

    // API çağrıları için connect kaynakları
    const connectSrc = [
      "'self'",
      ...this.allowedOrigins,
      // API endpoint'leri ekle
      'https://api.company.com',
    ];

    directives.push(`connect-src ${connectSrc.join(' ')}`);

    // iframe tabanlı micro frontend'ler için frame kaynakları
    const frameSrc = [
      "'self'",
      ...this.allowedOrigins,
    ];

    directives.push(`frame-src ${frameSrc.join(' ')}`);

    // Image kaynakları
    directives.push(`img-src 'self' data: https:`);

    // Style kaynakları
    const styleSrc = [
      "'self'",
      "'unsafe-inline'", // Dinamik stiller için gerekli
      ...this.allowedOrigins,
    ];

    directives.push(`style-src ${styleSrc.join(' ')}`);

    return directives.join('; ');
  }

  // Express.js için middleware
  middleware() {
    return (req: any, res: any, next: any) => {
      res.setHeader('Content-Security-Policy', this.generateCSP());
      next();
    };
  }
}

// Kullanım
const cspGenerator = new MicroFrontendCSPGenerator([
  'https://products.company.com',
  'https://cart.company.com',
  'https://user.company.com',
], process.env.NODE_ENV === 'development');

app.use(cspGenerator.middleware());

2. Güvenli Micro-Frontend Arası İletişim#

TypeScript
// Güvenli iletişim kanalı
class SecureMicroFrontendCommunication {
  private secretKey: string;
  private trustedOrigins: Set<string>;

  constructor(secretKey: string, trustedOrigins: string[]) {
    this.secretKey = secretKey;
    this.trustedOrigins = new Set(trustedOrigins);
  }

  async sendSecureMessage(data: any, targetOrigin: string): Promise<boolean> {
    if (!this.trustedOrigins.has(targetOrigin)) {
      throw new Error(`Güvenilmeyen origin: ${targetOrigin}`);
    }

    try {
      // Timestamp ve nonce ile mesaj oluştur
      const message = {
        data,
        timestamp: Date.now(),
        nonce: this.generateNonce(),
      };

      // Mesajı imzala
      const signature = await this.signMessage(message);
      const secureMessage = { ...message, signature };

      // postMessage ile gönder
      const targetWindow = this.findTargetWindow(targetOrigin);
      if (targetWindow) {
        targetWindow.postMessage(JSON.stringify(secureMessage), targetOrigin);
        return true;
      }

      return false;
    } catch (error) {
      console.error('Güvenli mesaj gönderme başarısız:', error);
      return false;
    }
  }

  async verifyAndHandleMessage(event: MessageEvent): Promise<boolean> {
    if (!this.trustedOrigins.has(event.origin)) {
      console.warn(`Güvenilmeyen origin'den mesaj: ${event.origin}`);
      return false;
    }

    try {
      const message = JSON.parse(event.data);

      // Timestamp doğrula (replay saldırılarını önle)
      const age = Date.now() - message.timestamp;
      if (age > 60000) { // Maksimum 1 dakika yaş
        console.warn('Mesaj çok eski, potansiyel replay saldırısı');
        return false;
      }

      // İmzayı doğrula
      const isValid = await this.verifySignature(message);
      if (!isValid) {
        console.warn('Geçersiz mesaj imzası');
        return false;
      }

      // Mesajı işle
      this.handleVerifiedMessage(message.data);
      return true;
    } catch (error) {
      console.error('Mesaj doğrulama başarısız:', error);
      return false;
    }
  }

  private async signMessage(message: any): Promise<string> {
    const encoder = new TextEncoder();
    const data = encoder.encode(JSON.stringify(message));
    const keyData = encoder.encode(this.secretKey);

    const cryptoKey = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );

    const signature = await crypto.subtle.sign('HMAC', cryptoKey, data);
    return Array.from(new Uint8Array(signature))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  private async verifySignature(message: any): Promise<boolean> {
    const { signature, ...messageWithoutSignature } = message;
    const expectedSignature = await this.signMessage(messageWithoutSignature);
    return signature === expectedSignature;
  }

  private generateNonce(): string {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
  }

  private findTargetWindow(targetOrigin: string): Window | null {
    // Önceki örnekteki implementasyona benzer
    return null; // Kısalık için basitleştirildi
  }

  private handleVerifiedMessage(data: any) {
    // Doğrulanmış mesajı ele al
    console.log('Doğrulanmış mesaj alındı:', data);
  }
}

Migrasyon Stratejileri#

Strangler Fig Pattern Implementasyonu#

TypeScript
// Monolith'ten micro frontend'lere kademeli migrasyon
class StranglerFigMigration {
  private routes: Map<string, 'monolith' | 'microfrontend'> = new Map();
  private featureFlags: Map<string, boolean> = new Map();

  constructor(private config: MigrationConfig) {
    this.initializeRoutes();
  }

  private initializeRoutes() {
    // Tüm route'lar monolith'e gidecek şekilde başla
    this.config.allRoutes.forEach(route => {
      this.routes.set(route, 'monolith');
    });

    // Feature flag'lara göre kademeli olarak micro frontend route'larını etkinleştir
    this.config.microfrontendRoutes.forEach(route => {
      const flagName = `enable_mf_${route.replace(/[^a-zA-Z0-9]/g, '_')}`;
      if (this.featureFlags.get(flagName)) {
        this.routes.set(route, 'microfrontend');
      }
    });
  }

  routeRequest(path: string): 'monolith' | 'microfrontend' {
    // Önce tam eşleşme kontrolü
    if (this.routes.has(path)) {
      return this.routes.get(path)!;
    }

    // Pattern eşleşmeleri kontrol et
    for (const [route, target] of this.routes.entries()) {
      if (this.matchesPattern(path, route)) {
        return target;
      }
    }

    // Bilinmeyen route'lar için monolith varsayılan
    return 'monolith';
  }

  migrateRoute(route: string) {
    console.log(`${route} route'u micro frontend'e migrate ediliyor`);
    this.routes.set(route, 'microfrontend');

    // İzleme için migrasyon event'ini logla
    this.logMigrationEvent(route);
  }

  rollbackRoute(route: string) {
    console.log(`${route} route'u monolith'e geri alınıyor`);
    this.routes.set(route, 'monolith');

    // Rollback event'ini logla
    this.logRollbackEvent(route);
  }

  private matchesPattern(path: string, pattern: string): boolean {
    // Basit wildcard eşleştirme
    const regex = new RegExp(
      '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '
    );
    return regex.test(path);
  }

  private logMigrationEvent(route: string) {
    if (typeof window !== 'undefined' && (window as any).analytics) {
      (window as any).analytics.track('Route Migrated', {
        route,
        timestamp: Date.now(),
      });
    }
  }

  private logRollbackEvent(route: string) {
    if (typeof window !== 'undefined' && (window as any).analytics) {
      (window as any).analytics.track('Route Rolled Back', {
        route,
        timestamp: Date.now(),
      });
    }
  }
}

interface MigrationConfig {
  allRoutes: string[];
  microfrontendRoutes: string[];
}

Seri Sonucu: Micro Frontend Mimarilerinde Ustalaşmak#

Tebrikler! Micro frontend mimarileri üzerine kapsamlı yolculuğumuzu tamamladınız. Üç bölümde ele aldıklarımızı özetleyelim:

Kısım 1 - Temel kalıpları öğrendiniz:

  • Server-side template composition
  • Build-time integration
  • Runtime integration
  • Iframe tabanlı izolasyon

Kısım 2 - Pratik implementasyonda ustalaştınız:

  • Production'a hazır Module Federation konfigürasyonları
  • Sağlam hata yönetimi ve iletişim kalıpları
  • Routing koordinasyon stratejileri
  • Geliştirme iş akışları ve test yaklaşımları

Kısım 3 (Bu yazı) - İleri düzey production tekniklerini keşfettiniz:

  • Event sourcing ile dağıtık state management
  • Performans izleme ve optimizasyon
  • Güvenlik kalıpları ve hata ayıklama stratejileri
  • Migrasyon yaklaşımları ve gerçek dünya dersleri

Production Başarısı için Anahtar Öğretiler#

Bu sistemleri büyük ölçekte implementasyona dayalı en kritik dersler:

  1. Basit Başlayın - Build-time integration ile başlayın ve takım bağımsızlığı gerektirdiğinde runtime'a geçin
  2. Tooling'e Yatırım Yapın - Performans izleme, hata ayıklama araçları ve geliştirme iş akışları opsiyonel değil
  3. Başarısızlık için Plan Yapın - Her dinamik yükleme başarısız olabilir; birinci günden nazik degradation şart
  4. Güvenlik Öncelik - Cross-origin iletişim dikkatli değerlendirme gerektiren yeni saldırı vektörleri tanıtır
  5. Her Şeyi İzleyin - Dağıtık sistemler kapsamlı gözlemlenebilirlik ve proaktif hata tespiti gerektirir

Sırada Ne Var?#

Micro frontend ekosistemi hızla gelişmeye devam ediyor. Şunları takip edin:

  • Single-SPA ve Module Federation alternatifleri gibi framework-agnostic çözümler
  • CDN sağlayıcıları ile micro frontend orkestrasyonu sunan edge-side composition
  • Daha da hızlı ilk sayfa yüklemeleri için streaming mimariler
  • Otomatik bundle bölme ve bağımlılık yönetimi için AI destekli optimizasyon

Bu seri boyunca paylaşılan kalıplar ve hata ayıklama hikayeleri, milyonlarca kullanıcıya hizmet veren production sistemlerinden savaşta test edilmiş bilgiyi temsil ediyor. Her implementasyon benzersiz zorluklar getirir, ancak bu temel kavramları anlamak karmaşıklığı daha etkili bir şekilde yönetmenize yardımcı olacaktır.

Micro Frontend Yolculuğunuza Devam Edin#

Daha derine inmek mi istiyorsunuz? Şunları keşfetmeyi düşünün:

Kendi sisteminizi mi kuruyorsunuz? Takımınızın ihtiyaçları için doğru kalıbı seçmek için Kısım 1 ile başlayın, sonra Kısım 2 tekniklerini kullanarak implemente edin.

Frontend mimarisinin geleceği dağıtık ve artık organizasyonunuzun büyümesiyle ölçeklenebilen sistemler kurma bilgisine sahipsiniz.


Tam Seri Navigasyonu

  • Kısım 1: Mimari temelleri
  • Kısım 2: Uygulama kalıpları
  • Kısım 3 (Mevcut): İleri düzey kalıplar ve hata ayıklama

Seri tamamlandı! Artık büyük ölçekte micro frontend mimarilerini tasarlama, uygulama ve optimize etme konusunda donanımlısınız.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts