MICRO-FRONTENDS STATE-MANAGEMENT FRONTEND ARCHITECTURE WEB-DEVELOPMENT SCALABILITY DEVELOPER-EXPERIENCE DESIGN-PATTERNS DATA-CONSISTENCY EVENT-DRIVEN

Strategi Manajemen State untuk Micro-Frontends: Menjaga Konsistensi dan Isolasi di Aplikasi Skala Besar

⏱️ 12 menit baca
👨‍💻

Strategi Manajemen State untuk Micro-Frontends: Menjaga Konsistensi dan Isolasi di Aplikasi Skala Besar

Halo para developer! Pernahkah Anda bekerja dengan aplikasi frontend yang begitu besar sehingga satu perubahan kecil di satu bagian bisa memicu rentetan bug di bagian lain? Atau, tim Anda kesulitan berkolaborasi karena semua orang mengerjakan satu monolit JavaScript raksasa? Jika ya, kemungkinan besar Anda sudah familiar dengan mengapa Micro-Frontends muncul sebagai solusi yang menarik.

Micro-Frontends adalah pendekatan arsitektur di mana aplikasi frontend dipecah menjadi bagian-bagian yang lebih kecil, independen, dan dapat dikelola oleh tim yang berbeda. Ini menjanjikan skalabilitas, otonomi tim, dan kecepatan pengembangan. Namun, seperti halnya setiap arsitektur yang kuat, Micro-Frontends juga membawa tantangan uniknya sendiri. Salah satu tantangan terbesar yang seringkali luput dari perhatian di awal adalah manajemen state.

Bagaimana micro-frontend yang berbeda bisa bekerja sama tanpa saling mengganggu state satu sama lain? Kapan kita perlu berbagi state, dan bagaimana caranya agar tetap konsisten? Mari kita selami lebih dalam!

1. Pendahuluan: Mengapa State Menjadi Rumit di Micro-Frontends?

Dalam aplikasi monolitik, state global seringkali mudah diakses oleh semua komponen. Anda punya Redux store tunggal, atau Context API yang bisa digunakan di mana saja. Namun, di dunia Micro-Frontends, setiap “mikro-aplikasi” idealnya bersifat independen. Ini berarti mereka punya siklus hidup, teknologi stack, dan state management mereka sendiri.

Tantangan muncul ketika:

  1. Isolasi: Kita ingin setiap micro-frontend beroperasi secara mandiri, tanpa terpengaruh oleh state internal micro-frontend lain. Ini penting untuk otonomi tim dan mencegah efek samping yang tidak diinginkan.
  2. Kebutuhan Berbagi: Pada saat yang sama, ada kalanya micro-frontends perlu berbagi informasi penting, seperti status autentikasi pengguna, keranjang belanja, atau preferensi bahasa. Jika tidak ada mekanisme berbagi yang baik, pengalaman pengguna bisa terfragmentasi.
  3. Konsistensi: Ketika state dibagikan, bagaimana kita memastikan bahwa semua micro-frontend melihat state yang sama dan konsisten, terutama saat terjadi perubahan?

Strategi yang tepat untuk manajemen state di Micro-Frontends adalah kunci untuk mewujudkan manfaat penuh dari arsitektur ini. Tanpa itu, Anda mungkin akan berakhir dengan “distributed monolith” di sisi frontend, yang lebih buruk daripada monolit aslinya!

2. Pola Isolasi State: Fondasi Otonomi Micro-Frontend

Prinsip dasar Micro-Frontends adalah isolasi. Setiap micro-frontend harus mampu berdiri sendiri dan mengelola state-nya tanpa bergantung pada state internal micro-frontend lain. Ini adalah pola default dan seringkali merupakan pola terbaik untuk sebagian besar state.

📌 Konsep Inti: Biarkan setiap micro-frontend mengelola state-nya sendiri seolah-olah itu adalah aplikasi tunggal.

Contoh Praktis: Misalkan Anda memiliki dua micro-frontend: ProductList dan ShoppingCart.

Setiap micro-frontend dapat menggunakan library state management favoritnya (misalnya, React Context, Redux, Zustand, Vuex, Pinia, atau bahkan useState sederhana).

// micro-frontend ProductList (menggunakan React)
import React, { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    // Fetch products based on searchTerm
    // ...
  }, [searchTerm]);

  return (
    <div>
      <input
        type="text"
        placeholder="Cari produk..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {loading ? <p>Memuat produk...</p> : (
        <ul>
          {products.map(product => (
            <li key={product.id}>{product.name} - ${product.price}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default ProductList;

Kelebihan Isolasi State:

Kekurangan Isolasi State:

Kapan Isolasi Cukup? Sebagian besar state harus diisolasi. Pola ini adalah dasar yang kuat dan harus menjadi pilihan pertama Anda. Hanya jika ada kebutuhan yang jelas dan tidak dapat dihindari untuk berbagi state, barulah Anda mempertimbangkan pola berbagi.

3. Pola Berbagi State: Menghubungkan Dunia Mikro

Meskipun isolasi adalah raja, ada skenario di mana micro-frontends perlu berkomunikasi dan berbagi state. Ini biasanya terjadi untuk data yang bersifat global bagi seluruh aplikasi atau data yang mempengaruhi pengalaman pengguna secara keseluruhan.

⚠️ Peringatan: Berbagi state harus dilakukan dengan hati-hati. Terlalu banyak berbagi bisa menghilangkan manfaat isolasi Micro-Frontends dan menciptakan “distributed monolith”.

Berikut adalah beberapa pola umum untuk berbagi state:

3.1. Berbagi State Melalui Custom Events (Event Bus)

Ini adalah salah satu cara paling umum dan paling fleksibel untuk berbagi state antar micro-frontends yang independen dan bahkan berbeda framework. Ide dasarnya adalah menggunakan mekanisme event browser asli (CustomEvent) atau implementasi Event Bus sederhana untuk mengirimkan dan menerima notifikasi tentang perubahan state.

💡 Cara Kerja:

  1. Satu micro-frontend “menerbitkan” sebuah event (dispatch CustomEvent) ketika state pentingnya berubah.
  2. Micro-frontends lain dapat “mendengarkan” event tersebut (addEventListener) dan memperbarui state internal mereka berdasarkan data yang diterima.

Contoh Kode (Sederhana): Misalkan micro-frontend Auth mengelola status login. Ketika pengguna login, ia mengirim event:

// micro-frontend Auth
function handleLoginSuccess(userData) {
  // ... simpan userData secara internal ...
  const event = new CustomEvent('app:user-logged-in', {
    detail: { user: userData, timestamp: Date.now() },
    bubbles: true, // agar event bisa "naik" ke parent DOM
    composed: true // agar event bisa melewati shadow DOM boundaries
  });
  window.dispatchEvent(event); // atau elemen DOM yang menjadi container micro-frontend
  console.log('Event app:user-logged-in dikirim');
}

// Contoh penggunaan:
// setelah login berhasil
// handleLoginSuccess({ id: 'user-123', name: 'Budi' });

Micro-frontend Header yang menampilkan nama pengguna bisa mendengarkan event ini:

// micro-frontend Header (menggunakan React)
import React, { useState, useEffect } from 'react';

function Header() {
  const [loggedInUser, setLoggedInUser] = useState(null);

  useEffect(() => {
    const handleLoginEvent = (event) => {
      console.log('Event app:user-logged-in diterima:', event.detail.user);
      setLoggedInUser(event.detail.user);
    };

    const handleLogoutEvent = () => {
      setLoggedInUser(null);
    };

    window.addEventListener('app:user-logged-in', handleLoginEvent);
    window.addEventListener('app:user-logged-out', handleLogoutEvent); // anggap ada event logout juga

    return () => {
      window.removeEventListener('app:user-logged-in', handleLoginEvent);
      window.removeEventListener('app:user-logged-out', handleLogoutEvent);
    };
  }, []);

  return (
    <header>
      <h1>Aplikasi Saya</h1>
      {loggedInUser ? (
        <p>Selamat datang, {loggedInUser.name}!</p>
      ) : (
        <button>Login</button>
      )}
    </header>
  );
}

export default Header;

Kelebihan Custom Events:

Kekurangan Custom Events:

3.2. State Melalui URL (Query Parameters / Path)

Untuk state yang terkait dengan navigasi atau filter, URL adalah tempat yang sangat baik untuk berbagi. Browser secara alami menyimpan state ini, dan pengguna dapat membagikan atau me-refresh halaman tanpa kehilangan konteks.

🎯 Contoh Penggunaan:

Ketika micro-frontend Search memperbarui query q, ia bisa mengubah URL. Micro-frontend ProductList dapat membaca query parameter q dari URL dan memperbarui daftar produknya.

// micro-frontend Search
function handleSearch(query) {
  const url = new URL(window.location);
  url.searchParams.set('q', query);
  window.history.pushState({}, '', url); // update URL
}

// micro-frontend ProductList (mendengarkan perubahan URL)
useEffect(() => {
  const handleUrlChange = () => {
    const params = new URLSearchParams(window.location.search);
    setSearchTerm(params.get('q') || '');
  };

  window.addEventListener('popstate', handleUrlChange); // untuk back/forward button
  // Juga panggil saat mount pertama kali
  handleUrlChange();

  return () => window.removeEventListener('popstate', handleUrlChange);
}, []);

Kelebihan URL State:

Kekurangan URL State:

3.3. Shared Library / Global State Container (Monorepo)

Jika Anda menggunakan monorepo dan semua micro-frontend Anda menggunakan framework yang sama (misalnya, semua React), Anda bisa membuat sebuah “shared library” yang berisi Context API atau Redux store yang dibagikan.

📦 Cara Kerja:

  1. Buat sebuah package terpisah di monorepo yang berisi provider state global (misalnya, AuthContext.Provider atau Redux store).
  2. Setiap micro-frontend yang membutuhkan state tersebut akan mengimpor dan menggunakan provider/store ini.

Contoh (React Context di Shared Library):

// packages/shared-auth/src/AuthContext.js
import React, { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null); // Atau fetch dari localStorage/cookie

  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);
// micro-frontend Header (menggunakan shared-auth)
import React from 'react';
import { useAuth } from '@shared-auth'; // Import dari shared library

function Header() {
  const { user, logout } = useAuth();

  return (
    <header>
      <h1>Aplikasi Saya</h1>
      {user ? (
        <>
          <p>Selamat datang, {user.name}!</p>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button>Login</button>
      )}
    </header>
  );
}

Kelebihan Shared Library:

Kekurangan Shared Library:

3.4. State Melalui Web Workers / Shared Workers

Untuk skenario yang lebih canggih, terutama jika Anda memiliki state yang sangat besar atau memerlukan komputasi berat di luar thread UI utama, Web Workers (khususnya SharedWorker) bisa menjadi solusi.

⚙️ Cara Kerja:

Kelebihan Shared Workers:

Kekurangan Shared Workers:

4. Memilih Strategi yang Tepat

Memilih strategi manajemen state tidak ada yang “satu ukuran cocok untuk semua”. Pertimbangkan faktor-faktor berikut:

🎯 Tips Umum:

Kesimpulan

Manajemen state di arsitektur Micro-Frontends adalah area yang menantang namun krusial. Dengan memahami pola isolasi dan berbagai pola berbagi state seperti Custom Events, URL State, Shared Libraries, dan Shared Workers, Anda dapat merancang aplikasi yang tetap fleksibel, skalabel, dan mudah dikelola. Ingat, kuncinya adalah menyeimbangkan kebutuhan akan otonomi dengan kebutuhan akan koordinasi. Mulailah dengan isolasi, dan hanya berbagi state jika ada alasan bisnis yang kuat. Dengan perencanaan yang matang, Micro-Frontends Anda akan tumbuh menjadi ekosistem aplikasi yang tangguh dan efisien!

🔗 Baca Juga