Micro Frontend Mimarisi Temelleri: Modern Web Uygulamaları İçin Eksiksiz Rehber
Micro frontend mimarisinin temelleri, uygulama stratejileri ve gerçek dünya örnekleri. Module Federation, single-spa ve diğer yaklaşımların detaylı karşılaştırması.
Frontend uygulamaları karmaşıklık ve takım boyutu olarak büyüdükçe, geçmişte bize iyi hizmet veren monolitik yaklaşım sınırlarını göstermeye başlar. Takımlar birbirlerinin ayağına basar, deployment bir koordinasyon kabusu haline gelir ve teknoloji seçimleri yıllarca kilitleniyor. Tanıdık geliyor mu?
Micro frontend'ler bu sorunlara çekici bir çözüm sunar, ancak sihirli bir değnek değiller. Birden fazla organizasyonda büyük ölçekte micro frontend mimarileri implementasyonumdan öğrendiğim şey, başarının büyük ölçüde temel kalıpları ve bunların trade-off'larını anlamaya bağlı olduğudur.
Kapsamlı Micro Frontend Serisi#
Bu, kapsamlı 3 bölümlük serinin 1. Bölümü. İşte tam öğrenme yolunuz:
- Kısım 1 (Burada bulunuyorsunuz): Mimari temelleri ve uygulama türleri
- Kısım 2: Module Federation, iletişim kalıpları ve entegrasyon stratejileri
- Kısım 3: İleri düzey kalıplar, performans optimizasyonu ve production hata ayıklama
Micro frontend'lere yeni misiniz? Temel kavramları anlamak için bu yazıyla başlayın, sonra seriyi sırayla takip edin.
Uygulamaya hazır mısınız? Uygulamalı Module Federation örnekleri için Kısım 2'ye geçin.
Production'da çalışıyor musunuz? İleri düzey hata ayıklama ve optimizasyon teknikleri için doğrudan Kısım 3'e gidin.
Micro Frontend'ler Nedir?#
Micro frontend'ler, microservis konseptini frontend geliştirmeye genişletir. Tek bir monolitik frontend uygulaması yerine, birden çok küçük, bağımsız olarak deploy edilebilir frontend uygulamasını uyumlu bir kullanıcı deneyimi haline getirirsiniz.
Ana prensipler şunlardır:
- Teknoloji Agnostik: Takımlar kendi framework'leri ve araçlarını seçebilir
- Bağımsız Deployment: Her micro frontend bağımsız olarak deploy edilebilir
- Takım Özerkliği: Farklı takımlar uygulamanın farklı kısımlarına sahip olabilir
- Aşamalı Migrasyon: Monolith'lerden kademeli migrasyon mümkündür
Micro Frontend Mimarisi Türleri#
Çeşitli yaklaşımları uygulama deneyimime dayalı olarak, işte dört ana mimari kalıp:
1. Server-Side Template Composition#
Farklı servislerin HTML fragment'larını render ettiği ve sunucuda compose edildiği en basit yaklaşım.
// Birden fazla micro frontend'i compose eden gateway servisi
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.get('/', async (req, res) => {
try {
// Farklı servislerden fragment'ları fetch et
const [header, navigation, content, footer] = await Promise.all([
fetch('http://header-service/fragment').then(r => r.text()),
fetch('http://nav-service/fragment').then(r => r.text()),
fetch('http://content-service/fragment').then(r => r.text()),
fetch('http://footer-service/fragment').then(r => r.text())
]);
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Compose Edilmiş Uygulama</title>
</head>
<body>
${header}
${navigation}
<main>${content}</main>
${footer}
</body>
</html>
`;
res.send(html);
} catch (error) {
res.status(500).send('Error composing page');
}
});
Artıları: Anlaması basit, iyi SEO, JavaScript olmadan çalışır Eksileri: Sınırlı etkileşim, navigasyon için sayfa yenileme, paylaşılan state zorlukları
Ne zaman kullanılır: İçerik ağırlıklı siteler, SEO kritik olduğunda, server-side geliştirmede rahat takımlar
2. Build-Time Integration#
Micro frontend'ler npm paketleri olarak yayınlanır ve build zamanında compose edilir.
// Shell uygulamasının Package.json'ı
{
"dependencies": {
"@company/header-mf": "^1.2.0",
"@company/product-catalog-mf": "^2.1.5",
"@company/checkout-mf": "^1.8.2"
}
}
// Shell uygulaması
import React from 'react';
import { Header } from '@company/header-mf';
import { ProductCatalog } from '@company/product-catalog-mf';
import { Checkout } from '@company/checkout-mf';
const App: React.FC = () => {
return (
<div>
<Header />
<main>
<ProductCatalog />
<Checkout />
</main>
</div>
);
};
export default App;
// Micro frontend paketi (header-mf)
import React from 'react';
export interface HeaderProps {
user?: {
name: string;
avatar: string;
};
onLogout?: () => void;
}
export const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
return (
<header className="bg-blue-600 text-white p-4">
<div className="flex justify-between items-center">
<h1>Uygulamam</h1>
{user && (
<div className="flex items-center gap-2">
<img src={user.avatar} alt={user.name} className="w-8 h-8 rounded-full" />
<span>{user.name}</span>
<button onClick={onLogout}>Çıkış</button>
</div>
)}
</div>
</header>
);
};
Artıları: Type safety, paylaşılan bağımlılık optimizasyonu, tanıdık geliştirme deneyimi Eksileri: Koordineli deployment'lar, versiyon yönetimi karmaşıklığı, gerçekten bağımsız değil
Ne zaman kullanılır: Micro frontend faydaları istiyorsanız ama koordineli deployment'ları tolere edebiliyorsanız
3. JavaScript ile Runtime Integration#
Micro frontend'lerin runtime'da yüklendiği ve entegre edildiği en esnek yaklaşım.
// Micro frontend registry
interface MicroFrontendConfig {
name: string;
url: string;
scope: string;
module: string;
}
class MicroFrontendRegistry {
private configs: Map<string, MicroFrontendConfig> = new Map();
private loadedModules: Map<string, any> = new Map();
register(config: MicroFrontendConfig) {
this.configs.set(config.name, config);
}
async load(name: string): Promise<any> {
if (this.loadedModules.has(name)) {
return this.loadedModules.get(name);
}
const config = this.configs.get(name);
if (!config) {
throw new Error(`Micro frontend ${name} kayıtlı değil`);
}
// Hata yönetimi ile dinamik import
try {
await this.loadScript(config.url);
const container = (window as any)[config.scope];
if (!container) {
throw new Error(`Container ${config.scope} bulunamadı`);
}
await container.init({
react: () => Promise.resolve(React),
'react-dom': () => Promise.resolve(ReactDOM),
});
const factory = await container.get(config.module);
const Module = factory();
this.loadedModules.set(name, Module);
return Module;
} catch (error) {
console.error(`Micro frontend ${name} yüklenemedi:`, error);
throw error;
}
}
private loadScript(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Script yüklenemedi: ${url}`));
document.head.appendChild(script);
});
}
}
// Shell uygulamasında kullanım
const registry = new MicroFrontendRegistry();
registry.register({
name: 'product-catalog',
url: 'http://localhost:3001/remoteEntry.js',
scope: 'productCatalog',
module: './ProductCatalog'
});
const DynamicMicroFrontend: React.FC<{ name: string }> = ({ name }) => {
const [Component, setComponent] = useState<React.ComponentType | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
registry.load(name)
.then(Module => {
setComponent(() => Module.default || Module);
setError(null);
})
.catch(err => {
setError(err.message);
setComponent(null);
})
.finally(() => setLoading(false));
}, [name]);
if (loading) return <div>{name} yükleniyor...</div>;
if (error) return <div>{name} yükleme hatası: {error}</div>;
if (!Component) return <div>{name} bileşeni bulunamadı</div>;
return <Component />;
};
Artıları: Gerçek bağımsızlık, farklı teknoloji stack'leri mümkün, runtime esnekliği Eksileri: Karmaşıklık, runtime hataları, performans overhead'i, hata ayıklama zorlukları
Ne zaman kullanılır: Birden fazla takıma sahip büyük organizasyonlar, teknoloji çeşitliliği ihtiyacı
4. Iframe Tabanlı Integration#
Tam izolasyon için iframe'leri kullanan en izole yaklaşım.
// postMessage iletişimi ile Iframe micro frontend wrapper
interface IframeMicroFrontendProps {
src: string;
name: string;
onMessage?: (data: any) => void;
}
const IframeMicroFrontend: React.FC<IframeMicroFrontendProps> = ({
src,
name,
onMessage
}) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Güvenlik için origin doğrula
if (event.origin !== new URL(src).origin) {
return;
}
if (event.data.source === name) {
onMessage?.(event.data.payload);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [src, name, onMessage]);
const sendMessage = (data: any) => {
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
source: 'shell',
target: name,
payload: data
}, new URL(src).origin);
}
};
return (
<div className="micro-frontend-container">
{!isLoaded && <div>{name} yükleniyor...</div>}
{error && <div>Hata: {error}</div>}
<iframe
ref={iframeRef}
src={src}
onLoad={() => setIsLoaded(true)}
onError={() => setError(`${name} yüklenemedi`)}
style={{
width: '100%',
border: 'none',
minHeight: '400px'
}}
title={name}
sandbox="allow-scripts allow-same-origin allow-forms"
/>
</div>
);
};
// Micro frontend içinde (iframe içeriği)
const MicroFrontendApp: React.FC = () => {
const [data, setData] = useState<any>(null);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data.target === 'product-catalog') {
setData(event.data.payload);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
const sendDataToShell = (payload: any) => {
window.parent.postMessage({
source: 'product-catalog',
payload
}, '*');
};
return (
<div>
<h2>Ürün Katalogu Micro Frontend</h2>
{/* Micro frontend içeriğiniz */}
</div>
);
};
Artıları: Tam izolasyon, güvenlik, farklı domain'ler mümkün, CSS izolasyonu Eksileri: Sınırlı iletişim, SEO zorlukları, performans overhead'i, UX düşünceleri
Ne zaman kullanılır: Güvenlik öncelik, legacy entegrasyon, üçüncü taraf içerik
Gerçek Bir Hata Ayıklama Hikayesi: Kaybolan Stiller Vakası#
Yaygın micro frontend tuzaklarını gösteren bir hata ayıklama hikayesi paylaşayım. Yukarıdakine benzer bir runtime entegrasyon sistemi implement etmiştik ve geliştirme ortamında her şey mükemmel çalışıyordu. Ancak production'da, ürün katalogu micro frontend'imizde eksik stiller hakkında raporlar almaya başladık.
Belirtiler şaşırtıcıydı:
- Stiller micro frontend tek başına çalıştığında gayet iyi çalışıyordu
- Sorun sadece production'da oluyordu, geliştirme ortamında değil
- Aralıklıydı - bazen stiller yükleniyordu, bazen yüklenmiyordu
Saatlerce araştırmadan sonra, temel nedeni keşfettik: CSS yükleme race condition'ları.
// Problemli kod
const ProductCatalogMF: React.FC = () => {
useEffect(() => {
// Bu, component mount'tan sonra CSS yüklüyordu
import('./styles.css');
}, []);
return <div className="product-grid">...</div>;
};
Sorun şuydu: production'da, daha agresif minification ve CDN cache'leme ile CSS import'u component zaten render olduktan sonra tamamlanıyordu. Çözüm daha sağlam bir yükleme stratejisi gerektiriyordu:
// Uygun CSS yükleme ile düzeltilmiş versiyon
const MicroFrontendLoader = {
async loadWithStyles(name: string, cssUrls: string[] = []) {
// Önce CSS'i yükle
await Promise.all(
cssUrls.map(url => this.loadStylesheet(url))
);
// Sonra component'i yükle
return await registry.load(name);
},
loadStylesheet(url: string): Promise<void> {
return new Promise((resolve, reject) => {
// Zaten yüklenmiş mi kontrol et
if (document.querySelector(`link[href="${url}"]`)) {
resolve();
return;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = () => resolve();
link.onerror = () => reject(new Error(`CSS yüklenemedi: ${url}`));
document.head.appendChild(link);
});
}
};
Bu deneyim bize şunların önemini öğretti:
- Micro frontend mimarilerinde doğru yükleme sıralaması
- Environment parity - production sorunları genellikle geliştirme ortamında göstermez
- Monitoring ve gözlemlenebilirlik - bu sorunları erken yakalamak için CSS yükleme takibi ekledik
Performans Düşünceleri#
Micro frontend'ler benzersiz performans zorlukları sunar:
Bundle Boyutu ve Duplikasyon#
Birden fazla micro frontend genellikle aynı bağımlılıkları ship eder, bu da şişkin bundle'lara yol açar.
// Paylaşılan bağımlılıklar için Webpack konfigürasyonu
const ModuleFederationPlugin = require('@module-federation/webpack');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
productCatalog: 'productCatalog@http://localhost:3001/remoteEntry.js',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
},
// Ortak yardımcıları paylaş
lodash: {
singleton: false, // Gerekirse birden fazla versiyona izin ver
}
},
}),
],
};
Yükleme Performansı#
Progressive yükleme stratejileri uygulayın:
// Progressive micro frontend yükleme
const ProgressiveMicroFrontend: React.FC<{
name: string;
priority: 'high' | 'medium' | 'low';
}> = ({ name, priority }) => {
const [shouldLoad, setShouldLoad] = useState(priority === 'high');
const isVisible = useIntersectionObserver();
useEffect(() => {
if (priority === 'medium' && isVisible) {
setShouldLoad(true);
} else if (priority === 'low') {
// Ana içerik hazır olduktan sonra yükle
const timer = setTimeout(() => setShouldLoad(true), 2000);
return () => clearTimeout(timer);
}
}, [isVisible, priority]);
if (!shouldLoad) {
return <div>{name} yükleniyor...</div>;
}
return <DynamicMicroFrontend name={name} />;
};
Doğru Mimariyi Seçmek#
Seçim, özel kısıtlarınıza bağlıdır:
Faktör | Server-Side | Build-Time | Runtime | Iframe |
---|---|---|---|---|
Takım Bağımsızlığı | Düşük | Orta | Yüksek | Yüksek |
Teknoloji Çeşitliliği | Orta | Düşük | Yüksek | Yüksek |
Performans | Yüksek | Yüksek | Orta | Düşük |
Karmaşıklık | Düşük | Orta | Yüksek | Orta |
SEO | Mükemmel | İyi | Zayıf | Zayıf |
Geliştirme Deneyimi | İyi | Mükemmel | Orta | Zayıf |
Sırada Ne Var?#
Artık temel micro frontend kalıplarını anladığınıza göre, pratik implementasyona daha derin dalmaya hazırsınız.
Kısım 2: Module Federation ve Uygulama Kalıpları'na devam edin burada şunları kapsayacağız:
- Production'a hazır Module Federation konfigürasyonları
- Sağlam hata yönetimi ve fallback stratejileri
- Cross-micro frontend iletişim kalıpları
- Uygulamalar arası routing koordinasyonu
- Geliştirme iş akışları ve tooling
- Production sistemlerinden gerçek hata ayıklama hikayeleri
Anahtar Öğreti: Micro frontend'ler sadece teknik bir kalıp değil - takım yapınız, iş gereksinimleri ve teknik kısıtlarınızın dikkatli değerlendirmesini gerektiren organizasyonel bir kalıptır.
Burada kapsanan temel kalıplar mimari kararlarınızı yönlendirecek, ancak gerçek karmaşıklık entegrasyon katmanında ortaya çıkar - bunu bir sonraki yazıda ele alacağız.
Seri Navigasyonu
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!