Memahami event-driven architecture (event broker, choreography vs orchestration/saga pattern), CQRS (command query responsibility segregation), dan outbox pattern untuk reliable event publishing yang mengatasi dual-write problem

Setelah di episode 15 kita memahami microservices architecture, pada episode ini kita masuk ke pola komunikasi yang powerful untuk microservices: event-driven architecture dan CQRS. Dua konsep ini menyelesaikan masalah fundamental: bagaimana menjaga service loose coupling saat ada banyak data yang harus dikonsistenikan lintas service?
Event-driven architecture mengubah cara kita berpikir tentang komunikasi: bukan "saya panggil kamu" (request-response), tapi "saya beri tahu semua orang tentang apa yang terjadi" (event). CQRS memisahkan model write dan read — memungkinkan optimasi terpisah untuk masing-masing.
OrderService.createOrder(order):
1. Save order ke database
2. Emit event: OrderCreated { orderId, userId, items }
3. Return success
NotificationListener.on(OrderCreated):
1. Kirim email "Order confirmed" ke user
InventoryListener.on(OrderCreated):
1. Kurangi stockKafka sebagai event broker memastikan:
| Aspek | Choreography | Orchestration |
|---|---|---|
| Pola | Setiap service merespons event secara independent | Satu orchestrator mengkoordinasi workflow |
| Coupling | Sangat loose | Tighter (orchestrator tahu semua step) |
| Visibility | Sulit melihat flow utuh | Mudah (orchestrator punya central logic) |
| Error handling | Setiap service handle sendiri | Orchestrator handle retry/compensation |
| Use case | Simple workflows | Complex workflows (saga) |
Saga adalah sequence transaksi lintas service dengan kompensasi (undo) jika ada kegagalan.
1. Order Service: create order → emit OrderCreated
2. Payment Service: process payment → emit PaymentProcessed
3. Inventory Service: reserve stock → emit StockReserved
4. Notification Service: send email → emit OrderConfirmed
Jika step 3 gagal:
3a. Inventory Service: emit StockReservationFailed
4a. Payment Service: refund (compensation)
5a. Order Service: update status ke "cancelled"Orchestrator (OrderSaga):
1. Create order
2. Process payment (retry 3x)
3. Reserve stock (retry 3x)
4. Send notification
Jika step 3 gagal:
→ Compensate step 2: refund payment
→ Compensate step 1: cancel orderPisahkan model write (command) dan read (query) — database terpisah untuk write dan read.
Write model: normalized (3NF) untuk data integrity
Read model: juga normalized → query lambat (JOIN banyak)
Solusi: CQRS
Write DB: normalized (PostgreSQL) → data integrity
Read DB: denormalized (Elasticsearch/Redis) → query cepatWrite side:
- Tidak simpan state langsung
- Simpan sequence of events (event store)
- State = replay semua events
Read side:
- Update read model dari events
- Read model = projection dari eventsEvent sourcing memberikan audit trail sempurna — setiap perubahan terekam sebagai event.
| Aspek | Kelebihan | Kekurangan |
|---|---|---|
| Read performance | Query cepat (denormalized) | Duplikasi data |
| Write performance | Write ke normalized DB | Eventual consistency untuk reads |
| Complexity | Flexible read model | Lebih banyak komponen |
| Debugging | Event store = audit trail | Harder untuk debugging state |
1. Save order ke DB
2. Publish event ke Kafka
Masalah: jika step 2 gagal (setelah step 1 sukses)
→ DB punya order baru, tapi Kafka tidak punya event
→ Consumer tidak tahu ada order baru1. Dalam SATU transaksi:
a. Save order ke DB
b. Save event ke outbox table di DB
2. Separate process (CDC/polling) baca outbox → publish ke Kafka
3. Hapus event dari outbox setelah publish sukses
Benefit: order dan event konsisten (same transaction)CREATE TABLE outbox (
id SERIAL PRIMARY KEY,
event_type VARCHAR(100),
payload JSONB,
created_at TIMESTAMP DEFAULT NOW(),
published BOOLEAN DEFAULT FALSE
);CDC (Change Data Capture) tools: Debezium, Maxwell, pg_notify.
Note
Outbox pattern mengatasi dual-write problem dengan menjamin atomicity antara data write dan event publish. Gunakan CDC (seperti Debezium) untuk mem-poll outbox table dan publish ke Kafka — lebih reliable dari polling manual.
Command: CreateOrder
1. Validate order (user exists, stock available)
2. Save order ke write DB (PostgreSQL)
3. Save event ke outbox (same transaction)
4. Return success
CDC Process:
5. Debezium detect outbox row
6. Publish event "OrderCreated" ke Kafka
7. Mark outbox as published
Query: GetUserOrders(userId)
1. Read dari read DB (denormalized, indexed)
2. Return cached/denormalized data (fast!)Inti yang harus dibawa pulang:
Di episode 17 selanjutnya kita akan membahas migrasi: monolith → modular monolith → microservices — Strangler Fig pattern, modular monolith sebagai starting point, dan kapan saatnya extract ke microservices. Migrasi arsitektur adalah perjalanan, bukan sekadar tujuan!