Event sourcing meminta Anda untuk berhenti menimpa data Anda. Dalam aplikasi CRUD tradisional, memperbarui alamat pengiriman pengguna berarti mencari baris tersebut, mengubah nilainya, dan membuang status sebelumnya. Event sourcing mengambil jalur yang berbeda. Ia menyimpan setiap perubahan sebagai fakta yang tidak dapat diubah (immutable): seorang pengguna membuat akun, memperbarui alamat mereka, memverifikasi email mereka. Status sistem saat ini tidak disimpan secara langsung. Status tersebut dihitung dengan memutar ulang (replaying) peristiwa-peristiwa ini secara berurutan.
Pola ini menyelesaikan masalah nyata. Jejak audit (audit trails) menjadi produk sampingan gratis. Anda dapat merekonstruksi status pesanan pada momen apa pun di masa lalu. Anda dapat melakukan debugging dengan memutar ulang tepat apa yang telah terjadi. Pertukarannya adalah kompleksitas. Anda kini mengelola aliran fakta, model baca (read models), dan konsistensi akhir (eventual consistency) alih-alih baris sederhana.
PostgreSQL dapat bertindak sebagai event store Anda. Sebagian besar tim sudah menjalankannya. Ia menawarkan transaksi ACID, JSONB untuk payload yang fleksibel, dan alat pencadangan yang terbukti. Anda tidak perlu memperkenalkan Kafka, Cassandra, atau database event-store khusus pada hari pertama. Instansi Postgres standar memberi Anda jaminan transaksional dan jejak audit yang diminta oleh event sourcing tanpa memperluas jejak infrastruktur Anda.
Bentuk Event Store Postgres
Skemanya bisa sangat sederhana hingga terasa memalukan. Minimal, Anda memerlukan tabel yang menambahkan (append) peristiwa dan tidak pernah memperbaruinya di tempat (in place). Desain praktisnya terlihat seperti ini:
idsebagai bigserial atau UUID, berfungsi sebagai pengurutan global.stream_iduntuk mengelompokkan peristiwa terkait, seperti semua perubahan untuk satu pengguna atau pesanan.event_typesebagai teks biasa:UserEmailChanged,PaymentReceived,InventoryAdjusted.payloadsebagai JSONB, menyimpan data spesifik untuk kejadian tersebut.occurred_atdengan presisi zona waktu.versionper stream, untuk menegakkan konkurensi optimistis (optimistic concurrency).
Anda menegakkan aturan imutabilitas dalam kode aplikasi atau dengan batasan (constraint) database. Indeks unik pada (stream_id, version) mencegah dua penulis menambahkan nomor urutan yang sama. Ketika sebuah perintah masuk, Anda membaca versi saat ini untuk stream tersebut, meningkatkannya, dan memasukkan peristiwa baru di dalam sebuah transaksi. Jika proses lain mendahului Anda, batasan unik tersebut akan gagal, dan Anda dapat mencoba lagi atau menolak perintah tersebut.
Pertimbangkan contoh konkret. Anda menjalankan sistem inventaris. Alih-alih satu baris inventory dengan kolom quantity, Anda menambahkan peristiwa ke tabel inventory_events. ItemReceived menambah sepuluh unit. ItemReserved mengurangi dua. ItemShipped mengurangi tiga. Untuk mengetahui stok saat ini untuk SKU-42, Anda menjumlahkan payload peristiwa yang relevan. Untuk mengetahui stok tiga hari yang lalu, Anda hanya menjumlahkan hingga timestamp tersebut. Jika bug dalam logika pengiriman Anda menyebabkan kesalahan pada Selasa lalu, Anda memutar ulang peristiwa tersebut melalui kode yang telah diperbaiki untuk mendapatkan status yang sebenarnya. Anda tidak dapat melakukan itu dengan pernyataan UPDATE sederhana.
Prinsip-Prinsip Agar Anda Terhindar dari Masalah
Membangun di atas Postgres tidak menghilangkan kebutuhan akan disiplin. Prinsip-prinsip berikut berlaku langsung untuk sistem berbasis event-sourcing.
Tetaplah sederhana. Kompleksitas membunuh keandalan. Tahan keinginan untuk membangun kerangka kerja (framework) event generik sebelum Anda merilis satu alur kerja yang berfungsi. Satu tabel, fungsi repositori untuk menambahkan peristiwa, dan worker proyeksi untuk membangun model baca sudah cukup untuk membuktikan nilai. Tambahkan alat hanya ketika masalah konkret muncul.
Mulailah dari yang kecil. Jangan menulis ulang seluruh monolit Anda. Pilih satu bounded context di mana jejak audit sebanding dengan biaya overhead-nya. Buku besar penagihan (billing ledger), mesin alur kerja (workflow engine), atau sistem reservasi inventaris adalah kandidat yang baik. Bangun satu jalur (pipeline) tersebut secara end-to-end. Biarkan berjalan di produksi. Kemudian putuskan apakah akan diperluas.
Tentukan kesuksesan terlebih dahulu. Event sourcing bukanlah arsitektur default; ia adalah solusi untuk kebutuhan spesifik. Jika kebutuhan Anda hanya untuk melacak status terbaru, CRUD lebih cepat dan lebih murah. Jika Anda memerlukan kueri temporal, auditabilitas yang ketat, atau kemampuan untuk membangun kembali model baca sesuai permintaan, maka penggunaan event masuk akal. Ketahuilah masalah mana yang sedang Anda selesaikan sebelum Anda berkomitmen.
Ukur sebelum Anda mengoptimalkan. PostgreSQL modern pada perangkat keras sederhana dapat menyerap ribuan peristiwa per detik dengan tabel append-only yang sederhana. Jangan melakukan sharding pada event store Anda atau memperkenalkan skema partisi yang kompleks sampai pemantauan Anda membuktikan bahwa Anda telah menghabiskan solusi yang lebih sederhana. Indeks bidang (field) yang Anda kueri. Atur autovacuum untuk beban kerja append-only. Kemudian ukur kembali.
Test everything. Unit test your event handlers. Integration test the append path. Most importantly, test failure scenarios. What happens when two nodes append to the same stream simultaneously? What happens when a projection worker crashes mid-batch? Write tests that verify your optimistic concurrency and your at-least-once delivery guarantees.
Monitor in production. The events table will grow. Unlike a normalized schema where updates keep row counts flat, event sourcing is intentionally additive. Track table size, disk I/O, and the lag between your write model and your read model projections. Set alerts on projection lag before your users notice stale data.
Automate manual tasks. Manual schema changes, manual projection rebuilds, and manual event replay are ticking time bombs. Script your migration strategy. If you evolve an event schema, automate the upcasting or transformation so that old events can be replayed through new logic without human intervention at midnight.
Document your choices. Write down why specific streams exist, what each event type means, and when the team should choose CRUD over events. Event sourcing introduces cognitive load. Good documentation prevents a new engineer from guessing wrong and appending malformed events to a critical stream.
Traps That Waste Months
Event sourcing has a way of sounding elegant in a diagram and painful in production. Watch for these traps.
Underestimating complexity. Replaying events to rebuild state is conceptually simple. Managing idempotency, snapshotting for performance, and compensating transactions across aggregates is not. Break your system into small pieces. Solve one stream at a time.
Over-engineering. Do not provision a multi-node Kafka cluster because you imagine your event volume will one day require it. Postgres can carry you surprisingly far. Introduce new infrastructure only when you have a measured bottleneck that you cannot fix within your current setup.
Ignoring technical debt. Old event schemas linger forever. If you change your OrderCreated payload, you still have ten million historical events in the old shape. Track this debt. Plan backward-compatible readers or migration scripts. Do not let the burden of legacy events slow every new feature.
Choosing tools the team cannot run. The best architecture fails if only one person understands it. If your team knows Postgres and SQL, start there. If you introduce a specialized event store, make sure you have the operational expertise to debug it at two in the morning.
A Practical Starting Point
If this approach fits your problem, do not wait for a five-quarter rewrite. Start this week.
Audit your current systems. Find a place where an audit trail would solve real pain. Maybe it is an order state machine that currently maintains a single status column. Perhaps it is a financial ledger where balance corrections require manual database patches. Pick one gap where overwriting state has hurt you.
Then pick one small improvement you can make today. Create one event table. Model one stream. Write one projection that builds a read model from those events. Deploy it behind a feature flag. Watch it handle real traffic.
Event sourcing with PostgreSQL is not magic. It is a practical tool for teams that need to know not just where things are, but how they got there. Build slowly, measure honestly, and let your actual requirements guide the architecture.
