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#
// @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#
// 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#
// 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#
// @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ı:
// 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:
// 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 <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.
// Çö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)#
// 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#
// 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#
// 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:
- Basit Başlayın - Build-time integration ile başlayın ve takım bağımsızlığı gerektirdiğinde runtime'a geçin
- Tooling'e Yatırım Yapın - Performans izleme, hata ayıklama araçları ve geliştirme iş akışları opsiyonel değil
- 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
- Güvenlik Öncelik - Cross-origin iletişim dikkatli değerlendirme gerektiren yeni saldırı vektörleri tanıtır
- 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:
- Resmi Module Federation dokümantasyonu
- Framework-agnostic implementasyonlar için Single-SPA framework
- Micro frontend metrikleri için Web Vitals gibi performans izleme araçları
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.
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!
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!