Microservices vs Monolith: Kapan Pake yang Mana?

Oke, jujur aja—kalau lo udah cukup lama di dunia software development, pasti pernah denger debat klasik: microservices vs monolith. Debat ini udah kayak perdebatan “tabs vs spaces” atau “vim vs emacs” yang nggak pernah kelar. Tapi bedanya, pilihan arsitektur ini beneran punya dampak gede ke bisnis dan tim lo.

Gue sendiri udah pernah ngerasain dua-duanya. Pernah bikin monolith yang akhirnya jadi “big ball of mud”, dan juga pernah bikin microservices yang malah bikin pusing karena over-engineering. Dari pengalaman itu, gue bisa bilang: nggak ada yang lebih baik secara absolut. Yang ada cuma “lebih cocok buat kasus lo”.

Di artikel ini, gue mau share pemikiran gue secara jujur dan natural, kayak lagi ngobrol bareng temen. Nggak textbook, nggak formal—langsung ke intinya. Yuk mulai.


Apa Sih Sebenernya Monolith dan Microservices Itu?

Sebelum kita bahas kapan mesti milih yang mana, mari kita samain dulu persepsinya. Biar nggak ada yang salah kaprah.

Monolith: Satu Aplikasi, Semua Ada

Monolith itu arsitektur di mana seluruh aplikasi lo jadi satu kesatuan. Satu codebase, satu deployment, satu proses. Misalnya lo bikin e-commerce, maka modul user, produk, keranjang, pembayaran, semuanya ada di dalam satu aplikasi yang sama.

Contoh sederhananya di Java Spring Boot:

// Semua dalam satu aplikasi monolith

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

@RestController
@RequestMapping("/api/products")
public class ProductController {
    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.findById(id);
    }
}

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping
    public Order createOrder(@RequestBody OrderRequest request) {
        return orderService.create(request);
    }
}

Liat kan? Semua controller, service, dan repository ada di satu project yang sama. Deploy sekali, langsung jalan semuanya. Simple, straightforward, dan enak buat mulai.

Microservices: Banyak Aplikasi Kecil yang Saling Ngobrol

Microservices, di sisi lain, itu pendekatan di mana aplikasi lo dipecah jadi beberapa service kecil yang independen. Setiap service punya codebase sendiri, database sendiri, dan bisa di-deploy secara terpisah. Komunikasi antar service biasanya lewat HTTP API, message queue (kafka, RabbitMQ), atau gRPC.

# User Service (Flask) - berdiri sendiri
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = find_user_in_db(user_id)
    return jsonify(user)

if __name__ == '__main__':
    app.run(port=5001)
# Order Service (Flask) - juga berdiri sendiri, beda port, beda repo
from flask import Flask, jsonify, request
import requests

app = Flask(__name__)

@app.route('/api/orders', methods=['POST'])
def create_order():
    data = request.json
    # Panggil user service lewat HTTP
    user = requests.get(f"http://user-service:5001/api/users/{data['user_id']}")
    order = process_order(data, user.json())
    return jsonify(order)

if __name__ == '__main__':
    app.run(port=5002)

Perhatikan bedanya: Order Service memanggil User Service lewat HTTP request. Mereka nggak sharing database, nggak satu codebase. Masing-masing berdiri sendiri dan bisa dikembangkan oleh tim yang berbeda.


Kenapa Banyak yang Teriak “Microservices!” Tanpa Mikir Dua Kali?

Gue paham kenapa banyak tim (dan especially management) tertarik sama microservices. Kata-kata seperti “scalable”, “independent deployment”, “team autonomy” itu kedengeran keren banget. Dan memang, di kasus yang tepat, semua benefit itu beneran ada.

Tapi masalahnya, banyak yang loncat ke microservices bukan karena kebutuhan teknis, tapi karena tren. Ini yang gue sebut sebagai “Resume-Driven Development”—arsitektur dipilih biar keliatan keren di CV, bukan karena solve problem yang beneran ada.

Gue pernah di satu startup yang timnya cuma 5 orang backend developer. Mereka mutusin buat split jadi 12 microservices. Hasilnya? Tiap orang handle 2-3 service sendirian. Nggak ada yang bener-bener paham keseluruhan sistem. Deploy butuh koordinasi kayak mau perang. Satu service down, yang lain ikutan error. Tracing bug? Good luck with that.

Ironisnya, kalau mereka pake monolith dari awal, kemungkinan besar productnya udah launching 3 bulan lebih cepat dan jauh lebih stabil.

Tanda-Tanda Lo “Dipaksa” ke Microservices yang Sebenernya Nggak Perlu

  • Tim lo cuma 3-10 orang developer
  • Lo belum punya CI/CD pipeline yang solid
  • Lo belum punya monitoring dan observability yang matang
  • Traffic aplikasi lo masih bisa di-handle satu server
  • Lo belum paham cara handle distributed system challenges (network failure, data consistency, service discovery)

Kalau semua poin di atas jawabannya “ya”, mending stay di monolith dulu. Serius.


Kapan Sebenernya Monolith Itu Pilihan yang Tepat?

Oke, mari kita bahas kapan monolith itu beneran jadi pilihan yang masuk akal. Spoiler: lebih sering dari yang lo kira.

1. Lo Baru Mulai / Startup Early Stage

Di fase ini, yang paling penting itu speed to market. Lo butuh iterate cepet, pivot kalau perlu, dan validate ide bisnis lo. Monolith ngasih lo semua itu. Satu codebase, deploy gampang, debugging juga straightforward.

GitHub, Shopify, dan Basecamp—mereka semua mulai dari monolith. Shopify bahkan masih monolith (dengan modularisasi di dalamnya) sampe skala yang sangat besar.

2. Tim Lo Kecil

Kalau tim lo di bawah 15-20 orang, nggak ada alasan kuat buat split jadi microservices. Malah overhead-nya bakal lebih gede dari benefit-nya. Monolith nggak berarti lo nggak bisa bikin kode yang terstruktur kok. Lo tetep bisa bikin modul yang rapi di dalam monolith.

# Monolith tapi tetap rapi dengan modul yang jelas

# project/
# ├── modules/
# │   ├── user/
# │   │   ├── __init__.py
# │   │   ├── models.py
# │   │   ├── routes.py
# │   │   ├── services.py
# │   │   └── repository.py
# │   ├── product/
# │   │   ├── __init__.py
# │   │   ├── models.py
# │   │   ├── routes.py
# │   │   ├── services.py
# │   │   └── repository.py
# │   └── order/
# │       ├── __init__.py
# │       ├── models.py
# │       ├── routes.py
# │       ├── services.py
# │       └── repository.py
# ├── shared/
# │   ├── database.py
# │   ├── config.py
# │   └── middleware.py
# └── app.py

# modules/user/services.py
from modules.user.repository import UserRepository

class UserService:
    def __init__(self):
        self.repo = UserRepository()

    def get_user(self, user_id):
        return self.repo.find_by_id(user_id)

    def create_user(self, data):
        # business logic di sini
        return self.repo.create(data)

Liat? Masih monolith, tapi modular. Boundary antar modul jelas. Kalau suatu hari butuh split ke microservices, lo udah punya fondasi yang bagus.

3. Domain Lo Belum Terlalu Kompleks

Kalau aplikasi lo domain-nya relatif sederhana—misal CRUD-heavy, flow-nya linear, nggak ada kebutuhan teknologi yang berbeda-beda tiap modul—monolith udah lebih dari cukup.


Kapan Lo HARUS Pindah ke Microservices?

Nah, ini bagian yang lebih seru. Ada beberapa situasi di mana microservices beneran jadi jawaban yang tepat, dan bukan cuma sekadar tren.

1. Skala yang Beda-Beda Tiap Bagian

Misalnya lo punya platform e-commerce. Bagian “search” kena traffic 10x lebih banyak dari bagian “admin”. Di monolith, lo harus scale seluruh aplikasi cuma karena satu bagian yang butuh lebih banyak resource. Di microservices, lo bisa scale “search service” aja.

# docker-compose.yml - Scale individual service
version: '3.8'
services:
  search-service:
    image: myapp/search:latest
    deploy:
      replicas: 5  # Butuh banyak karena traffic tinggi
  
  admin-service:
    image: myapp/admin:latest
    deploy:
      replicas: 1  # Cukup satu, traffic rendah
  
  user-service:
    image: myapp/user:latest
    deploy:
      replicas: 2  # Sedang-sedang aja

2. Tim Lo Udah Gede dan Butuh Autonomy

Kalau lo udah punya 50+ developer yang dibagi jadi beberapa tim, microservices ngasih team autonomy. Tim A bisa deploy kapan aja tanpa nunggu Tim B. Tim A bisa pake Go, Tim B tetep pake Java. Nggak ada bottleneck di satu codebase.

3. Butuh Teknologi yang Berbeda-Beda

Mungkin bagian ML/AI lo lebih cocok pake Python, sementara API gateway lo lebih optimal pake Go, dan service yang CPU-intensive pake Rust. Di monolith, lo terbatas di satu tech stack. Microservices ngasih lo kebebasan itu.

// API Gateway di Go - karena butuh performa tinggi
package main

import (
    "encoding/json"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func createProxy(target string) *httputil.ReverseProxy {
    u, _ := url.Parse(target)
    return httputil.NewSingleHostReverseProxy(u)
}

func main() {
    http.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
        proxy := createProxy("http://user-service:5001")
        proxy.ServeHTTP(w, r)
    })

    http.HandleFunc("/api/predict/", func(w http.ResponseWriter, r *http.Request) {
        proxy := createProxy("http://ml-service:5003")
        proxy.ServeHTTP(w, r)
    })

    http.ListenAndServe(":8080", nil)
}

4. Butuh Resilience dan Fault Isolation

Di microservices, kalau satu service down, yang lain tetep bisa jalan (asalkan lo handle dengan benar—circuit breaker, retry, fallback). Di monolith, satu bug di satu modul bisa bikin seluruh aplikasi crash.

// Circuit breaker pattern dengan Resilience4j
@Service
public class OrderService {

    @CircuitBreaker(name = "inventoryService", fallbackMethod = "inventoryFallback")
    @Retry(name = "inventoryService")
    public OrderResponse createOrder(OrderRequest request) {
        // Panggil inventory service
        InventoryResponse inventory = inventoryClient.checkStock(request.getProductId());
        if (inventory.isAvailable()) {
            return processOrder(request);
        }
        throw new OutOfStockException("Product not available");
    }

    // Fallback kalau inventory service down
    public OrderResponse inventoryFallback(OrderRequest request, Throwable t) {
        // Queue order for later processing
        messageQueue.enqueue("order-pending", request);
        return new OrderResponse("Order queued. Will be processed when inventory service is back.");
    }
}

“Modular Monolith”: Best of Both Worlds?

Nah, ini yang menurut gue banyak orang overlook. Ada pendekatan tengah yang disebut modular monolith—arsitektur monolith tapi dengan boundary yang sangat jelas antar modul, sehingga kalau suatu hari butuh split ke microservices, transisinya jauh lebih smooth.

Konsepnya: lo tetep deploy satu aplikasi, tapi di dalamnya setiap modul punya boundary yang ketat. Module A nggak boleh langsung akses database Module B—harus lewat service layer atau interface.

// Modular monolith - module boundaries yang jelas

// order-module/src/main/java/com/app/order/OrderModule.java
@Module(requires = {UserModule.class, InventoryModule.class})
public class OrderModule {

    @Bean
    public OrderService orderService(
        OrderRepository orderRepo,
        UserGateway userGateway,        // Interface, bukan langsung akses
        InventoryGateway inventoryGateway // Interface, bukan langsung akses
    ) {
        return new OrderService(orderRepo, userGateway, inventoryGateway);
    }
}

// Abstraction layer antar module
public interface UserGateway {
    UserDTO getUser(Long userId);
}

// Implementasi untuk monolith (direct call)
@Component
@Profile("monolith")
public class LocalUserGateway implements UserGateway {
    @Autowired
    private UserService userService;

    @Override
    public UserDTO getUser(Long userId) {
        return userService.findById(userId); // direct call, same process
    }
}

// Implementasi untuk microservices (HTTP call)
@Component
@Profile("microservice")
public class RemoteUserGateway implements UserGateway {
    @Autowired
    private UserFeignClient userClient;

    @Override
    public UserDTO getUser(Long userId) {
        return userClient.getUser(userId); // HTTP call ke service lain
    }
}

Pendekatan ini powerful banget karena lo bisa mulai dari monolith, develop dengan modular pattern, dan kalau suatu hari butuh split, lo tinggal ganti implementasi gateway-nya dari “local call” jadi “remote call”. Nggak perlu rewrite dari nol.


Perbandingan Langsung: Monolith vs Microservices

Biar lebih jelas, gue bikin tabel perbandingannya:

AspekMonolithMicroservices
Kompleksitas AwalRendahTinggi
Speed to MarketCepatLambat (butuh setup infra)
DeploymentSatu pipelineBanyak pipeline
ScalingScale semuaScale per service
Team StructureBisa shared codebaseTim independen
Tech StackSatu stackBebas pilih tiap service
DebuggingRelatif mudahDistributed tracing needed
Data ConsistencyACID, gampangEventual consistency, tricky
Fault IsolationRendahTinggi (kalau di-handle bener)
Infrastructure CostRendahTinggi (butuh orchestration)
Cocok untukStartup, tim kecil, MVPEnterprise, tim besar, high scale

Tips dari Pengalaman Pribadi

Gue mau tutup dengan beberapa tips yang gue pelajari dari pengalaman pribadi, baik bikin monolith maupun microservices:

1. Mulai dari monolith, selalu. Bahkan kalau lo yakin bakal butuh microservices nanti, mulai dari monolith yang modular. Lo bakal lebih paham domain lo setelah beberapa bulan development, dan baru saat itu lo bisa bikin keputusan split yang informed.

2. Jangan split berdasarkan teknologi, split berdasarkan business domain. “Ini pake Node.js, itu pake Python, jadi pisah aja” itu alasan yang lemah. Split karena ada boundary bisnis yang jelas, bukan karena beda bahasa pemrograman.

3. Invest di observability dari hari pertama. Baik monolith maupun microservices, lo butuh logging, monitoring, dan tracing yang bagus. Kalau lo milih microservices, ini bukan optional—ini mandatory.

4. Komunikasi antar service itu tricky. Network bisa fail, latency bisa naik, data bisa inconsistency. Pastiin lo paham distributed system challenges sebelum ambil microservices.

5. Nggak ada yang permanen. Arsitektur bisa berubah. Yang hari ini monolith bisa jadi microservices besok, dan sebaliknya. Yang penting lo bikin kode yang clean dan modular, supaya perpindahannya nggak terlalu painful.


Kesimpulan

Kalau lo baca sampe sini, gue harap lo udah punya gambaran yang lebih jelas tentang kapan mesti pake monolith dan kapan mesti pake microservices. Singkatnya:

  • Pilih monolith kalau lo baru mulai, tim kecil, domain belum kompleks, dan lo butuh speed.
  • Pilih microservices kalau lo udah di skala besar, tim gede, butuh independent deployment, dan punya infra yang matang.
  • Pertimbangkan modular monolith sebagai jalan tengah yang paling masuk akal untuk banyak kasus.

Yang paling penting: pilih arsitektur berdasarkan kebutuhan, bukan berdasarkan tren. Lo nggak harus pake microservices biar keliatan keren. Monolith yang well-designed itu jauh lebih baik daripada microservices yang berantakan.


Ada Proyek yang Butuh Konsultasi Arsitektur?

Kalau lo lagi di titik di mana lo harus bikin keputusan arsitektur—atau bahkan lagi consider migrasi dari monolith ke microservices (atau sebaliknya)—dan butuh temen diskusi, jangan ragu buat reach out. Kadang ngobrol 30 menit sama orang yang udah pernah lewat jalan yang sama bisa hemat lo berminggu-minggu waktu.

📧 Email: [email protected]


FAQ

Apakah microservices selalu lebih scalable dari monolith?

Nggak juga. Monolith juga bisa di-scale secara horizontal—tinggal tambah instance di belakang load balancer. Yang bikin microservices unggul di scaling adalah kemampuan buat scale per komponen secara terpisah. Kalau seluruh aplikasi lo scale secara seragam, monolith masih sangat viable. Tapi kalau ada bagian yang butuh 10x resource dari bagian lain, baru microservices lebih efisien.

Apakah microservices bikin development lebih cepat?

Justru seringkali lebih lambat, terutama di awal. Lo butuh setup service discovery, API gateway, distributed tracing, CI/CD untuk tiap service, container orchestration, dan banyak lagi. Development lebih cepat itu terjadi di fase di mana tim lo udah besar dan butuh parallel development di banyak service. Untuk tim kecil, monolith hampir selalu lebih cepat.

Gimana cara migrasi dari monolith ke microservices tanpa rewrite total?

Pake pola yang disebut Strangler Fig Pattern. Caranya: identifikasi satu bounded context/modul yang paling cocok buat dipecah duluan. Buat service baru, redirect traffic secara bertahap (bisa lewat API gateway), dan matikan modul lama di monolith setelah service baru stabil. Ulangi proses ini untuk modul-modul lainnya. Jangan coba split semua sekaligus—itu resep buat bencana.

Apakah microservices butuh Kubernetes?

Nggak wajib, tapi hampir semua implementasi microservices di production skala besar pake Kubernetes atau orchestration tool sejenis (Docker Swarm, Nomad, ECS). Tanpa orchestration, manage ratusan container secara manual itu nggak realistis. Jadi kalau tim lo belum familiar sama container orchestration, itu salah satu pertimbangan penting sebelum milih microservices.

Kapan waktu yang tepat buat mulai mikirin microservices?

Beberapa sinyal yang bisa lo jadikan patokan: (1) deployment bottleneck karena terlalu banyak tim yang commit ke satu repo, (2) ada bagian aplikasi yang butuh scale berbeda jauh dari bagian lain, (3) lo butuh pake teknologi berbeda di bagian yang berbeda, (4) satu bug kecil di satu modul bisa bikin seluruh aplikasi down dan lo butuh fault isolation yang lebih baik. Kalau lo belum ngerasain semua itu, kemungkinan besar lo belum butuh microservices.