Micro Frontend Mimarisi: Modern Web Uygulamalarında Modülerlik

Yunus Emre Güzel
19 Ocak 202515 dkFrontend
Micro Frontend Mimarisi: Modern Web Uygulamalarında Modülerlik

Micro frontend mimarisi, büyük ölçekli web uygulamalarını daha yönetilebilir parçalara bölmek için kullanılan modern bir yaklaşımdır. Bu yazıda, micro frontend mimarisinin temellerini, uygulama stratejilerini ve pratik örneklerini inceleyeceğiz.

Micro Frontend Nedir?

Micro frontend, monolitik frontend uygulamalarını bağımsız olarak geliştirilebilen, test edilebilen ve dağıtılabilen daha küçük uygulamalara bölme yaklaşımıdır. Bu yaklaşım, microservices mimarisinin frontend tarafındaki karşılığı olarak düşünülebilir.

Neden Micro Frontend?

Micro frontend mimarisinin sağladığı avantajlar:

  1. Bağımsız Geliştirme: Her takım kendi micro frontend'ini bağımsız olarak geliştirebilir
  2. Teknoloji Özgürlüğü: Her micro frontend farklı teknolojilerle geliştirilebilir
  3. Kolay Ölçeklendirme: Uygulamanın belirli parçaları bağımsız olarak ölçeklendirilebilir
  4. Hızlı Deployment: Küçük parçalar daha hızlı deploy edilebilir
  5. İzole Hatalar: Bir micro frontend'deki hata diğerlerini etkilemez

Entegrasyon Stratejileri

1. Build-Time Entegrasyon

NPM paketleri aracılığıyla micro frontend'leri entegre etme:

{
  "dependencies": {
    "@team-a/header": "1.0.0",
    "@team-b/product-list": "2.1.0",
    "@team-c/shopping-cart": "1.2.0"
  }
}

2. Runtime Entegrasyon (Module Federation)

Webpack 5 Module Federation kullanarak runtime entegrasyon:

// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        headerApp: 'header@http://localhost:3001/remoteEntry.js',
        productApp: 'products@http://localhost:3002/remoteEntry.js',
        cartApp: 'cart@http://localhost:3003/remoteEntry.js'
      }
    })
  ]
};

3. Web Components

Custom elementler kullanarak micro frontend'leri entegre etme:

// header-app.js
class HeaderApp extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <header>
        <nav>
          <ul>
            <li><a href="/">Ana Sayfa</a></li>
            <li><a href="/products">Ürünler</a></li>
            <li><a href="/cart">Sepet</a></li>
          </ul>
        </nav>
      </header>
    `;
  }
}

customElements.define('header-app', HeaderApp);

State Yönetimi

Micro frontend'ler arası state yönetimi için örnek bir implementasyon:

// shared-state.ts
type SharedState = {
  user: {
    id: string;
    name: string;
  };
  cart: {
    items: Array<{
      id: string;
      quantity: number;
    }>;
  };
};

class StateManager {
  private state: SharedState;
  private subscribers: Set<(state: SharedState) => void>;

  constructor() {
    this.state = {
      user: { id: '', name: '' },
      cart: { items: [] }
    };
    this.subscribers = new Set();
  }

  subscribe(callback: (state: SharedState) => void) {
    this.subscribers.add(callback);
    return () => this.subscribers.delete(callback);
  }

  setState(newState: Partial<SharedState>) {
    this.state = { ...this.state, ...newState };
    this.notify();
  }

  private notify() {
    this.subscribers.forEach(callback => callback(this.state));
  }
}

export const stateManager = new StateManager();

Routing Stratejisi

Micro frontend'ler arası routing yönetimi:

// app-shell.ts
import { Router } from '@vaadin/router';

const router = new Router(document.querySelector('#outlet'));

router.setRoutes([
  {
    path: '/',
    component: 'main-app',
    children: [
      {
        path: '/products',
        component: 'product-list',
        action: async () => {
          await import('./products/product-list');
        }
      },
      {
        path: '/cart',
        component: 'shopping-cart',
        action: async () => {
          await import('./cart/shopping-cart');
        }
      }
    ]
  }
]);

Styling Stratejisi

CSS izolasyonu ve paylaşılan stil yönetimi:

// shared-styles.scss
:root {
  --primary-color: #4A90E2;
  --secondary-color: #F5A623;
  --text-color: #333333;
  --spacing-unit: 8px;
}

// Paylaşılan mixinler
@mixin card {
  background: white;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: var(--spacing-unit) * 2;
}

// CSS Modules ile izole stiller
.productCard {
  @include card;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--spacing-unit) * 2;
}

Performans Optimizasyonu

1. Lazy Loading

// main.js
const loadMicroFrontend = async (name) => {
  const script = document.createElement('script');
  script.src = `http://localhost:3000/${name}/remoteEntry.js`;
  document.head.appendChild(script);

  return new Promise((resolve) => {
    script.onload = () => {
      // @ts-ignore
      const module = window[name];
      resolve(module);
    };
  });
};

2. Caching Stratejisi

// service-worker.js
const CACHE_NAME = 'micro-frontend-cache-v1';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/shell/index.html',
        '/shell/main.js',
        '/header/remoteEntry.js',
        '/products/remoteEntry.js'
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

Test Stratejisi

Micro frontend'ler için test yaklaşımı:

// product-list.test.ts
import { render, screen } from '@testing-library/react';
import { ProductList } from './ProductList';

describe('ProductList Micro Frontend', () => {
  it('should render products independently', () => {
    render(<ProductList />);
    expect(screen.getByRole('list')).toBeInTheDocument();
  });

  it('should communicate with other micro frontends', () => {
    const mockStateManager = {
      subscribe: jest.fn(),
      setState: jest.fn()
    };

    render(<ProductList stateManager={mockStateManager} />);
    
    // Add to cart işlemi
    const addButton = screen.getByRole('button', { name: /sepete ekle/i });
    addButton.click();

    expect(mockStateManager.setState).toHaveBeenCalledWith({
      cart: expect.any(Object)
    });
  });
});

Sonuç

Micro frontend mimarisi, büyük ölçekli web uygulamalarının geliştirilmesinde önemli avantajlar sağlar:

  • Bağımsız geliştirme ve deployment
  • Teknoloji özgürlüğü
  • Ölçeklenebilirlik
  • Daha iyi kod organizasyonu
  • Takımlar arası daha iyi iş bölümü

Bu yaklaşımı uygularken dikkat edilmesi gereken noktalar:

  • Doğru entegrasyon stratejisinin seçimi
  • Tutarlı bir kullanıcı deneyimi
  • Performans optimizasyonu
  • Test stratejisi
  • State yönetimi

Kaynaklar