Desain Sistem yang Baik
(seangoedecke.com)- Desain sistem yang baik adalah bentuk yang tidak tampak rumit dan tidak menimbulkan masalah berarti dalam jangka waktu lama
- Bagian tersulit dalam desain sistem adalah menangani state, dan penting untuk mengurangi sebanyak mungkin jumlah komponen yang menyimpan state
- Database pada dasarnya adalah tempat state disimpan, sehingga diperlukan pendekatan yang berfokus pada desain skema dan pengindeksan, serta penghilangan bottleneck
- Caching, pemrosesan event, dan pekerjaan latar belakang perlu diperkenalkan dengan hati-hati demi performa dan kemudahan pemeliharaan, dan sebaiknya tidak digunakan secara berlebihan
- Daripada desain yang rumit, menggunakan komponen dan metodologi sederhana yang sudah cukup teruji dengan tepat adalah kunci untuk membangun sistem yang berkelanjutan dan stabil
Definisi desain sistem dan pendekatan secara keseluruhan
- Jika desain perangkat lunak adalah perakitan kode, maka desain sistem adalah proses menggabungkan berbagai layanan
- Komponen utama dalam desain sistem meliputi app server, database, cache, queue, event bus, proxy dan lain-lain
- Desain yang baik memunculkan respons seperti "tidak ada masalah khusus", "selesai lebih mudah dari perkiraan", atau "bagian ini tidak perlu terlalu dipikirkan"
- Sebaliknya, desain yang rumit dan mencolok bisa menyembunyikan masalah mendasar atau menunjukkan overengineering
- Daripada langsung menerapkan sistem yang kompleks sejak awal, lebih menguntungkan untuk berkembang secara bertahap dari struktur sederhana minimal yang bisa berjalan
Membedakan state dan stateless
- Bagian paling sulit dalam desain perangkat lunak adalah pengelolaan state
- Layanan yang tidak menyimpan informasi dan langsung mengembalikan hasil (seperti rendering PDF GitHub) bersifat stateless
- Sebaliknya, layanan yang melakukan penulisan ke database mengelola state
- Sebaiknya kurangi sebanyak mungkin komponen penyimpan state dalam sistem. Ini menurunkan kompleksitas sistem dan kemungkinan terjadinya gangguan
- Struktur yang disarankan adalah hanya satu layanan yang menangani state, sementara layanan lain berfokus pada peran stateless seperti pemanggilan API atau pemicu event
Desain database dan titik bottleneck
Desain skema dan indeks
- Untuk penyimpanan data, diperlukan desain skema yang mudah dibaca manusia
- Skema yang terlalu fleksibel (misalnya menyimpan semuanya dalam kolom JSON) bisa membebani kode aplikasi dan performa
- Indeks yang tepat perlu ditetapkan berdasarkan kolom yang akan sering di-query. Memberi indeks pada semuanya justru menimbulkan overhead yang tidak perlu
Cara mengatasi bottleneck
- Akses database sering menjadi bottleneck yang berat
- Sebisa mungkin, data yang kompleks lebih menguntungkan diproses di dalam database dengan JOIN dan sejenisnya daripada di aplikasi
- Saat menggunakan ORM, perlu berhati-hati agar tidak membuat query di dalam loop
- Jika perlu, memecah query untuk mengatur beban database atau kompleksitas query juga bisa menjadi salah satu cara
- Strategi mendistribusikan query baca ke read-replica efektif untuk mengurangi beban node utama (Write)
- Ketika query dalam jumlah besar datang sekaligus, transaksi dan operasi tulis dapat dengan mudah membuat database kelebihan beban, sehingga perlu mempertimbangkan query throttling (pembatasan)
Memisahkan pekerjaan lambat dan pekerjaan cepat
- Pekerjaan yang berinteraksi dengan pengguna perlu merespons dalam ratusan milidetik
- Untuk pekerjaan yang memakan waktu lama (misalnya konversi PDF berukuran besar), pola yang efektif adalah memberikan hanya pekerjaan minimum secara langsung di frontend dan menyerahkan sisanya ke latar belakang
- Pekerjaan latar belakang umumnya berjalan dengan kombinasi queue (misalnya Redis) dan job runner
- Untuk pekerjaan yang dijadwalkan jauh di masa depan, bentuk yang praktis adalah mengelolanya lewat tabel DB terpisah alih-alih Redis, lalu mengeksekusinya dengan scheduler
Caching
- Caching membantu mengurangi biaya dan meningkatkan performa ketika operasi yang sama atau mahal diulang berkali-kali
- Biasanya engineer junior yang baru belajar cache ingin men-cache semuanya, sedangkan engineer berpengalaman lebih berhati-hati dalam memperkenalkan cache
- Cache memperkenalkan state baru, sehingga ada risiko isu sinkronisasi/error/stale data
- Lebih baik terlebih dahulu mencoba peningkatan performa seperti menambahkan indeks pada query, lalu menerapkan caching
- Untuk cache berkapasitas besar, selain Redis/Memcached, pendekatan menyimpan secara berkala ke document storage seperti S3/Azure Blob Storage juga dapat digunakan
Pemrosesan event
- Sebagian besar perusahaan memiliki event hub (misalnya Kafka), dan berbagai layanan diproses secara terdistribusi berbasis event
- Daripada menggunakan event secara berlebihan, desain API request–response yang sederhana lebih berguna untuk logging dan pemecahan masalah
- Pemrosesan berbasis event cocok ketika pengirim tidak perlu memedulikan perilaku penerima, atau untuk skenario berkapasitas tinggi dan toleran terhadap latensi
Cara penyampaian data: push dan pull
- Dalam penyampaian data ada dua cara: Pull (request lalu response) dan Push (dikirim otomatis saat ada perubahan)
- Metode Pull sederhana, tetapi bisa menimbulkan masalah permintaan berulang dan overload
- Metode Push langsung mengirim data ke klien saat ada perubahan di server, sehingga lebih efisien dan menguntungkan untuk menjaga data tetap terbaru
- Untuk menangani klien dalam jumlah besar, infrastruktur (queue event, beberapa server cache, dan sebagainya) perlu diperluas sesuai karakteristik masing-masing metode
Fokus pada hot path
- Hot path berarti jalur paling penting dalam sistem dan tempat data mengalir paling banyak
- Hot path memiliki sedikit pilihan, dan jika desainnya gagal dapat menimbulkan masalah serius pada seluruh layanan, sehingga desain yang hati-hati sangat penting
- Daripada mengalokasikan sumber daya pada fitur minor dengan banyak opsi, lebih efektif untuk memusatkan desain dan pengujian pada hot path
Logging, metrik, dan pelacakan
- Untuk mendiagnosis penyebab saat gangguan terjadi, perlu secara aktif mencatat log terperinci untuk jalur abnormal (unhappy path)
- Diperlukan pengumpulan metrik observabilitas dasar seperti resource sistem (CPU/memori), ukuran queue, serta waktu request/pekerjaan
- Alih-alih hanya melihat nilai rata-rata, metrik distribusi seperti latensi p95 dan p99 juga wajib diamati. Sejumlah kecil request yang lambat di bagian atas distribusi bisa menjadi masalah bagi pengguna utama
Kill switch, retry, dan pemulihan gangguan
- Penggunaan kill switch (penghentian sementara sistem) dan strategi retry secara tepat sangat penting
- Retry tanpa arah hanya akan membebani layanan lain, sehingga lebih efektif jika request terlebih dahulu dikendalikan dengan circuit breaker dan mekanisme serupa
- Dengan menerapkan Idempotency Key, kita dapat mencegah pekerjaan duplikat saat request yang sama diproses ulang
- Dalam beberapa situasi gangguan, perlu memilih antara fail open atau fail closed. Misalnya, untuk rate limiting, fail open (mengizinkan) memberi dampak lebih kecil bagi pengguna. Sebaliknya, autentikasi wajib fail closed
Penutup
- Beberapa topik seperti pemisahan layanan, container, penerapan VM, dan tracing memang dihilangkan, tetapi menggunakan komponen yang sudah terbukti baik di tempat yang tepat pada akhirnya menghasilkan pembangunan sistem yang paling stabil dalam jangka panjang
- Desain yang secara teknis terasa istimewa sebenarnya sangat jarang, dan desain yang sederhana sampai terasa membosankan justru paling sering digunakan dalam praktik
- Pada dasarnya, desain sistem yang baik adalah proses menggabungkan metodologi yang tidak mencolok, aman, dan sudah cukup terbukti
1 komentar
Komentar Hacker News
Saya sering merasa sendirian dalam hal ini. Saat para engineer melihat sistem yang kompleks, ada banyak elemen menarik sehingga mereka berpikir, “di sinilah desain sistem yang sesungguhnya terjadi!”, padahal sistem yang kompleks sering kali justru merupakan hasil dari tidak adanya desain yang baik. Jika kamu sedang mencari kerja, fakta ini harus benar-benar kamu lupakan saat wawancara. Saya juga pernah membuat kesalahan dengan menyampaikan pemikiran ini secara jujur dalam wawancara desain sistem. Dalam wawancara aplikasi startup hipotetis, saya menjawab hal-hal seperti “pada QPS sebesar ini, backpressure bisa diabaikan”, “tidak perlu memakai queue alih-alih cron job, meskipun tentu ada trade-off”, “SQL vs NoSQL? Pakai saja yang paling dikuasai tim”, tetapi para pewawancara tidak menginginkan jawaban seperti itu. Kamu harus memenuhi papan tulis putih dan menunjukkan desain yang cukup kompleks sampai tingkat Kubernetes mengelola Kubernetes agar memberi sinyal yang mereka inginkan
Saya mengatakan ini sebagai orang yang telah menjalani ratusan wawancara desain sistem dan melatih banyak orang. Jawaban yang kamu sebutkan memberi sinyal yang lemah (kecuali jawaban soal queue); yang benar-benar ingin diketahui pewawancara adalah ‘mengapa’ kamu membuat keputusan seperti itu, faktor apa yang kamu pertimbangkan, dan mereka ingin mendengar proses berpikirmu. Jika kamu tidak menjelaskan jawabanmu secara rinci, dari sudut pandang pewawancara akan mudah muncul kesan “tidak banyak informasi yang bisa didapat”. Karena itu kandidat perlu secara aktif menyampaikan informasi yang diinginkan pewawancara. Bahkan pewawancara yang baik pun, jika harus memaksa jawaban keluar, akan mencatat “penjelasannya masuk akal tetapi komunikasinya tidak efisien”. Keterampilan komunikasi juga dinilai. Terakhir, saya tidak setuju dengan jawaban SQL/NoSQL. Pengalaman tim memang penting, tetapi perbedaan antar teknologi jelas ada, dan tergantung situasinya perbedaan performanya juga besar. Jawaban itu memberi kesan kurang punya pengalaman dalam berbagai situasi
Seperti ungkapan “wawancara itu dua arah”, menurut saya jawabanmu sangat masuk akal. Jika saya yang menjadi pewawancara, justru saya akan memberi nilai tinggi. Sebaliknya, jika ada perusahaan yang menggugurkanmu karena jawaban seperti itu, kemungkinan besar perusahaannya memang kurang bagus. Namun secara realistis, sering kali kita perlu segera mendapat posisi, jadi perlu juga menjaga keseimbangan dan menyesuaikan jawaban ke arah yang ingin didengar lawan bicara
Saran ini tidak bagus. Desain yang sederhana namun elegan tidak dimulai dari mengabaikan potensi masalah. Pertanyaan lanjutan bukan waktu untuk memuntahkan trivia teknis, melainkan sinyal untuk berdiskusi bersama. Jawabanmu tidak menunjukkan kebijaksanaan, dan malah memberi kesan bahwa kamu masih belum matang. Ini bukan salah pewawancara
Saya setuju dengan poin dari komentar sebelah bahwa “wawancara itu dua arah”, tetapi pewawancara yang baik akan jujur mengatakan, “jawaban ini juga bagus, tetapi saat ini saya sedang menguji pengetahuan pada tema ini”. Kalau orang itu terus membicarakan hal yang melenceng, itu justru sinyal yang mengkhawatirkan
Menurut saya ini contoh yang menunjukkan persis mengapa LinkedIn-driven development itu ada. Kenyataannya, mencantumkan banyak teknologi di CV terlihat jauh lebih keren daripada menjelaskan bahwa kamu hanya menggunakan satu Postgres dan modular monolith dengan baik
Menurut saya ini tulisan yang sangat bagus. Namun saya juga ingin menyebutkan keterbatasan dari best practice seperti ini. Misalnya, ada saran “jangan biarkan 5 layanan berbeda menulis ke satu tabel; biarkan 4 melakukan panggilan API atau melempar event, dan hanya 1 layanan yang menulis ke tabel”. Kenyataannya tidak selalu terbagi serapi itu. Jika kelimanya mengakses DB, pada dasarnya kamu memang sedang membuat sistem terdistribusi, tetapi karena DB secara bawaan mendukung otorisasi, transaksi, dan kueri kustom, kadang tidak perlu mendesain antarmuka terpisah. Sebaliknya, jika membuat antarmuka tingkat tinggi lewat satu layanan, sekarang kamu harus mengimplementasikan sendiri autentikasi, transaksi, dan penanganan pengecualian. Jadi pertanyaannya, bukankah pada praktiknya ini malah menambah mode kegagalan dan pajak operasional microservice yang lebih kompleks? Di sisi lain, beberapa layanan yang mengakses satu DB itu sendiri mungkin memang code smell. Mungkin DB ini adalah jejak dari beberapa DB yang digabung, dan mungkin sebenarnya layanannya pun bisa dikurangi menjadi dua atau tiga
Untuk pertanyaan “apa yang didapat”, API jauh lebih adaptif terhadap perubahan dibanding memakai skema DB bersama. Setelah bekerja di berbagai sistem, saya tidak akan mendesain ulang struktur di mana banyak layanan berbagi satu DB. Mungkin itu masih bisa diterima di perusahaan kecil awal 2000-an, tetapi setelah itu saya hanya melihat kegagalan (kecuali kasus di mana dalam layanan yang sama hanya jalur baca/tulisnya yang dipisah)
Saya tidak setuju dengan klaim bahwa karena DB adalah antarmuka, maka tidak perlu desain terpisah. Jika banyak klien memakai DB yang sama, pola aksesnya berbeda-beda dan masalah migrasinya juga membesar. Pada akhirnya tetap perlu desain tambahan seperti view dan manajemen izin, dan beban pemeliharaannya pun meningkat. Dalam situasi ideal, API jauh lebih rapi. Realitanya, karena tekanan untuk merilis fitur dengan cepat, orang mengizinkan akses langsung ke DB sebagai jalan pintas, tetapi akar masalahnya adalah banyak orang enggan mendesain ulang keseluruhan agar sesuai dengan kebutuhan atau desain baru
Saat perubahan dibutuhkan, tujuannya adalah meminimalkan cakupan koordinasi yang terkait. Ketika struktur datastore harus diubah, kamu harus mengendalikan semua bagian yang mengakses datastore itu, jadi semakin sedikit jalur akses, semakin mudah perubahannya. Sebagai contoh, di pekerjaan nyata saat DB dipisahkan, lebih dari 40 tim harus memperbaiki kodenya. Kalau perubahan sebesar ini terjadi karena “permintaan fitur”, ya masih begitu. Kalau penyebabnya isu “skalabilitas”, produknya sendiri bisa rusak total
Kamu menyebut struktur banyak layanan ke satu DB sebagai “code smell”, tetapi sebaliknya jika setiap layanan harus diberi DB fisik terpisah, ketersediaannya bisa bertambah dari N menjadi N pangkat M sehingga dalam praktiknya justru bisa menjadi lebih tidak stabil (jika kita berbicara pada level klaster DB)
Saat mengueri database, yang paling efisien memang benar-benar mengueri DB. Jika kamu butuh data dari beberapa tabel, sebaiknya gunakan join daripada mengueri masing-masing di aplikasi lalu menggabungkannya. Dan saya juga sangat merekomendasikan view atau bahkan stored procedure. View adalah lapisan abstraksi data, jadi sangat membantu desain, dan kode SQL pun jika ditulis dengan baik mudah dipahami serta mudah dipelihara
Karena poin inilah ORM menimbulkan banyak masalah. Menggunakan SQL view/custom query secara langsung di tiap MVC view dalam lingkungan SSR adalah cara membuat layanan web besar menjadi efisien dan elegan. Serahkan pekerjaan berat kepada RDBMS, lalu web server tinggal meneruskan hasil SQL ke tabel. RDBMS lawas seperti MSSQL dan Oracle punya sangat banyak optimisasi bawaan. Sebaliknya, ORM memaksakan satu model objek sehingga nyaris tidak fleksibel
Stored procedure memang tampak berguna, tetapi dalam praktiknya, karena keterbatasan bahasa (T-SQL dan semacamnya), sulit menyatukan pengembangan ke bahasa modern yang familier bagi seluruh anggota tim. Saya sedang memelihara codebase T-SQL yang besar; version control dan tool diagnosis-nya juga kurang bagus. Kode dari karyawan baru masih bisa dibaca, tetapi T-SQL sendiri adalah mimpi buruk
Saya tidak setuju. Dalam arsitektur modern yang berorientasi skalabilitas, lebih baik melakukan join di backend di depan DB. Jika DB hanya menangani pencarian indeks sederhana dan join dilakukan di backend, skalabilitas DB menjadi lebih baik dan kecepatannya juga lebih tinggi. Menambah instance server lebih mudah daripada memperbesar DB. Jika join memang melibatkan data yang sedemikian besar hingga harus dilakukan di DB, barulah strukturnya perlu diubah. Jika join bahkan bisa dipindahkan ke frontend, cache hasil pun jadi lebih menguntungkan
Benarkah begitu? Misalnya ada 10 ribu pelanggan dan 1 juta pesanan; kalau semua tabel pelanggan 20 field + pesanan 5 field dijoin lalu dikirim, hasilnya 25 juta field yang ditransfer. Kalau diambil terpisah dengan dua query lalu dijoin, jadinya 5 juta field pesanan + 200 ribu field pelanggan. Dari sisi bandwidth dan performa, ini jauh lebih baik
Aturan ini bagus sebagai titik awal, tetapi kamu harus paham kapan pengecualian dibutuhkan. Aplikasi yang pernah saya tangani punya struktur di mana record membengkak secara eksponensial karena join. Jadi setelah query dipisah, keuntungan dari pemrosesan/filter hasil ternyata jauh lebih besar daripada overhead jaringan, sehingga performanya jauh lebih cepat. Belakangan kami bahkan mengubahnya menjadi struktur yang menyimpan semua data sebagai JSONB, dan hasilnya justru lebih baik lagi
Saya merasa disayangkan bahwa saat membahas desain sistem yang baik, domain masalahnya justru tidak disebut sama sekali. Hal paling inti sekaligus paling sulit dalam desain sistem adalah antarmuka yang diberikan sistem kepada pengguna. Pada akhirnya sistem perangkat lunak adalah pertukaran masalah seperti “saya akan menyediakan fungsi ini, tetapi sebagai gantinya kamu harus memahami struktur/model seperti ini”. Kesalahan dalam desain antarmuka adalah biaya terbesar, dan jika sebagian besar waktu tidak dihabiskan untuk membahas antarmuka, berarti kita sedang melewatkan hal yang paling penting. Elemen sistem lainnya nanti bisa diperbaiki sesuka hati tanpa menyentuh pengguna
Saya sangat merasakan kalimat “desain yang baik tidak menonjolkan diri, dan desain yang buruk justru tampak lebih meyakinkan”. Tampaknya evaluasi terhadap teknisi dilakukan dengan ukuran “kompleksitas”, sehingga mendorong overengineering. Prinsip KISS sudah terlalu lama tidak cukup dihargai
Kadang saya melihat kembali bagian-bagian dalam codebase yang begitu saja saya lewati tanpa banyak berpikir, dan justru itu adalah jejak desain yang baik
Ini sayangnya benar. Kebanyakan orang lebih tertarik pada solusi yang rumit, dan kalau kamu memberi jawaban sederhana, kesannya bisa terlihat tidak kompeten. Tetapi secara realistis, struktur sederhana yang mudah dikelola jauh lebih berkontribusi pada keberhasilan keseluruhan proyek. Tentu ada masalah yang memang tak terelakkan kompleks, tetapi sebagian besar hanyalah web app biasa
Hal terpenting dalam desain skema adalah fleksibilitas. Begitu data menumpuk, mengubah skema menjadi sangat sulit. Namun jika didesain terlalu fleksibel (memasukkan semua data ke JSON atau ke struktur EAV!), kode aplikasi menjadi sangat kompleks dan masalah performa aneh pun bertambah. Karena itu biasanya saya lebih suka skema yang mudah dibaca manusia, sampai hanya dengan melihat struktur tabelnya saja kita bisa langsung paham untuk apa gunanya. Saat terlalu sering melihat EAV, kolom/tabel JSON, rasanya benar-benar ingin berhenti jadi developer. Memang jelas ada kasus di mana EAV berguna, tetapi dalam kebanyakan kasus hanya membawa kekacauan ke lapangan. Masalah N+1, pembangkitan query dinamis, pola menyimpan data audit di DB yang sama hingga akhirnya terserap ke logika bisnis, lingkungan Oracle yang kompleks, serta desain yang salah memisahkan apa yang harus dimasukkan ke DB dan apa yang dibiarkan di aplikasi, masing-masing variabel seperti ini sangat menggerus kualitas hidup developer
Terkait hal ini, buku Bill Karwin “SQL Antipatterns” menjelaskan dengan baik bahaya dan keterbatasan pola EAV. Meski begitu, kadang saat sulit menggambar skema (misalnya dengan kolom JSONB di Postgres) ini bisa dipakai sebagai solusi sementara, tetapi tidak bisa menjadi aturan teladan. Jika normalisasi memungkinkan, selalu lebih baik memilih normalisasi
Soal “kalau data audit disimpan di DB yang sama, akhirnya itu menjadi bagian dari logika bisnis dan menyulitkan”, saya penasaran apa “cara bakunya”. DB terpisah? Storage yang benar-benar independen?
Terkait saran “hindari 5 layanan menulis ke tabel yang sama; biarkan 4 hanya memanggil API atau melempar event, dan hanya 1 yang menulis langsung ke DB”, yang terbaik adalah sejak awal punya struktur di mana 5 layanan itu memang tidak perlu menulis ke tabel yang sama. Jika mereka menulis ke sana, mungkin logika antar layanan sebenarnya sangat tumpang tindih. Maka perlu dipikirkan apakah 5 layanan itu benar-benar harus berbeda semua, atau justru bisa digabung jadi satu. Dalam praktiknya, memberi tabel data terpisah untuk masing-masing atau menyelesaikan masalah lewat refactoring justru bisa menjadi jawaban
Pembedaan stateful/stateless adalah inti pembagian tanggung jawab antara infrastruktur dan pengembangan. Saat dijalankan secara stateless dengan container, tidak banyak hal yang bisa salah, jadi kalau gagal tinggal deploy ulang saja. Asalkan menghindari kesalahan DB yang sampai merusak dataset, sebagian besar bisa dipulihkan cepat. Sampai tahap ini, orang dengan pengalaman karier, waktu, dan tingkat ketelitian yang beragam masih bisa menanganinya. Sebaliknya, area yang memiliki state seperti database, file storage, dan sejenisnya benar-benar berbeda. Satu kesalahan saja bisa membahayakan seluruh bisnis, jadi harus ditangani personel khusus yang kaya pengalaman praktis. Bahkan jika DB berjalan tanpa masalah, kalau tidak ada backup itu sendiri sudah merupakan risiko besar. Dalam kenyataannya, ini adalah area masalah yang tidak bisa diselesaikan hanya dengan deploy dalam beberapa menit
Tentang saran “tandai dengan timestamp alih-alih bool”, saya merasa itu terlalu umum sebagai pedoman. Misalnya is_on → true, on_at → 1023030 memang jelas, tetapi is_a_bear → true, a_bear_at → 12312231231 terasa aneh. Kebanyakan beruang tidak “menjadi beruang” pada suatu waktu tertentu... jadi ini hanya cocok untuk situasi tertentu
Menurut saya dalam hampir semua kasus, lebih baik memakai timestamp atau integer daripada boolean. Terutama karena field yang hanya punya dua status sering kali berkembang menjadi “klasifikasi tipe”. Misalnya walaupun sekarang hanya ada beruang, lebih baik mengantisipasi kemungkinan perluasan dengan enum type, dan field status juga sering berkembang dari sekadar aktif/nonaktif menjadi beragam status lain (berhenti, dihapus, dijeda, dll.), sehingga boolean justru makin menambah kompleksitas. Integer lebih baik
Jika mengikuti proposisi itu apa adanya, artinya memakai boolean di DB itu sendiri adalah smell, dan saya setuju. Hanya saja pendekatan seperti ini (mengganti bool → timestamp) sering kali lebih merupakan kemudahan untuk join, bukan “solusi tuntas”. Jika perubahan real-time penting, sejak awal audit table lebih tepat. Soft delete juga menurut saya solusi yang setengah-setengah. Maksud sebenarnya adalah mencegah penghapusan, padahal perlindungan yang lebih efektif bisa didapat lewat backup dan restore
Tipe boolean memiliki ukuran data yang lebih kecil, jadi pada sebagian workload (misalnya data besar untuk analitik) ia efisien. Terkadang memang secara logis tepat menyimpan nilai boolean. Misalnya hasil proses (penanda sukses/gagal) praktis jika berupa boolean
Saya juga ragu apakah hanya boolean saja yang perlu diubah menjadi timestamp. isDarkTheme, paginationItems, dan lain-lain pun bisa saja perlu diketahui kapan berubah. Rasanya ini semacam poor-man changelog
Kalau kasusnya seperti itu, lebih baik pakai nilai enum seperti Bear
Jika mencari buku untuk belajar tentang desain sistem yang baik dari sudut pandang yang lebih abstrak, saya sangat merekomendasikan John Gall, Systemantics. Menurut saya ini bacaan wajib bagi engineer