Distributed Monolith: Microservices yang Gagal Move On dari Monolit

Posted: 26-02-2026, 20:49 WIB
distributed-monolith-thumb

Microservices tapi setiap deploy harus barengan. Scaling satu service tetap bikin yang lain ikut deg-degan. Dan yang paling klasik: semua masih numpang di database yang sama.

Kalau ini terasa familiar, kemungkinan besar kamu tidak punya arsitektur terdistribusi. Kamu cuma memecah monolit jadi potongan kecil… lalu menyebarkannya ke network.

Itulah Distributed Monolith — dapat kompleksitas distributed system, tapi tidak dapat independensinya.

Ketika Service Cuma Dipisah Secara Fisik

Banyak tim memulai perjalanan ke microservices dengan niat mulia: mau scalable, mau tim bisa deploy sendiri-sendiri, mau lebih fleksibel. Tapi di tengah jalan, desainnya melenceng.

Service dipisah berdasarkan layer teknis. Ada OrderService, InventoryService, UserService. Kedengarannya rapi. Tapi di balik layar, semuanya masih:

  • Mengakses tabel yang sama
  • Panggil satu sama lain secara synchronous
  • Gagal total kalau satu service down
  • Perlu deployment terkoordinasi

Secara folder sudah terpisah. Secara repo mungkin sudah beda. Tapi secara coupling? Masih satu tubuh.

distributed-monolith-sharing-database

Diagram distributed monolith: beberapa service terpisah tapi semuanya terhubung ke satu shared database

Masalah paling berbahaya adalah shared database. Begitu dua service menyentuh tabel yang sama, boundary langsung kabur. Perubahan skema kecil di satu service bisa merusak service lain. Itu bukan independensi. Itu sandera berjamaah.

Contoh Nyata: Shared Database + Synchronous Call

Mari lihat contoh sederhana. Ini terlihat seperti dua service terpisah, tapi sebenarnya masih satu kesatuan rapuh.

// =============================
// ❌ Distributed Monolith
// Shared Database + Synchronous Call
// =============================
// Simulasi shared database
const db = {
  orders: [],
  inventory: {
    productA: 10
  }
};
class InventoryService {
  async reserveStock(items) {
    for (const item of items) {
      if (!db.inventory[item.sku] || db.inventory[item.sku] < item.qty) {
        return false;
      }
    }
    for (const item of items) {
      db.inventory[item.sku] -= item.qty;
    }
    return true;
  }
}
class OrderService {
  constructor(inventoryService) {
    this.inventoryService = inventoryService;
  }
  async createOrder(orderData) {
    // Simpan langsung ke shared DB
    db.orders.push(orderData);
    // Panggil service lain secara synchronous
    const reserved = await this.inventoryService.reserveStock(orderData.items);
    if (!reserved) {
      throw new Error("Out of stock");
    }
    return orderData;
  }
}

Kelihatannya masuk akal. Order dibuat, lalu inventory dicek. Tapi pikirkan ini: kalau InventoryService down, OrderService ikut gagal. Kalau skema inventory berubah, order bisa ikut rusak.

Itu artinya mereka bukan dua bounded context. Mereka satu transaksi besar yang dipaksa terdistribusi.

Dan kamu tetap harus deploy keduanya bersama.

Versi yang Lebih Sehat: Event-Driven + Isolated Data

Sekarang bandingkan dengan pendekatan yang lebih matang. Masing-masing service punya data sendiri. Komunikasi lewat event, bukan panggilan langsung.

sequence-event-driven

Diagram sequence event-driven: Order Service publish OrderCreated → Event Bus → Inventory Service handle event

// =============================
// ✅ Microservices Lebih Sehat
// Event-Driven + Isolated Database
// =============================
// Event Bus sederhana
class EventBus {
  constructor() {
    this.handlers = {};
  }
  subscribe(eventName, handler) {
    if (!this.handlers[eventName]) {
      this.handlers[eventName] = [];
    }
    this.handlers[eventName].push(handler);
  }
  async publish(eventName, payload) {
    const handlers = this.handlers[eventName] || [];
    for (const handler of handlers) {
      await handler(payload);
    }
  }
}
// Order punya "database" sendiri
class OrderRepository {
  constructor() {
    this.orders = [];
  }
  async save(order) {
    this.orders.push(order);
    return order;
  }
}
// Inventory punya "database" sendiri
class InventoryRepository {
  constructor() {
    this.stock = {
      productA: 10
    };
  }
  async reserveStock(items) {
    for (const item of items) {
      if (!this.stock[item.sku] || this.stock[item.sku] < item.qty) {
        throw new Error("Out of stock");
      }
    }
    for (const item of items) {
      this.stock[item.sku] -= item.qty;
    }
  }
}
class OrderService {
  constructor(orderRepo, eventBus) {
    this.orderRepo = orderRepo;
    this.eventBus = eventBus;
  }
  async createOrder(orderData) {
    const order = await this.orderRepo.save(orderData);
    // Publish event, bukan panggil langsung service lain
    await this.eventBus.publish("OrderCreated", {
      orderId: order.id,
      items: order.items
    });
    return order;
  }
}
class InventoryService {
  constructor(inventoryRepo, eventBus) {
    this.inventoryRepo = inventoryRepo;
    eventBus.subscribe("OrderCreated", async (event) => {
      try {
        await this.inventoryRepo.reserveStock(event.items);
        console.log("Stock reserved for order", event.orderId);
      } catch (err) {
        console.log("Stock reservation failed for order", event.orderId);
      }
    });
  }
}

Di sini OrderService tidak peduli apakah inventory berhasil atau gagal saat itu juga. Ia hanya publish event. InventoryService bereaksi terhadap event tersebut.

Coupling turun drastis. Masing-masing bisa evolve sendiri. Database benar-benar terisolasi.

Apakah ini lebih kompleks? Ya. Tapi kompleksitasnya terkontrol dan sesuai tujuan — bukan kompleksitas karena desain setengah matang.

Akar Masalah Sebenarnya: Boundary yang Salah

Distributed monolith hampir selalu lahir dari satu hal: tidak ada pemetaan domain yang jelas.

Service dipisah karena alasan teknis, bukan karena business capability. Padahal microservices bukan soal berapa banyak service yang kamu punya. Ini soal independensi evolusi.

Kalau dua service selalu berubah bersama, selalu deploy bersama, selalu saling panggil synchronous — besar kemungkinan mereka sebenarnya satu bounded context.

Memisahkannya hanya karena “microservices itu keren” adalah cara cepat menciptakan utang arsitektur.

Mungkin Kamu Tidak Butuh Microservices

Ini yang jarang dibahas: monolit modular yang rapi seringkali jauh lebih sehat daripada distributed monolith.

Monolit yang punya boundary jelas di dalamnya, punya testing yang kuat, dan struktur domain yang bersih, seringkali lebih mudah di-maintain daripada lima service kecil yang saling menyandera lewat network.

Microservices bukan upgrade otomatis. Itu trade-off.

Kalau sistem kamu terasa seperti microservices tapi tetap menegangkan setiap kali deploy, mungkin masalahnya bukan di scaling. Mungkin masalahnya di desain.

Dan desain yang salah tidak akan pernah bisa ditutupi hanya dengan memecah repository menjadi lebih banyak service.

Yuk, Jelajahi Topik Lainnya!