Event sourcing meminta anda berhenti menulis semula data anda. Dalam aplikasi CRUD tradisional, mengemas kini alamat penghantaran pengguna bermakna mencari baris tersebut, mengubah nilainya, dan membuang keadaan sebelumnya. Event sourcing mengambil pendekatan yang berbeza. Ia menyimpan setiap perubahan sebagai fakta yang tidak boleh diubah (immutable): seorang pengguna mencipta akaun, mengemas kini alamat mereka, mengesahkan e-mel mereka. Keadaan semasa sistem tidak disimpan secara langsung. Ia dikira dengan memainkan semula (replaying) peristiwa-peristiwa ini mengikut urutan.
Corak ini menyelesaikan masalah sebenar. Jejak audit menjadi hasil sampingan percuma. Anda boleh membina semula keadaan pesanan pada bila-bila masa yang lalu. Anda boleh menyahpepijat (debug) dengan memainkan semula apa yang sebenarnya berlaku. Pertukaran (trade-off) yang perlu dihadapi ialah kerumitan. Anda kini menguruskan aliran fakta, model bacaan, dan konsistensi akhirnya (eventual consistency) berbanding baris yang ringkas.
PostgreSQL boleh bertindak sebagai stor peristiwa (event store) anda. Kebanyakan pasukan sudah pun menggunakannya. Ia menawarkan transaksi ACID, JSONB untuk payload yang fleksibel, dan alatan sandaran yang terbukti. Anda tidak perlu memperkenalkan Kafka, Cassandra, atau pangkalan data stor-peristiwa khusus pada hari pertama. Pemasangan Postgres standard memberikan jaminan transaksi dan jejak audit yang diperlukan oleh event sourcing tanpa meluaskan jejak infrastruktur anda.
Bentuk Stor Peristiwa Postgres
Skemanya boleh menjadi sangat ringkas. Sekurang-kurangnya, anda memerlukan jadual yang menambah (append) peristiwa dan tidak pernah mengemas kini mereka di tempat yang sama. Reka bentuk praktikal adalah seperti berikut:
idsebagai bigserial atau UUID, berfungsi sebagai susunan global.stream_iduntuk mengumpulkan peristiwa berkaitan, seperti semua perubahan untuk pengguna atau pesanan tunggal.event_typesebagai teks biasa:UserEmailChanged,PaymentReceived,InventoryAdjusted.payloadsebagai JSONB, menyimpan data khusus untuk kejadian tersebut.occurred_atdengan ketepatan zon masa.versionbagi setiap aliran, menguatkuasakan keconkurrenan optimistik (optimistic concurrency).
Anda menguatkuasakan peraturan ketidakterubahan (immutability) dalam kod aplikasi atau dengan kekangan pangkalan data. Indeks unik pada (stream_id, version) menghalang dua penulis daripada menambah nombor urutan yang sama. Apabila sesuatu arahan (command) masuk, anda membaca versi semasa untuk aliran tersebut, menambahnya, dan memasukkan peristiwa baharu di dalam satu transaksi. Jika proses lain mendahului anda, kekangan unik akan gagal, dan anda perlu mencuba semula atau menolak arahan tersebut.
Pertimbangkan satu contoh konkrit. Anda menjalankan sistem inventori. Berbanding satu baris inventory dengan lajur quantity, anda menambah peristiwa ke dalam jadual inventory_events. ItemReceived menambah sepuluh unit. ItemReserved menolak dua. ItemShipped menolak tiga. Untuk mengetahui stok semasa bagi SKU-42, anda menjumlahkan payload peristiwa yang berkaitan. Untuk mengetahui stok tiga hari yang lalu, anda hanya menjumlahkan sehingga cap masa tersebut. Jika pepijat dalam logik penghantaran anda menyebabkan ralat pada Selasa lalu, anda memainkan semula peristiwa tersebut melalui kod yang telah diperbetulkan untuk mendapatkan keadaan yang sebenar. Anda tidak boleh melakukannya dengan pernyataan UPDATE yang ringkas.
Prinsip-Prinsip Untuk Mengelakkan Masalah
Membina di atas Postgres tidak menghapuskan keperluan untuk disiplin. Prinsip-prinsip berikut terpakai secara langsung kepada sistem berasaskan event sourcing.
Kekalkan kesederhanaan. Kerumitan membunuh kebolehpercayaan. Lawan keinginan untuk membina rangka kerja peristiwa generik sebelum anda berjaya melancarkan satu aliran kerja yang berfungsi. Satu jadual, satu fungsi repositori untuk menambah peristiwa, dan satu pekerja unjuran (projection worker) untuk membina model bacaan sudah mencukupi untuk membuktikan nilai. Tambah alatan hanya apabila masalah konkrit muncul.
Mula secara kecil-kecilan. Jangan tulis semula keseluruhan monolit anda. Pilih satu konteks terikat (bounded context) di mana jejak audit dapat menampung kos overhead. Lejar pengebilan, enjin aliran kerja, atau sistem tempahan inventori adalah calon yang baik. Bina satu saluran (pipeline) tersebut dari awal hingga akhir. Biarkan ia berjalan dalam pengeluaran (production). Kemudian barulah buat keputusan sama ada ingin berkembang.
Tentukan kejayaan terlebih dahulu. Event sourcing bukanlah seni bina lalai (default); ia adalah penyelesaian kepada keperluan khusus. Jika keperluan anda hanyalah untuk menjejaki keadaan terkini, CRUD adalah lebih pantas dan murah. Jika anda memerlukan pertanyaan temporal (temporal queries), kebolehauditan yang ketat, atau keupayaan untuk membina semula model bacaan mengikut permintaan, maka penggunaan peristiwa adalah lebih masuk akal. Ketahui masalah mana yang anda selesaikan sebelum anda membuat komitmen.
Ukur sebelum anda mengoptimumkan. PostgreSQL moden pada perkakasan sederhana boleh menerima beribu-ribu peristiwa sesaat dengan jadual append-only yang ringkas. Jangan lakukan sharding pada stor peristiwa anda atau perkenalkan skema pensisihan (partitioning) yang kompleks sehingga pemantauan anda membuktikan bahawa anda telah menghabiskan semua penyelesaian yang lebih mudah. Indekskan medan yang anda cari. Laraskan autovacuum untuk beban kerja append-only. Kemudian ukur semula.
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.
