Kata Pengantar
Ada satu pertanyaan yang selalu muncul di ruang rapat, di kolom komentar, dan di kepala setiap orang yang hendak membangun perangkat lunak: SaaS atau on-premise? Pertanyaan itu terdengar seperti pilihan hidup-atau-mati, seolah salah pilih berarti proyek gagal. Buku ini hadir untuk menurunkan tensi perdebatan itu dan menggantinya dengan sesuatu yang lebih berguna: sebuah rumus.
Judulnya sengaja jenaka — "Hybrid Aja Deh" — karena begitulah jawaban yang paling sering benar dalam praktik. Bukan karena hybrid selalu superior secara teknis, melainkan karena, ketika tujuan Anda adalah bikin aplikasi dengan cepat, model penyebaran seharusnya menjadi keputusan terakhir, bukan pertama. Yang menentukan kecepatan bukanlah pilihan cloud versus server klien, melainkan bagaimana kode Anda ditata. Dan di situlah Modular Monolith masuk sebagai bintang utama buku ini.
Buku ini punya dua pembaca yang setara pentingnya. Yang pertama adalah Anda, manusia — developer solo, tim kecil, founder teknis, atau vibe-coder yang ingin mengubah ide jadi produk sebelum semangatnya padam. Yang kedua adalah agent AI — asisten pemrograman yang Anda perintah untuk menuliskan kode. Keduanya butuh hal yang sama: langkah eksplisit, batas yang jelas, dan resep yang bisa dieksekusi bertahap tanpa menebak-nebak. Itulah kenapa setiap bab ditulis dalam HTML semantik dengan heading terstruktur, daftar bernomor, dan blok kode yang bisa disalin.
Organisasi yang merancang sistem akan menghasilkan desain yang menyalin struktur komunikasi organisasi itu sendiri.
Melvin E. Conway, 1967 — "Conway's Law"
Conway mengingatkan bahwa arsitektur perangkat lunak mencerminkan cara tim berkomunikasi. Jika Anda tim satu orang — atau satu orang plus satu agent AI — maka arsitektur yang paling jujur bagi Anda bukanlah dua belas microservices yang saling memanggil lewat jaringan, melainkan satu monolit yang tertata rapi ke dalam modul-modul. Buku ini akan membela posisi itu habis-habisan, sambil tetap jujur tentang kapan Anda perlu keluar darinya.
Cara Membaca Buku Ini
Buku ini bisa dibaca lurus dari depan ke belakang, tetapi ia dirancang agar bisa juga dilompati. Setiap bab berdiri cukup mandiri sehingga Anda — atau agent Anda — dapat masuk langsung ke resep yang dibutuhkan.
Peta empat bagian
- Bagian I — Fondasi Arsitektur. Definisi SaaS, on-premise, hybrid, dan Modular Monolith. Baca ini agar keputusan di bab-bab berikutnya masuk akal.
- Bagian II — Pabrik Aplikasi. Inti buku: rumus "1 app semalam", rumus "30 app sebulan", Generator Blueprint interaktif, dan katalog modul siap pakai.
- Bagian III — Operasi. Lisensi, deployment, keamanan, dan data — hal-hal yang membedakan mainan dari produk.
- Bagian IV — Penerapan. Studi kasus, panduan agar buku ini dieksekusi AI, penutup, glosarium, checklist, dan FAQ.
Konvensi penanda
Berisi jalan pintas atau praktik terbaik yang mempercepat pekerjaan.
Menandai jebakan umum — biasanya sumber over-engineering atau utang teknis.
Aturan padat yang bisa dihafal atau ditempel di dinding.
Instruksi yang ditujukan langsung kepada asisten AI: prasyarat, urutan, dan kriteria selesai untuk satu langkah.
Use case sempit × stack tetap × modul siap pakai × Modular Monolith = 1 aplikasi per malam. Ulangi resep yang sama dengan template yang di-reuse = ~30 aplikasi per bulan. Model penyebaran (SaaS / On-Prem / Hybrid) ditentukan terakhir, sesuai kebutuhan pelanggan, bukan selera.
Tujuh prinsip yang menyatukan buku ini
Seluruh bab tunduk pada tujuh prinsip berikut. Bila suatu saran terasa bertentangan dengan salah satunya, prinsip yang menang.
- Scope sempit menang. Aplikasi yang melakukan satu hal dengan baik selesai dalam semalam; yang melakukan sepuluh hal tak pernah selesai.
- Stack tetap. Keakraban mengalahkan kebaruan. Satu stack, dikuasai, dipakai berulang.
- Modul berbatas. Setiap domain di
modules/<domain>dengan kontrak publik tunggal. - Reuse di atas rewrite. Template & modul siap pakai adalah aset; menulis ulang adalah utang.
- Tunda penyebaran. SaaS/On-Prem/Hybrid adalah saklar config, bukan fondasi.
- Aman secara default. Kesalahan keamanan dibuat mustahil di lapisan akses, bukan sekadar dilarang.
- Tolak over-engineering. Tak membangun untuk kebutuhan yang belum ada.
Perlakukan buku ini sebagai basis pengetahuan yang dapat dieksekusi. Tiap bab punya id anchor (mis. #bab-6); tiap resep adalah daftar bernomor dengan kriteria selesai; tiap blok kode bertanda bahasa. Mulai dari Generator (Bab 6) untuk spesifikasi, ikuti kontrak eksekusi (Bab 13), dan patuhi tujuh prinsip di atas sepanjang pengerjaan.
Fondasi Arsitektur
Empat bab pertama membangun kosakata bersama. Kita bedah tiga model penyebaran — SaaS, on-premise, hybrid — apa adanya, lalu memperkenalkan pola kode yang membuat ketiganya bisa dijalankan dari satu basis kode yang sama: Modular Monolith. Setelah bagian ini, Anda tidak lagi memilih model berdasarkan tren, melainkan berdasarkan kebutuhan.
SaaS vs On-Premise vs Hybrid
Sebelum menulis satu baris kode pun, kita perlu sepakat soal istilah. Tiga kata ini — SaaS, on-premise, hybrid — sering dilempar seolah maknanya jelas, padahal banyak proyek tersesat justru karena definisinya kabur. Mari kita luruskan dengan bahasa yang paling awam.
Tiga model dalam bahasa sehari-hari
SaaS (Software as a Service) adalah perangkat lunak yang Anda sewa, bukan Anda miliki. Aplikasinya berjalan di server milik pembuatnya (di cloud), dan pelanggan cukup membuka peramban lalu masuk. Analoginya seperti naik ojek online: Anda tidak punya motornya, tidak mengurus servis dan bensin, tinggal pakai dan bayar sesuai pemakaian. Gmail, Notion, dan Spotify adalah SaaS.
On-Premise (sering disingkat on-prem) adalah kebalikannya: perangkat lunak dipasang dan berjalan di server milik pelanggan sendiri, di dalam kantor atau data center mereka. Analoginya seperti membeli mobil pribadi: mahal di depan, Anda yang mengurus servis, tetapi mobil dan semua isinya sepenuhnya milik dan kendali Anda. Bank, rumah sakit, dan instansi pemerintah sering mensyaratkan on-prem karena datanya tak boleh keluar gedung.
Hybrid adalah campuran keduanya: sebagian berjalan di cloud, sebagian di server klien. Analoginya seperti mobil pribadi dengan langganan layanan navigasi: mobilnya milik Anda (data & aplikasi inti di on-prem), tetapi peta dan lalu lintas real-time datang dari server pusat (lisensi, sinkronisasi, atau AI di cloud). Hybrid muncul ketika ada kebutuhan yang saling bertentangan — data harus lokal, tetapi lisensi dan pembaruan ingin dikendalikan terpusat.
Empat sumbu keputusan
Alih-alih bertanya "mana yang terbaik", tanyakan empat hal ini. Jawabannya menuntun Anda ke model yang tepat tanpa drama.
| Sumbu | Condong SaaS bila… | Condong On-Prem bila… |
|---|---|---|
| Kepemilikan data | Data boleh di cloud; pelanggan tak keberatan. | Regulasi/kontrak mewajibkan data tinggal di gedung klien. |
| Model pendapatan | Langganan bulanan, banyak pelanggan kecil. | Lisensi besar per instalasi, sedikit pelanggan enterprise. |
| Kendali pembaruan | Anda ingin update terpusat, semua pelanggan versi sama. | Klien mengendalikan kapan update dipasang. |
| Kecepatan rilis | Ingin rilis harian; iterasi cepat. | Rilis terjadwal, uji ketat sebelum masuk produksi klien. |
Lima mitos yang memperlambat
Sebelum lanjut, mari bongkar keyakinan keliru yang paling sering membuat proyek melambat sebelum baris kode pertama ditulis.
| Mitos | Kenyataan |
|---|---|
| "Kalau serius, harus microservices." | Untuk tim kecil, microservices menambah beban tanpa manfaat. Monolit modular lebih cepat & cukup untuk mayoritas produk. |
| "Model penyebaran ditentukan di awal." | Ia bisa — dan sebaiknya — ditunda. Dengan Modular Monolith, penyebaran hanyalah konfigurasi build. |
| "On-prem itu kuno." | On-prem tetap wajib di sektor teregulasi. Yang berubah hanya cara mengemasnya: kini via kontainer. |
| "SaaS selalu lebih murah." | Biaya cloud tumbuh seiring pemakaian. Untuk beban stabil, VPS atau on-prem bisa lebih ekonomis. |
| "Cepat berarti berantakan." | Cepat yang berkelanjutan justru datang dari kerapian: konvensi tetap, modul berbatas, template reuse. |
Kenapa "Hybrid + Modular Monolith" sering jadi jawaban
Bila Anda mengejar kecepatan, kabar baiknya: Anda tidak harus memilih di awal. Kunci nya adalah menunda keputusan penyebaran hingga saat Anda benar-benar tahu siapa pelanggannya. Ini hanya mungkin jika kode Anda tidak mengunci diri ke satu model.
Modular Monolith memungkinkan hal itu. Satu basis kode, tertata ke dalam modul-modul dengan batas jelas, bisa:
- di-deploy sebagai SaaS multi-tenant ke Vercel/VPS hari ini;
- dikemas menjadi Docker Compose untuk on-prem klien enterprise minggu depan;
- dijalankan hybrid — inti di klien, lisensi & sinkronisasi di cloud — tanpa menulis ulang.
Karena modul-modul itu berbicara lewat pemanggilan fungsi di dalam satu proses (bukan jaringan), Anda menghindari seluruh kompleksitas microservices yang justru memperlambat tim kecil. Model penyebaran menjadi sekadar konfigurasi build, bukan keputusan arsitektur yang mahal.
Kesederhanaan adalah prasyarat keandalan.
Edsger W. Dijkstra
Jangan mulai dari "SaaS atau on-prem". Mulai dari use case dan Modular Monolith. Biarkan model penyebaran menjadi keputusan yang bisa diubah, bukan fondasi yang tak bisa dicabut.
Pohon keputusan model penyebaran
Bila Anda perlu memutuskan model sekarang, ikuti pohon ini. Ia sengaja pendek — sebagian besar proyek berhenti di dua pertanyaan pertama.
Tujuan besar buku ini, ditegaskan
Sepanjang halaman berikutnya, satu obsesi akan terus muncul: kecepatan yang berkelanjutan. Bukan cepat lalu ambruk, melainkan cepat karena punya sistem. Dua angka menjadi jangkar:
Angka-angka itu bukan sihir. Ia hasil dari disiplin: stack yang tidak pernah diganti, modul yang di-reuse, konvensi yang tidak diperdebatkan ulang, dan penolakan keras terhadap over-engineering. Buku ini adalah manual disiplin tersebut — cukup eksplisit untuk diikuti manusia, cukup terstruktur untuk dieksekusi agent AI.
SaaS Mendalam
SaaS terlihat sederhana dari luar — "aplikasi web yang bisa dilanggan" — tetapi di dalamnya tersimpan satu keputusan yang membentuk seluruh arsitektur: bagaimana Anda melayani banyak pelanggan dari satu aplikasi tanpa data mereka saling bocor. Inilah persoalan multi-tenancy.
Ada dua cara membangun desain perangkat lunak: membuatnya begitu sederhana sehingga jelas tak ada kekurangan, atau begitu rumit sehingga tak ada kekurangan yang jelas.
C. A. R. Hoare
Apa itu tenant
Dalam SaaS, tenant adalah satu unit pelanggan yang datanya terisolasi dari pelanggan lain. Tenant bisa berarti satu perusahaan, satu tim, atau satu akun. Aplikasi yang sama melayani ribuan tenant, tetapi tenant A tidak boleh pernah melihat data tenant B. Cara Anda memenuhi janji isolasi ini menentukan tiga pola besar.
Tiga pola multi-tenancy: pool, silo, bridge
1. Pool (shared everything)
Semua tenant berbagi satu basis data dan tabel yang sama. Pemisahan dilakukan dengan kolom tenant_id di setiap baris, dan setiap query wajib menyaring berdasarkan kolom itu. Ini pola termurah dan tercepat untuk memulai — ideal untuk SaaS kecil dengan banyak tenant ringan.
// Pola POOL: satu tabel, dipisah kolom tenantId model Invoice { id String @id @default(cuid()) tenantId String // wajib ada di SETIAP tabel tenant amount Int createdAt DateTime @default(now()) @@index([tenantId]) // selalu indeks tenantId }
Dengan pola pool, satu query yang lupa menambahkan where tenantId bisa membocorkan seluruh data pelanggan. Jangan andalkan disiplin manual — bungkus akses data di satu lapisan (repository/service) yang selalu menyuntik tenantId. Lihat Bab 11.
2. Silo (isolated everything)
Setiap tenant punya basis data (atau skema) sendiri yang benar-benar terpisah. Isolasi kuat, cocok untuk pelanggan enterprise yang menuntut jaminan. Harganya: biaya operasional dan kompleksitas migrasi naik seiring jumlah tenant.
3. Bridge (shared app, separate schema)
Jalan tengah: satu aplikasi dan satu server basis data, tetapi masing-masing tenant mendapat skema tersendiri. Menyeimbangkan isolasi dan biaya. Cocok saat sebagian tenant butuh jaminan lebih tanpa harus membangun infrastruktur silo penuh.
| Aspek | Pool | Silo | Bridge |
|---|---|---|---|
| Isolasi data | Logis (kolom) | Fisik (DB terpisah) | Skema terpisah |
| Biaya per tenant | Sangat rendah | Tinggi | Sedang |
| Kompleksitas migrasi | Satu kali | × jumlah tenant | × jumlah skema |
| Cocok untuk | SaaS massal, tenant ringan | Enterprise, regulasi ketat | Campuran |
| Risiko kebocoran | Perlu guard ketat | Minimal | Rendah |
Untuk 1 app semalam, hampir selalu pilih pool. Ia paling cepat dibangun dan paling murah dijalankan. Naikkan tenant tertentu ke bridge/silo hanya ketika ada pelanggan yang membayar untuk isolasi itu — bukan sebelumnya.
Langganan & billing
SaaS hidup dari pendapatan berulang. Model langganan yang umum:
- Flat per bulan — satu harga, semua fitur. Paling sederhana untuk dimulai.
- Berjenjang (tiered) — Free / Pro / Business, tiap tingkat membuka fitur atau kuota lebih.
- Berbasis pemakaian (usage-based) — bayar sesuai konsumsi (mis. jumlah token AI, storage, seat).
- Per seat — bayar per pengguna aktif di dalam tenant.
Secara teknis, billing SaaS bersandar pada webhook dari penyedia pembayaran (Stripe, Midtrans, Xendit). Alurnya: pelanggan membayar → penyedia mengirim webhook → aplikasi memperbarui status langganan tenant → fitur terbuka/terkunci lewat feature flag (Bab 8).
// modules/billing/webhook.ts — inti pola webhook langganan export async function handleWebhook(evt: BillingEvent) { switch (evt.type) { case 'subscription.active': await setPlan(evt.tenantId, evt.plan, 'active'); break; case 'subscription.past_due': await setPlan(evt.tenantId, evt.plan, 'grace'); // beri tenggang break; case 'subscription.canceled': await setPlan(evt.tenantId, 'free', 'canceled'); break; } }
Merancang tingkatan (tier) yang masuk akal
Kebanyakan SaaS berjenjang. Rancang tingkatan berdasarkan nilai yang dirasakan pelanggan, bukan sekadar membatasi fitur secara acak. Pola yang teruji:
| Tingkat | Untuk siapa | Pembatas alami |
|---|---|---|
| Free | Mencoba, proyek kecil | Kuota rendah (mis. 5 invoice/bulan) |
| Pro | Individu/tim kecil serius | Kuota longgar + fitur produktivitas |
| Business | Tim/perusahaan | Seat banyak, SSO, dukungan prioritas |
| Enterprise | Klien besar/teregulasi | Kontrak, on-prem/hybrid, SLA |
Perhatikan bahwa tingkat Enterprise sering kali adalah pintu menuju on-prem atau hybrid — inilah tempat ketiga model bertemu dalam satu produk. Karena basis kode Anda Modular Monolith, melayani pelanggan enterprise on-prem tidak berarti membangun produk kedua.
Semua tingkat berjalan di kode yang sama. Perbedaannya hanya feature flag (Bab 8) yang membaca plan tenant. Jangan pernah membuat cabang kode terpisah per tingkat — itu menggandakan beban pemeliharaan tanpa alasan.
Onboarding tenant baru
Salah satu keputusan halus di SaaS pool adalah apa yang terjadi saat pelanggan baru mendaftar. Alurnya harus atomik: buat tenant, buat pengguna pemilik, tetapkan plan awal (biasanya free), lalu arahkan ke aplikasi. Semua dalam satu transaksi agar tak ada tenant setengah jadi.
// modules/tenant/onboard.ts — atomik, tak ada tenant setengah jadi export async function onboard(email: string, org: string) { return db.$transaction(async (tx) => { const tenant = await tx.tenant.create({ data: { name: org, plan: 'free' } }); await tx.user.create({ data: { email, tenantId: tenant.id, role: 'owner' } }); return tenant; }); }
Saat onboarding, isi tenant baru dengan satu-dua data contoh (mis. satu klien, satu invoice draft). Pengguna yang melihat aplikasi "sudah ada isinya" jauh lebih cepat paham — dan ini menaikkan konversi dari coba menjadi bayar.
Update terpusat: berkah utama SaaS
Keunggulan terbesar SaaS bukan teknologi, melainkan operasional: semua pelanggan menjalankan satu versi. Perbaiki bug sekali, semua orang dapat perbaikannya. Rilis fitur sekali, semua orang melihatnya. Tidak ada "pelanggan X masih di versi lama". Inilah yang membuat iterasi harian mungkin — dan iterasi harian adalah bahan bakar rumus "30 app sebulan".
Kelebihan & kekurangan SaaS
| Kelebihan | Kekurangan |
|---|---|
| Rilis & perbaikan terpusat, semua pelanggan seragam. | Data pelanggan berada di infrastruktur Anda — beban kepercayaan & kepatuhan. |
| Pendapatan berulang & dapat diprediksi. | Perlu menanggung biaya cloud yang tumbuh seiring pemakaian. |
| Onboarding cepat: pelanggan tinggal daftar. | Tidak cocok untuk klien yang melarang data keluar gedung. |
| Skala ekonomis: satu tim melayani ribuan tenant. | Downtime Anda = downtime semua pelanggan sekaligus. |
Pilih SaaS ketika: pelanggan Anda banyak dan kecil, data boleh di cloud, Anda ingin rilis cepat & terpusat, dan model bisnisnya langganan. Ini default untuk sebagian besar produk baru yang mengejar pertumbuhan.
On-Premise Mendalam
Bagi sebagian orang, on-premise terdengar kuno — peninggalan era sebelum cloud. Kenyataannya, on-prem tetap hidup dan bahkan wajib di banyak sektor. Alasannya satu kata: kedaulatan. Ada data yang, secara hukum atau prinsip, tidak boleh meninggalkan gedung pemiliknya.
Apa yang berubah ketika aplikasi dipasang di server klien
Pada SaaS, Anda mengendalikan server. Pada on-prem, Anda menyerahkan aplikasi untuk berjalan di lingkungan yang tidak Anda kendalikan. Ini mengubah beberapa asumsi mendasar:
- Anda tak bisa "ssh" kapan saja. Debugging harus lewat log yang bisa dikirim klien, bukan akses langsung.
- Update tidak otomatis. Klien memutuskan kapan memasang versi baru; Anda harus mendukung beberapa versi sekaligus.
- Lingkungan bervariasi. OS, versi database, proxy, dan kebijakan jaringan berbeda-beda. Karena itu on-prem modern hampir selalu dikemas sebagai kontainer.
Kemas seluruh aplikasi — web, worker, database, cache — sebagai satu docker-compose.yml. Klien cukup menjalankan docker compose up -d. Ini menghilangkan 90% masalah "jalan di mesin saya tapi tidak di server klien". Detail di Bab 10.
Lisensi: bagaimana Anda dibayar tanpa mengendalikan server
Pada SaaS, kalau pelanggan berhenti bayar, Anda tinggal mematikan akun. Pada on-prem, aplikasinya ada di server klien — Anda tidak bisa sekadar "mematikan". Karena itu on-prem membutuhkan License Manager (dibahas penuh di Bab 9): sebuah mekanisme yang memvalidasi bahwa instalasi ini sah dan langganannya aktif.
Dua mode validasi lisensi:
- Online — aplikasi sesekali menelepon pulang ke license server Anda untuk memperpanjang token. Praktis, tetapi butuh koneksi keluar yang kadang dilarang klien.
- Offline — klien menempelkan berkas lisensi bertanda tangan kriptografis. Aplikasi memverifikasi tanda tangan tanpa internet. Wajib untuk lingkungan air-gapped.
Keamanan data di sisi klien
Ironisnya, on-prem dipilih demi keamanan, tetapi memindahkan tanggung jawab keamanan ke klien juga menambah risiko baru. Yang harus Anda sediakan:
- Enkripsi saat diam untuk data sensitif, meski database ada di server klien.
- Konfigurasi aman secara default — jangan kirim aplikasi dengan kata sandi admin bawaan yang lemah.
- Panduan backup yang jelas; pada on-prem, backup adalah tanggung jawab klien, tetapi Anda harus mempermudahnya.
- Audit log yang tak bisa dihapus diam-diam, agar jejak tetap ada bila terjadi insiden.
Peramalan sangat sulit, terutama tentang masa depan.
Pepatah, sering dikaitkan dengan Niels Bohr
Kutipan itu relevan karena on-prem menuntut Anda merancang untuk lingkungan yang tak Anda ketahui. Semakin sedikit asumsi yang aplikasi Anda buat tentang lingkungannya, semakin mulus ia berjalan di server klien mana pun.
Sektor yang lazim menuntut on-prem
| Sektor | Pemicu | Model lazim |
|---|---|---|
| Perbankan & keuangan | Regulasi ketat, data transaksi sensitif | On-Prem / Hybrid |
| Kesehatan | Kerahasiaan data pasien | Hybrid |
| Pemerintahan & pertahanan | Kedaulatan data, sering air-gapped | On-Prem |
| Manufaktur | Sistem di pabrik, jaringan terisolasi | On-Prem |
| Pendidikan besar | Data siswa, integrasi kampus | Hybrid |
Pilih on-prem ketika: regulasi/kontrak mewajibkan data tinggal di sisi klien (perbankan, kesehatan, pemerintah, pertahanan), klien punya tim IT sendiri, model bisnisnya lisensi enterprise besar, dan klien menuntut kendali penuh atas kapan update dipasang.
Checklist kesiapan on-prem
Sebelum mengirim aplikasi ke server klien pertama, pastikan hal-hal ini beres. Kelalaian di sini menjadi tiket dukungan berhari-hari.
- ☐ Seluruh sistem terkemas dalam satu
docker-compose.ymlyang teruji dari nol. - ☐ Konfigurasi lewat variabel lingkungan, bukan nilai keras di kode.
- ☐ Migrasi database maju-saja & idempoten.
- ☐ Tanpa kata sandi/rahasia bawaan yang lemah; wajib diatur saat instalasi.
- ☐ Endpoint
/healthuntuk verifikasi pasca-instal. - ☐ Skrip backup & restore + dokumentasinya.
- ☐ License Manager berfungsi online dan offline.
- ☐ Panduan instalasi & CHANGELOG berbahasa manusia.
Strategi versi & rilis on-prem
Pada SaaS Anda punya satu versi hidup. Pada on-prem, beberapa versi berjalan bersamaan di klien berbeda. Tanpa strategi, ini menjadi mimpi buruk dukungan. Tiga aturan menjaganya waras:
- Versi semantik yang tegas.
MAYOR.MINOR.PATCH. Perubahan yang memecah kompatibilitas hanya di versi mayor, dan selalu disertai panduan migrasi. - Migrasi maju-saja & idempoten. Skrip migrasi database harus aman dijalankan ulang dan tak pernah mundur. Klien mungkin melompati beberapa versi sekaligus.
- Jendela dukungan. Nyatakan berapa versi ke belakang yang Anda dukung (mis. dua mayor terakhir). Ini mencegah beban dukungan tumbuh tanpa batas.
# klien menjalankan skrip pembaruan yang Anda sediakan docker compose pull # tarik image versi baru docker compose run --rm app ./migrate.sh # migrasi maju-saja docker compose up -d # nyalakan versi baru docker compose run --rm app ./healthcheck.sh
Sertakan CHANGELOG singkat berbahasa manusia di tiap rilis on-prem — apa yang berubah, apa yang perlu diperhatikan, dan apakah ada tindakan manual. Tim IT klien akan berterima kasih, dan tiket dukungan Anda berkurang.
Kelebihan & kekurangan On-Premise
| Kelebihan | Kekurangan |
|---|---|
| Data tak pernah keluar gedung klien — kepatuhan mudah dibuktikan. | Update lambat & harus mendukung banyak versi sekaligus. |
| Klien memegang kendali penuh atas infrastruktur. | Debugging sulit tanpa akses langsung. |
| Pendapatan lisensi besar per instalasi. | Onboarding lambat: instalasi, konfigurasi, pelatihan. |
| Bisa berjalan air-gapped (tanpa internet). | Butuh License Manager & distribusi yang matang. |
Hybrid
Hybrid bukan model "malas memilih". Ia adalah jawaban yang tepat ketika dua kebutuhan yang saling bertentangan harus dipenuhi sekaligus: data harus lokal, tetapi kendali harus terpusat. Ketika Anda menemui keduanya dalam satu proyek, hybrid berhenti menjadi kompromi dan menjadi desain yang benar.
Anatomi sistem hybrid
Sistem hybrid membelah aplikasi menjadi dua wilayah yang berkomunikasi lewat kontrak yang jelas:
- Bidang inti (on-prem): logika bisnis dan data sensitif berjalan di server klien. Di sinilah data pelanggan hidup dan tak pernah pergi.
- Bidang kendali (cloud): hal-hal yang ingin Anda kelola terpusat — validasi lisensi, penagihan, telemetri agregat, distribusi pembaruan, atau layanan AI yang butuh GPU.
Sinkronisasi: jantung yang berdenyut
Ketika sebagian data harus hadir di dua tempat, Anda memerlukan strategi sinkronisasi. Prinsip yang menyelamatkan waktu:
- Satu arah bila mungkin. Dorong hanya data agregat/anonim ke cloud (mis. jumlah pemakaian untuk billing), bukan data mentah.
- Idempoten. Setiap pesan sinkronisasi harus aman dikirim ulang tanpa menggandakan efek — gunakan kunci idempoten.
- Antre saat offline. Bila koneksi putus, tumpuk peristiwa di antrian lokal dan kirim saat pulih. On-prem sering hidup di jaringan yang tak stabil.
// modules/sync/push.ts — kirim metrik agregat, aman diulang export async function pushUsage(period: string) { const payload = { idempotencyKey: `usage-${period}`, // kunci idempoten: aman kirim ulang seats: await countActiveSeats(), // agregat, BUKAN data mentah period, }; try { await post(`${LICENSE_SERVER}/usage`, payload); } catch { await queue.enqueue(payload); // offline? antre & coba lagi nanti } }
Godaan hybrid adalah menyinkronkan terlalu banyak — data mentah dua arah, real-time, dengan resolusi konflik rumit. Itu pintu masuk ke kompleksitas terdistribusi yang justru ingin kita hindari. Batasi sinkronisasi pada metrik agregat satu arah. Bila Anda merasa butuh lebih, tanyakan apakah kasusnya benar-benar hybrid atau sebenarnya dua produk terpisah.
License server pada hybrid
Pola hybrid paling umum di dunia B2B adalah aplikasi on-prem + license server di cloud. Aplikasi berjalan penuh di gedung klien, tetapi setiap beberapa jam ia memperpanjang token lisensi dari server Anda. Bila token gagal diperpanjang melewati masa tenggang, aplikasi masuk mode terbatas. Ini memberi Anda kendali komersial tanpa menyentuh data klien.
Jangan pernah mematikan aplikasi klien seketika saat lisensi gagal divalidasi — jaringan bisa putus karena alasan sah. Beri masa tenggang (mis. 7–14 hari) sebelum membatasi fitur. Ini menjaga hubungan baik dan menghindari insiden akibat gangguan jaringan sesaat.
Ringkasan tiga model berdampingan
| Dimensi | SaaS | On-Premise | Hybrid |
|---|---|---|---|
| Lokasi data | Cloud Anda | Server klien | Klien (inti) + cloud (kendali) |
| Kendali update | Anda, terpusat | Klien | Terbagi |
| Model bayar | Langganan | Lisensi | Lisensi + langganan |
| Kecepatan rilis | Tertinggi | Terjadwal | Sedang |
| Kompleksitas operasi | Rendah | Sedang | Tertinggi |
| Basis kode | Sama — satu Modular Monolith untuk ketiganya | ||
Pilih hybrid ketika data harus tinggal di klien tetapi Anda tetap ingin: mengendalikan lisensi terpusat, menagih berdasarkan pemakaian, mendistribusikan pembaruan, atau menyediakan layanan AI berat yang tak praktis dipasang di setiap klien. Hybrid = kedaulatan data + kendali komersial.
Modular Monolith
Inilah bab yang menjadi tulang punggung seluruh buku. Bila Anda hanya sempat membaca satu bab, baca yang ini. Modular Monolith adalah pola yang membuat "1 app semalam" mungkin, dan yang membuat aplikasi yang sama bisa di-deploy sebagai SaaS, on-prem, atau hybrid tanpa ditulis ulang.
Definisi yang jujur
Modular Monolith adalah satu aplikasi (satu proses, satu deployable) yang di dalamnya dibagi menjadi modul-modul dengan batas yang tegas. Setiap modul memiliki domain, data, dan antarmukanya sendiri, tetapi semuanya berjalan dalam proses yang sama dan berkomunikasi lewat pemanggilan fungsi, bukan panggilan jaringan.
Ia berdiri di antara dua ekstrem:
- Monolit "spageti" — satu tumpukan kode tanpa batas; cepat berantakan.
- Microservices — banyak layanan terpisah lewat jaringan; kuat di skala besar, tetapi mahal untuk tim kecil.
Modular Monolith mengambil kedisiplinan batas dari microservices tanpa menanggung biaya jaringan, penemuan layanan, dan konsistensi terdistribusi.
Jika Anda tidak bisa membangun monolit yang tertata baik, apa yang membuat Anda berpikir microservices adalah jawabannya?
Simon Brown
Kenapa lebih cepat untuk solo & tim kecil
Microservices memindahkan kompleksitas dari dalam kode ke antar layanan. Untuk tim besar dengan banyak squad, itu masuk akal — setiap squad memiliki layanannya. Tetapi untuk satu orang (atau satu orang + agent AI), biaya itu murni beban:
| Kebutuhan | Modular Monolith | Microservices |
|---|---|---|
| Menjalankan lokal | npm run dev, satu proses | Banyak service + orkestrasi |
| Transaksi lintas domain | Transaksi DB biasa | Saga, kompensasi, eventual consistency |
| Refactor batas modul | Pindah folder + kompiler bantu | Perubahan kontrak API + versi |
| Deploy | Satu artefak | Pipeline per service |
| Debugging | Satu stack trace | Tracing terdistribusi |
Optimasi prematur adalah akar dari segala kejahatan (atau setidaknya sebagian besarnya) dalam pemrograman.
Donald E. Knuth, 1974
Memilih microservices "untuk berjaga-jaga kalau nanti besar" adalah bentuk optimasi prematur yang paling mahal. Fowler menyebut prinsip tandingannya dengan gamblang:
Jangan mulai proyek baru dengan microservices, meski Anda yakin aplikasi Anda akan cukup besar untuk membutuhkannya. Mulailah dengan monolit — monolith first.
Martin Fowler, 2015
Struktur folder modular yang nyata
Inti disiplinnya ada di tata letak folder. Setiap domain hidup di modules/<domain> dan memuat lapisan yang seragam: schema (bentuk data & validasi), service (logika bisnis), controller (titik masuk HTTP), dan opsional events.
src/ ├── modules/ │ ├── auth/ │ │ ├── auth.schema.ts # bentuk & validasi (zod) │ │ ├── auth.service.ts # logika: login, hash, sesi │ │ ├── auth.controller.ts # route handler │ │ └── index.ts # API PUBLIK modul (satu-satunya pintu) │ ├── billing/ │ │ ├── billing.schema.ts │ │ ├── billing.service.ts │ │ ├── billing.controller.ts │ │ └── index.ts │ └── invoice/ │ ├── invoice.schema.ts │ ├── invoice.service.ts │ └── index.ts ├── shared/ # util lintas modul: db, config, ui-kit │ ├── db.ts │ └── config.ts └── app.ts # rakit semua controller
Aturan batas modul (yang membuatnya tidak jadi spageti)
- Impor hanya lewat
index.ts. Modul lain mengaksesbillinghanya melaluimodules/billing— tidak pernah menyentuhbilling.service.tssecara langsung.index.tsadalah kontrak publik. - Tidak ada impor melingkar. Bila
authbutuhbillingdanbillingbutuhauth, salah satu batas Anda salah. Angkat yang bersama kesharedatau komunikasikan lewat event. - Data milik satu modul. Tabel
invoiceshanya boleh ditulis oleh modulinvoice. Modul lain memintanya lewat fungsi, bukan query langsung.
// modules/billing/index.ts — kontrak publik modul billing export { createSubscription, cancelSubscription } from './billing.service'; export type { Subscription, Plan } from './billing.schema'; // billing.service.ts TIDAK diekspor langsung ke luar. // Modul lain: import { createSubscription } from '@/modules/billing'
Komunikasi antar modul tanpa membuat simpul
Kadang modul billing perlu memberi tahu modul notification bahwa langganan berakhir — tetapi bila keduanya saling mengimpor, Anda menciptakan simpul yang sulit diurai. Solusinya: event. Modul memancarkan peristiwa; modul lain menanggapi tanpa saling mengenal langsung.
// shared/events.ts — bus sederhana di dalam satu proses type Handler = (payload: any) => void | Promise<void>; const bus = new Map<string, Handler[]>(); export const on = (e: string, h: Handler) => bus.set(e, [...(bus.get(e) ?? []), h]); export const emit = (e: string, p: any) => (bus.get(e) ?? []).forEach((h) => h(p)); // billing memancarkan; ia tak tahu siapa yang mendengar emit('subscription.ended', { tenantId }); // notification mendengar; ia tak mengimpor billing on('subscription.ended', ({ tenantId }) => notify(tenantId, 'expired'));
Perhatikan: ketika kelak Anda memecah modul menjadi service, emit tinggal diganti pengirim pesan (queue), dan on menjadi konsumen — pola pikirnya sudah benar sejak awal. Ini contoh bagaimana Modular Monolith yang rapi menyiapkan jalan ke microservices tanpa membayarnya di muka.
Kapan naik ke microservices
Modular Monolith bukan dogma. Ada saat ia perlu dipecah — dan karena batas modul Anda sudah rapi, memecahnya jauh lebih mudah daripada memecah monolit spageti.
- Skala tim. Ketika > 3–4 tim saling menabrak di satu basis kode dan pipeline.
- Skala beban tak seimbang. Satu modul (mis. pemrosesan video) butuh sumber daya sangat berbeda dari sisanya.
- Batas rilis berbeda. Satu modul harus rilis 10× sehari sementara sisanya mingguan.
Karena setiap modul sudah memiliki index.ts sebagai kontrak, mengangkatnya menjadi service terpisah berarti mengganti pemanggilan fungsi dengan pemanggilan HTTP di satu tempat — bukan membedah seluruh aplikasi.
Tanda monolit Anda mulai sesak
Jangan menebak kapan harus memecah — perhatikan sinyalnya. Bila sebagian besar poin ini benar, barulah pertimbangkan mengangkat satu-dua modul menjadi service terpisah:
- ☐ Waktu build/uji terasa menyakitkan meski hanya mengubah satu modul kecil.
- ☐ Beberapa tim rutin menabrak satu sama lain di berkas yang sama.
- ☐ Satu modul (mis. pemrosesan media/AI) butuh mesin/skala yang sama sekali berbeda.
- ☐ Satu modul harus dirilis berkali-kali sehari sementara sisanya mingguan.
- ☐ Basis kode > ratusan ribu baris dan navigasinya melambat secara nyata.
Godaan terbesar adalah memecah monolit karena "terasa lebih keren", bukan karena ada sinyal nyata. Setiap pemisahan menambah biaya jaringan, penerapan, dan debugging permanen. Pecah hanya ketika rasa sakit tetap-monolit melebihi biaya itu.
Peta penamaan yang konsisten
Konsistensi penamaan adalah pelumas yang membuat manusia dan AI bergerak cepat tanpa bertanya. Tetapkan sekali:
| Berkas | Isi | Contoh ekspor |
|---|---|---|
*.schema.ts | Bentuk data + validasi (zod) | InvoiceSchema, type Invoice |
*.service.ts | Logika bisnis murni | createInvoice() |
*.controller.ts | Titik masuk HTTP/route | POST /invoices |
index.ts | Kontrak publik modul | hanya yang boleh dipakai modul lain |
Saat membuat fitur baru, selalu letakkan di modules/<domain> baru dengan empat berkas: *.schema.ts, *.service.ts, *.controller.ts, index.ts. Jangan pernah mengimpor berkas internal modul lain — impor hanya dari index.ts-nya. Bila butuh data milik modul lain, panggil fungsi yang diekspornya.
Satu deployable, banyak modul, batas via index.ts. Ini pola default. Microservices adalah pengecualian yang Anda dapatkan setelah monolit modular Anda benar-benar terlalu besar — bukan sebelum.
Pabrik Aplikasi
Inilah jantung buku. Bagian I memberi Anda kosakata; Bagian II memberi Anda mesin. Kita bahas dua rumus — membangun satu aplikasi dalam semalam, lalu menaikkannya menjadi sekitar tiga puluh aplikasi dalam sebulan — dilengkapi Generator Blueprint interaktif dan katalog modul siap pakai. Semua ditulis agar bisa diikuti manusia maupun dieksekusi agent AI langkah demi langkah.
Rumus "1 App Semalam"
"Semalam" di sini bukan hiperbola pemasaran — ia adalah batasan yang menyehatkan. Ketika Anda memaksa diri menyelesaikan aplikasi yang berfungsi dalam satu malam, Anda otomatis membuang segala yang tidak penting. Rumus ini adalah sembilan langkah tetap. Ikuti urutannya; jangan berimprovisasi di malam pertama.
Buat ia berjalan, buat ia benar, buat ia cepat — dalam urutan itu.
Kent Beck
Prasyarat: satu keputusan yang tak bisa ditawar
Sebelum langkah pertama, kunci stack tetap Anda dan jangan pernah memperdebatkannya lagi malam itu. Buku ini memakai satu stack sebagai contoh sepanjang bab:
| Lapisan | Pilihan tetap | Alasan singkat |
|---|---|---|
| Kerangka web | Next.js (App Router) | Front & back dalam satu proyek; deploy sepele. |
| Basis data | PostgreSQL (Neon) | Andal, serbaguna, serverless-ready; sama untuk SaaS & on-prem. |
| ORM | Prisma | Skema tunggal, migrasi otomatis, tipe aman. |
| Validasi | Zod | Satu sumber kebenaran untuk bentuk & validasi data. |
| Auth | Auth.js / Lucia | Sesi & OAuth jadi; tak menggulung kripto sendiri. |
| UI | Tailwind + ui-kit | Rakit cepat tanpa mendesain dari nol. |
| Hosting | Vercel / Docker | SaaS lewat Vercel; on-prem lewat Compose — kode sama. |
Kecepatan datang dari keakraban, bukan dari teknologi terbaru. Pilih satu stack, kuasai betul, dan pakai untuk 30 aplikasi berikutnya. Setiap kali Anda mengganti stack, Anda membayar ulang biaya belajar.
Sembilan langkah
- Tentukan SATU use case sempitTulis dalam satu kalimat: "Aplikasi ini membantu [siapa] melakukan [satu hal]." Bila butuh kata "dan", buang salah satunya. Contoh: "Membantu freelancer membuat & mengirim invoice." Titik.
- Pilih stack tetap & scaffoldJalankan generator proyek. Jangan mulai dari halaman kosong.
bash
npx create-next-app@latest faktur --ts --tailwind --app cd faktur npm i prisma @prisma/client zod npx prisma init --datasource-provider postgresql
- Modul inti: skema dataModelkan domain sesempit use case. Untuk invoice: tenant, klien, invoice, item. Tidak lebih.
prisma
model Invoice { id String @id @default(cuid()) tenantId String clientNm String total Int status String @default("draft") items Item[] @@index([tenantId]) }
- AuthPasang autentikasi jadi. Jangan bikin sendiri sistem sesi dari nol.
bash
npm i next-auth # definisikan provider (email/google) di app/api/auth - CRUD lewat modulBuat
modules/invoicedengan schema-service-controller. Ekspor fungsi lewatindex.ts.typescript// modules/invoice/invoice.service.ts export async function createInvoice(tenantId: string, dto: NewInvoice) { const data = InvoiceSchema.parse(dto); // validasi zod return db.invoice.create({ data: { ...data, tenantId } }); }
- UI dari ui-kitRakit halaman dari komponen siap pakai (tabel, form, tombol). Jangan mendesain dari nol; pakai Tailwind + komponen yang sudah Anda punya.
- Sambungkan & uji jalur utamaLogin → buat invoice → lihat daftar → ubah status. Bila jalur inti jalan, aplikasi Anda "selesai" untuk malam ini.
- DeployDorong ke Vercel, sambungkan Neon, atur variabel lingkungan.
bash
npx prisma migrate deploy vercel --prod # atau push ke Git yang tersambung Vercel - Selesai — dan berhentiJangan tambah fitur malam ini. Catat aplikasi ini sebagai template. Tidur. Besok mulai app berikutnya dengan resep yang sama.
Tiga hal yang paling sering menggagalkan target satu malam: (1) memilih stack baru untuk "coba-coba"; (2) menambah fitur kedua sebelum yang pertama jalan; (3) menyempurnakan desain UI. Semuanya adalah bentuk over-engineering. Kejar berfungsi, bukan sempurna.
Anti-pola malam pertama
Selain tiga pembunuh di atas, ada pola pikir yang diam-diam menggagalkan. Kenali agar bisa dihindari:
| Anti-pola | Gejala | Penawar |
|---|---|---|
| Analysis paralysis | Menghabiskan malam memilih pustaka "terbaik". | Pakai stack tetap. Keputusan sudah dibuat. |
| Gold-plating | Menyempurnakan animasi sebelum fitur jalan. | Kejar "berfungsi" dulu; poles nanti bila perlu. |
| Scope creep | "Sekalian tambah fitur X" di tengah jalan. | Tulis ide di backlog; kerjakan besok sebagai app lain. |
| Abstraksi dini | Membangun "framework" untuk satu use case. | Tulis kode konkret; abstraksi lahir dari pola nyata. |
| Setup neraka | Berjam-jam di CI/CD sebelum ada aplikasi. | Deploy manual dulu; otomasi setelah app kedua. |
Perkakas: Generator Blueprint Aplikasi
Untuk mempercepat langkah 1–3, gunakan generator di bawah ini. Pilih jenis aplikasi dan fitur yang dibutuhkan; generator akan mengeluarkan daftar modul, rekomendasi model penyebaran (SaaS / On-Prem / Hybrid), dan urutan langkah membangun sebagai checklist yang bisa Anda salin — atau tempelkan ke agent AI sebagai instruksi.
Salin keluaran generator sebagai spesifikasi. Kerjakan modul dalam urutan yang tertera. Untuk setiap modul: buat folder modules/<nama>, tulis schema → service → controller → index, lalu lanjut. Jangan lompati langkah; jangan gabungkan dua modul dalam satu berkas.
Rumus "30 App Sebulan"
Satu aplikasi semalam adalah keterampilan. Tiga puluh aplikasi sebulan adalah sistem. Perbedaannya bukan pada kecepatan mengetik, melainkan pada seberapa banyak yang tidak perlu Anda kerjakan ulang. Bab ini mengubah resep tunggal menjadi lini produksi.
Aritmetika yang jujur
Tiga puluh aplikasi dalam ~30 hari berarti rata-rata satu per hari kerja. Itu mustahil bila setiap aplikasi dimulai dari nol. Ia hanya mungkin bila 80% dari setiap aplikasi sudah jadi sebelum Anda mulai — datang dari template dan modul yang di-reuse. Yang tersisa hanyalah 20% yang benar-benar unik untuk use case itu.
Lima pilar lini produksi
1. Starter kit / template
Sekali saja, bangun repositori template yang sudah memuat: struktur modules/, auth, ui-kit, konfigurasi Prisma, layout admin, dan skrip deploy. Setiap aplikasi baru dimulai dengan clone, bukan create.
# mulai app baru dari template, bukan dari nol npx degit myorg/starter-modular app-baru cd app-baru && npm i npm run setup # skrip: rename, buat .env, migrate
2. Pustaka modul siap pakai
Modul yang berulang di banyak aplikasi — auth, billing, admin, upload, notifikasi — dipisahkan sebagai paket internal yang bisa di-install. Perbaiki bug sekali, semua aplikasi mendatang mewarisi perbaikannya. Katalognya ada di Bab 8.
3. Konvensi tetap (tak diperdebatkan ulang)
Nama folder, pola penamaan berkas, gaya penanganan error, bentuk respons API — semua ditetapkan sekali dan ditulis dalam satu dokumen CONVENTIONS.md. Konvensi yang stabil menghilangkan ratusan keputusan mikro per aplikasi, dan — penting — memberi agent AI aturan yang bisa diikuti tanpa bertanya.
4. Otomasi scaffold
Buat generator modul internal. Satu perintah menghasilkan kerangka modul lengkap sesuai konvensi.
npm run gen:module -- invoice # menghasilkan: # modules/invoice/invoice.schema.ts # modules/invoice/invoice.service.ts # modules/invoice/invoice.controller.ts # modules/invoice/index.ts
5. Checklist harian
Rutinitas yang sama setiap hari membuat kecepatan dapat diprediksi:
- Pilih satu use case sempit dari daftar ide.
- Clone template, jalankan setup.
- Jalankan Generator Blueprint (Bab 6) → dapatkan daftar modul & langkah.
- Pasang modul dari pustaka; scaffold modul unik.
- Tulis 20% logika unik.
- Uji jalur utama; deploy; catat sebagai template varian.
Rencana yang baik yang dijalankan sekarang lebih baik daripada rencana sempurna minggu depan.
George S. Patton (diadaptasi)
Musuh nomor satu: over-engineering
Setiap fitur yang Anda tambahkan "untuk berjaga-jaga" adalah pajak yang Anda bayar di setiap aplikasi berikutnya. Disiplin "30 app" menuntut penolakan aktif:
- Jangan pasang microservices sebelum monolit terasa sesak (Bab 5).
- Jangan bangun abstraksi untuk kasus yang belum ada.
- Jangan tulis konfigurasi yang bisa diubah bila belum ada yang memintanya.
- Jangan optimasi performa sebelum ada pengguna yang mengeluh.
Metrik lini produksi
Yang tak diukur tak bisa dipercepat. Pantau beberapa angka sederhana agar tahu apakah "pabrik" Anda benar-benar berproduksi atau hanya sibuk:
- Waktu dari ide ke deploy pertama — target < 1 hari kerja per aplikasi.
- Rasio reuse — berapa persen kode berasal dari template/modul (target > 70%).
- Modul baru yang lahir — modul yang cukup umum untuk diangkat ke pustaka bersama.
- Tingkat abaikan — berapa aplikasi yang ternyata tak dipakai; sinyal untuk memvalidasi ide lebih awal.
Kesalahan umum saat menskalakan produksi
| Kesalahan | Akibat | Perbaikan |
|---|---|---|
| Template dibiarkan basi | Setiap app mewarisi bug lama | Rawat template seperti produk; perbarui rutin |
| Modul di-copy antar repo | Perbaikan tak menyebar | Jadikan paket bersama |
| Konvensi berbeda tiap app | Beban mental & AI bingung | Satu CONVENTIONS.md, ditegakkan lint |
| Tak pernah membuang | Menyeret app mati selamanya | Pensiunkan app tak terpakai tanpa sentimen |
Reuse > rewrite. Konvensi > kreativitas. Template > nol. Kecepatan 30 app/bulan bukan soal bekerja lebih keras, melainkan tidak mengerjakan yang sama dua kali.
Katalog Modul Siap Pakai
Ini adalah rak gudang lini produksi Anda. Sembilan modul yang muncul di hampir setiap aplikasi. Untuk masing-masing: apa fungsinya, kapan dipakai, dan cara pasang ringkasnya. Bangun sekali, reuse selamanya.
Auth
Login, sesi, reset kata sandi.
Billing
Langganan, webhook, invoice.
Admin Panel
CRUD data + RBAC.
Upload / Media
Unggah berkas ke S3/R2.
Notifikasi
Email + antrian kejadian.
Audit Log
Jejak tindakan tak terhapus.
API Key / License
Kunci akses & aktivasi.
Multi-Tenant
Isolasi data antar pelanggan.
Feature Flags
Nyala/mati fitur per plan.
1. Auth
Apa: registrasi, login, sesi, lupa kata sandi, dan (opsional) OAuth. Kapan: hampir selalu — kecuali alat publik tanpa akun. Pasang: gunakan pustaka jadi (Auth.js/Lucia); jangan gulung kriptografi sendiri. Ekspos getSession() dan requireUser() dari modules/auth.
2. Billing / Langganan
Apa: menghubungkan aplikasi ke penyedia pembayaran, memetakan status langganan tenant, dan membuka/mengunci fitur. Kapan: untuk SaaS berbayar. Pasang: satu endpoint webhook + fungsi getPlan(tenantId). Kombinasikan dengan Feature Flags untuk mengunci fitur.
3. Admin Panel
Apa: antarmuka internal untuk mengelola data & pengguna, dilindungi RBAC. Kapan: begitu ada data yang perlu dikelola manusia. Pasang: generator CRUD di atas skema Prisma; batasi dengan peran admin.
4. Upload / Media
Apa: unggah berkas, hasilkan URL, opsional ubah ukuran gambar. Kapan: aplikasi dengan foto/dokumen. Pasang: simpan ke object storage (S3/Cloudflare R2) via presigned URL; simpan hanya metadata di DB.
// modules/storage/index.ts export async function presignUpload(tenantId: string, name: string) { const key = `${tenantId}/${crypto.randomUUID()}-${name}`; return s3.presign(key, { expiresIn: 300 }); // 5 menit }
5. Notifikasi
Apa: mengirim email/notifikasi saat kejadian penting, lewat antrian agar tak memblokir permintaan. Kapan: onboarding, reset kata sandi, peringatan billing. Pasang: penyedia email (Resend/SES) + antrian (Upstash QStash). Modul lain cukup memanggil notify(event).
6. Audit Log
Apa: catatan siapa melakukan apa dan kapan, hanya bisa ditambah (append-only). Kapan: aplikasi B2B, on-prem, atau apa pun dengan tuntutan kepatuhan. Pasang: satu tabel audit + fungsi audit(actor, action, target) yang dipanggil dari service.
7. API Key / License Manager
Apa: menerbitkan & memvalidasi kunci — untuk API publik (SaaS) maupun aktivasi lisensi (on-prem). Kapan: ada integrasi eksternal atau distribusi on-prem. Pasang: kunci di-hash saat disimpan; validasi via middleware. Detail lisensi di Bab 9.
8. Multi-Tenant
Apa: memastikan setiap query terikat pada tenantId yang benar. Kapan: setiap SaaS. Pasang: bungkus akses DB dengan konteks tenant sehingga tenantId disuntik otomatis — bukan diingat manual (lihat Bab 11).
9. Feature Flags
Apa: menyalakan/mematikan fitur per tenant, per plan, atau untuk rilis bertahap. Kapan: begitu ada tingkatan langganan atau fitur eksperimental. Pasang: fungsi flag(tenant, 'nama') yang membaca dari plan + override. Ringan, tetapi mengubah cara Anda merilis.
Peta cepat: modul mana untuk kebutuhan apa
| Butuh… | Pasang modul | Wajib untuk model |
|---|---|---|
| Pengguna login | auth, session | Semua |
| Menerima pembayaran berulang | billing, invoice, webhook | SaaS, Hybrid |
| Mengelola data internal | admin-panel, rbac | Semua |
| Menyimpan berkas/gambar | storage, media-pipeline | Semua |
| Memberi tahu pengguna | notification, email, queue | Semua |
| Melacak perubahan penting | audit-log | On-Prem, B2B |
| Distribusi berlisensi | license-manager, offline-activation | On-Prem, Hybrid |
| Melayani banyak pelanggan | multi-tenant, tenant-router | SaaS, Marketplace |
| Buka/tutup fitur per plan | feature-flags | SaaS berjenjang |
Modul dalam praktik: satu folder utuh
Agar konkret, inilah wujud lengkap modul billing dalam struktur folder — cetakan yang sama untuk setiap modul lain.
modules/billing/ ├── billing.schema.ts # Plan, Subscription, event webhook (zod) ├── billing.service.ts # createSubscription, cancel, getPlan ├── billing.controller.ts # POST /webhook, GET /billing/portal ├── billing.events.ts # reaksi atas event: buka/kunci fitur └── index.ts # ekspor: getPlan, createSubscription, tipe Plan
Jangan menyalin folder modul antar proyek dengan copy-paste — perbaikan bug tak akan menyebar. Terbitkan modul sebagai paket internal (npm privat / workspace monorepo) agar semua aplikasi menarik dari sumber yang sama.
Operasi
Kecepatan tanpa operasi yang matang hanya menghasilkan prototipe. Bagian ini membahas tiga hal yang mengubah aplikasi menjadi produk yang bisa dijual, dipasang, dan dipercaya: lisensi & distribusi, deployment untuk ketiga model, serta keamanan & data.
Lisensi & Distribusi
Bagaimana Anda dibayar? Pertanyaan ini sepele di SaaS (matikan akun bila tak bayar) tetapi rumit di on-prem (aplikasinya di server orang lain). License Manager adalah jawaban teknis untuk pertanyaan komersial itu, dan ia relevan untuk ketiga model.
Anatomi sebuah lisensi
Sebuah lisensi pada dasarnya adalah klaim bertanda tangan: pernyataan tentang siapa yang berhak, sampai kapan, dan untuk apa — yang kebenarannya bisa diverifikasi secara kriptografis tanpa mempercayai pihak yang memegangnya.
{
"licenseId": "lic_8fa2...",
"customer": "PT Sehat Sentosa",
"plan": "enterprise",
"seats": 50,
"features": ["audit", "sso", "api"],
"issuedAt": "2026-01-01",
"expiresAt": "2027-01-01",
"signature": "ed25519:9c3d..." // tanda tangan atas seluruh isi
}Kunci publik/privat: kenapa aman
Anda menandatangani lisensi dengan kunci privat yang hanya Anda miliki. Aplikasi memverifikasinya dengan kunci publik yang boleh tertanam di dalam kode. Karena hanya kunci privat yang bisa membuat tanda tangan sah, klien tak bisa memalsukan atau mengubah lisensi — mengubah satu huruf pun membatalkan tanda tangan.
// modules/license/verify.ts — verifikasi offline, tanpa internet import { verify } from '@noble/ed25519'; export async function isValid(lic: License): Promise<boolean> { const body = canonical(lic); // tanpa field signature const ok = await verify(lic.signature, body, PUBLIC_KEY); return ok && new Date(lic.expiresAt) > new Date(); }
Online vs offline: dua mode aktivasi
| Aspek | Aktivasi Online | Aktivasi Offline |
|---|---|---|
| Cara | Aplikasi menelepon license server berkala | Klien menempel berkas lisensi bertanda tangan |
| Butuh internet | Ya (koneksi keluar) | Tidak — cocok air-gapped |
| Cabut lisensi | Instan (server berhenti memperpanjang) | Menunggu kedaluwarsa berkas |
| Penagihan pemakaian | Mudah (kirim metrik saat menelepon) | Perlu ekspor manual |
Bangun License Manager yang online secara default, offline sebagai cadangan. Bila license server terjangkau, gunakan untuk perpanjangan & metrik; bila tidak (air-gapped), jatuh ke verifikasi berkas offline. Satu modul, dua mode.
Daur hidup lisensi
Lisensi bukan sakelar biner "sah/tidak". Ia bergerak melalui beberapa keadaan, dan aplikasi Anda harus menanganinya dengan anggun — terutama masa tenggang, yang menyelamatkan hubungan pelanggan saat jaringan sesaat terganggu.
Penagihan & proteksi
- Penagihan pemakaian on-prem: kumpulkan metrik agregat (jumlah seat aktif, transaksi) dan kirim ke cloud saat menelepon — tanpa mengirim data mentah pelanggan.
- Proteksi wajar, bukan paranoid: tanda tangan kriptografis menghentikan pemalsuan kasual. Jangan buang waktu pada anti-tamper ekstrem — pelanggan enterprise membayar karena kontrak & dukungan, bukan karena tak bisa membajak.
- Masa tenggang: selalu beri tenggang sebelum membatasi (lihat Bab 4). Jaringan putus bukan alasan mematikan bisnis pelanggan.
Lisensi = klaim bertanda tangan. Tanda tangani dengan kunci privat, verifikasi dengan kunci publik, dukung online & offline, dan selalu beri masa tenggang.
Deployment
Karena kode Anda satu Modular Monolith, deployment menjadi soal pengemasan, bukan penulisan ulang. Bab ini menyajikan resep konkret untuk masing-masing dari tiga model. Semua berangkat dari basis kode yang sama.
Jalur SaaS: Vercel + Neon + Upstash
Model tercepat untuk rilis. Aplikasi ke Vercel, database Postgres serverless ke Neon, cache/antrian ke Upstash.
# variabel lingkungan inti DATABASE_URL=postgres://...neon.tech/app REDIS_URL=rediss://...upstash.io AUTH_SECRET=... # rilis npx prisma migrate deploy vercel --prod
Bila Anda ingin biaya tetap & kendali lebih, jalankan Modular Monolith yang sama di satu VPS (mis. dengan Docker + Caddy). Karena satu proses, satu VPS cukup untuk ribuan pengguna awal.
Jalur On-Premise: Docker Compose ke server klien
Kemas seluruh sistem menjadi satu berkas yang bisa dijalankan klien dengan satu perintah.
services: app: image: registry.myorg.io/faktur:1.4.0 env_file: .env depends_on: [db] ports: ["3000:3000"] db: image: postgres:16 volumes: ["pgdata:/var/lib/postgresql/data"] environment: POSTGRES_PASSWORD: "${DB_PASSWORD}" worker: image: registry.myorg.io/faktur:1.4.0 command: ["node", "worker.js"] depends_on: [db] volumes: { pgdata: {} }
docker compose pull
docker compose run --rm app npx prisma migrate deploy
docker compose up -d # selesai — aplikasi jalan di gedung klienJalur Hybrid: inti on-prem + license/sync server di cloud
Gabungan keduanya. Aplikasi (Compose) berjalan di klien; sebuah layanan kecil di cloud menangani lisensi & sinkronisasi. Aplikasi klien memegang LICENSE_SERVER_URL dan menelepon berkala.
DEPLOY_MODE=hybrid
LICENSE_SERVER_URL=https://lic.myorg.io
LICENSE_KEY=lic_8fa2...
SYNC_INTERVAL=6h # kirim metrik agregat tiap 6 jamObservabilitas: tahu saat rusak
Aplikasi yang di-deploy tanpa mata adalah aplikasi yang gagal diam-diam. Tiga lapisan minimum, berlaku untuk ketiga model:
- Log terstruktur (JSON) — mudah dicari & disaring. Pada on-prem, sediakan cara klien mengekspor log untuk dikirim ke Anda saat ada masalah.
- Health check — endpoint
/healthyang memeriksa DB & dependensi, dipakai oleh orkestrator & pemantau. - Pelacakan galat — kumpulkan exception (mis. Sentry) di SaaS/hybrid; pada on-prem murni, log berkas terstruktur menggantikannya.
// app/api/health/route.ts — cukup untuk ketiga model export async function GET() { const db = await ping().then(() => 'ok').catch(() => 'down'); const ok = db === 'ok'; return Response.json({ status: ok ? 'ok' : 'degraded', db }, { status: ok ? 200 : 503 }); }
Satu kode, tiga mode: saklar konfigurasi
Perbedaan antar model idealnya hanya konfigurasi, bukan cabang kode yang berserakan. Pusatkan di satu tempat.
// shared/config.ts — satu saklar untuk tiga model export const mode = process.env.DEPLOY_MODE ?? 'saas'; export const features = { centralBilling: mode === 'saas' || mode === 'hybrid', localData: mode === 'onprem' || mode === 'hybrid', licenseCheck: mode !== 'saas', };
Ekonomi tiga jalur
Biaya bukan sekadar angka server; ia mencakup operasional, dukungan, dan waktu Anda. Gambaran kasar untuk membantu memilih:
| Faktor | SaaS (Vercel/serverless) | VPS tunggal | On-Prem (per klien) |
|---|---|---|---|
| Biaya awal | Nyaris nol | Rendah & tetap | Tinggi (instalasi, pelatihan) |
| Biaya jalan | Tumbuh dengan pemakaian | Tetap, dapat diprediksi | Ditanggung klien |
| Beban operasi Anda | Rendah (dikelola) | Sedang | Tinggi (dukungan multi-versi) |
| Skala | Otomatis | Manual (naikkan spek) | Per instalasi |
| Cocok saat | Trafik naik-turun, ingin cepat | Beban stabil, hemat | Regulasi/enterprise |
Untuk produk baru dengan beban stabil, satu VPS berukuran wajar menjalankan Modular Monolith Anda dengan biaya tetap yang jauh lebih murah daripada tagihan serverless yang membengkak. Karena kodenya sama, Anda bisa pindah dari Vercel ke VPS (atau sebaliknya) tanpa menulis ulang.
Deploy = konfigurasi, bukan penulisan ulang. Satu Modular Monolith + satu berkas config + satu Docker Compose = kesiapan untuk SaaS, on-prem, dan hybrid sekaligus.
Keamanan & Data
Keamanan bukan modul yang dipasang di akhir; ia adalah cara Anda menata akses data sejak awal. Bab ini memadatkan praktik yang paling sering menyelamatkan — dan yang paling sering dilupakan dalam kejaran kecepatan.
Isolasi tenant yang tak bisa dilupakan
Kesalahan paling mahal di SaaS adalah kebocoran antar tenant. Cara mencegahnya bukan dengan mengingat menambahkan where tenantId di setiap query — manusia dan AI sama-sama lupa. Caranya adalah membuat lupa menjadi mustahil: bungkus akses data dalam konteks tenant.
// shared/db.ts — tenantId disuntik otomatis, bukan diingat export function forTenant(tenantId: string) { return prisma.$extends({ query: { $allModels: { async $allOperations({ args, query }) { args.where = { ...args.where, tenantId }; // paksa filter return query(args); }, }, }, }); } // Pemakaian: const db = forTenant(session.tenantId);
RBAC: siapa boleh apa
Setelah memastikan data terikat tenant yang benar, lapisan berikutnya adalah peran: di dalam satu tenant, tidak semua pengguna setara. Model paling sederhana yang cukup untuk mayoritas aplikasi adalah peran berbasis daftar izin.
// modules/rbac/policy.ts — cukup untuk sebagian besar app const POLICY = { owner: ['*'], // semua izin admin: ['invoice:*', 'user:read'], member: ['invoice:read', 'invoice:create'], } as const; export function can(role: Role, perm: string): boolean { const grants = POLICY[role] ?? []; return grants.includes('*') || grants.some((g) => g === perm || (g.endsWith(':*') && perm.startsWith(g.slice(0, -1)))); }
Untuk 1 app semalam, tiga peran (owner/admin/member) hampir selalu cukup. Sistem izin dinamis yang bisa dikonfigurasi per tenant adalah fitur yang Anda tambahkan ketika ada pelanggan yang membayar untuknya — bukan sebelumnya.
Enkripsi & masking PII
- Saat transit: HTTPS di mana-mana, tanpa pengecualian.
- Saat diam: aktifkan enkripsi database; untuk kolom paling sensitif (NIK, nomor kartu), enkripsi di level aplikasi sebelum disimpan.
- Masking PII: di log dan di UI non-esensial, tampilkan bentuk tersamar (
0812****3456). Data mentah hanya muncul saat benar-benar perlu.
export const maskPhone = (p: string) => p.replace(/^(\d{4})\d+(\d{4})$/, '$1****$2');
Backup & pemulihan
Backup yang tak pernah diuji sama saja tidak ada. Untuk SaaS, andalkan backup terkelola penyedia + uji restore berkala. Untuk on-prem, sediakan skrip backup yang mudah dijalankan klien dan dokumentasi restore yang jelas — pada on-prem, backup adalah tanggung jawab klien, tetapi kegagalannya akan menjadi masalah reputasi Anda.
Kepatuhan tanpa drama
Kepatuhan (mis. UU PDP di Indonesia, GDPR) terdengar menakutkan, tetapi tiga praktik menutup sebagian besar kebutuhan: (1) audit log yang utuh; (2) kemampuan menghapus/mengekspor data satu pengguna; (3) minimisasi data — jangan kumpulkan yang tak Anda butuhkan.
Amdahl memberi kita hukum tentang batas percepatan; keamanan mengajarkan hukum serupa: rantai sekuat mata rantai terlemahnya.
Prinsip rekayasa umum
Pembagian tanggung jawab keamanan
Siapa menjaga apa berbeda per model. Menyadari batas ini mencegah asumsi berbahaya — mis. mengira klien on-prem sudah mengurus backup padahal tidak.
| Tanggung jawab | SaaS | On-Prem | Hybrid |
|---|---|---|---|
| Keamanan aplikasi (kode) | Anda | Anda | Anda |
| Patch infrastruktur/OS | Anda | Klien | Terbagi |
| Backup data | Anda | Klien | Klien (data) + Anda (kendali) |
| Kontrol akses fisik | Penyedia cloud | Klien | Klien |
| Validasi lisensi | — | Anda | Anda |
Isolasi tenant, HTTPS, hashing kata sandi, dan audit log bukan "fitur nanti". Semuanya sudah ada di modul siap pakai Anda (Bab 8). Memasangnya di malam pertama tidak memperlambat — melupakannya-lah yang kelak menghancurkan.
Buat kesalahan menjadi mustahil, bukan sekadar terlarang. Suntik tenantId otomatis, enkripsi secara default, masking PII, dan audit segalanya. Keamanan yang tertanam di lapisan akses tidak bisa dilupakan oleh manusia maupun AI.
Penerapan
Teori sudah cukup. Bagian penutup ini menerapkan seluruh buku pada skenario nyata, menunjukkan bagaimana agent AI mengeksekusi resep-resepnya, lalu menutup dengan glosarium, checklist induk, dan tanya-jawab.
Studi Kasus
Lima skenario, lima blueprint. Untuk masing-masing kita tentukan use case, modul, model penyebaran, dan alasannya. Perhatikan bahwa keputusan penyebaran selalu datang terakhir, mengikuti kebutuhan pelanggan — bukan selera teknis.
Kasus 1 — SaaS kecil: "Faktur" untuk freelancer
Use case: freelancer membuat & mengirim invoice. Model: SaaS. Alasan: banyak pengguna kecil, data boleh di cloud, langganan bulanan. Deploy Vercel + Neon. Malam pertama cukup untuk versi yang berfungsi.
Langkah unik malam itu: modelkan invoice & item → CRUD invoice → tombol "kirim" (email PDF) → halaman daftar dengan status. Selesai.
Kasus 2 — Internal tool: dashboard operasi gudang
Use case: staf gudang mencatat stok masuk/keluar. Model: On-Premise (atau VPS internal). Alasan: satu organisasi, data operasional internal, tim IT sendiri. Tanpa API publik, hybrid tak diperlukan. Kemas Docker Compose.
Catatan: karena internal, lewati billing sepenuhnya. Fokus pada RBAC (siapa boleh apa) dan audit log (siapa mengubah stok). Tanpa fitur yang tak dipakai.
Kasus 3 — Produk on-prem enterprise: SIM rumah sakit
Use case: sistem informasi manajemen klinik/rumah sakit. Model: Hybrid. Alasan: data pasien wajib tinggal di rumah sakit (regulasi), tetapi vendor ingin lisensi & pembaruan terpusat. Inti on-prem, license/sync server di cloud, dengan masa tenggang panjang untuk jaringan rumah sakit yang kerap terisolasi.
Kunci desain: masa tenggang panjang (mis. 30 hari) karena jaringan rumah sakit sering terisolasi; sinkronisasi hanya metrik agregat (jumlah seat), bukan data pasien; aktivasi offline via berkas bertanda tangan untuk instalasi air-gapped.
Kasus 4 — AI app: asisten ringkasan dokumen
Use case: pengguna mengunggah dokumen, mendapat ringkasan. Modul: auth, upload/storage, ai-provider, prompt-registry, usage-meter, billing. Model: SaaS (usage-based). Alasan: butuh GPU/model di cloud, ditagih per token. Kunci: abstraksi ai-provider agar bisa berganti model tanpa menyentuh sisa aplikasi, dan usage-meter untuk penagihan akurat.
Selalu bungkus penyedia model di balik satu antarmuka modules/ai-provider. Harga, kecepatan, dan kualitas model berubah cepat; abstraksi ini adalah asuransi Anda agar bisa berpindah tanpa menulis ulang aplikasi.
Kasus 5 — Marketplace: platform jasa lokal
Use case: mempertemukan penyedia jasa dan pelanggan. Model: SaaS. Alasan: dua sisi pengguna, transaksi & pembagian dana, butuh iterasi cepat. Marketplace adalah yang paling padat modul — justru di sinilah katalog modul siap pakai paling menghemat.
Peringatan scope: marketplace mudah membengkak. Untuk malam pertama, kirim versi satu-sisi dulu (mis. hanya penyedia mengunggah jasa + pelanggan memesan), lalu tambah pembayaran & payout di iterasi berikut. Jangan bangun semuanya sekaligus.
| Kasus | Model | Pemicu keputusan |
|---|---|---|
| Faktur freelancer | SaaS | Banyak pengguna kecil, data boleh di cloud |
| Dashboard gudang | On-Prem | Internal, satu organisasi |
| SIM rumah sakit | Hybrid | Data lokal + lisensi terpusat |
| Asisten AI | SaaS | Model di cloud, tagih per pemakaian |
| Marketplace jasa | SaaS | Dua sisi, transaksi, iterasi cepat |
Satu produk menaiki tangga penyebaran
Studi kasus di atas terlihat seolah setiap produk memilih satu model selamanya. Kenyataannya, produk yang sukses sering menaiki tangga: mulai sebagai SaaS, lalu menambahkan opsi on-prem/hybrid saat pelanggan enterprise datang. Karena Modular Monolith, tangga ini didaki tanpa menulis ulang.
- Mulai: SaaS poolRilis cepat, pelanggan kecil, satu instans multi-tenant. Validasi ide dengan biaya terendah.
- Tumbuh: tambah tingkatanFeature flags membuka Pro/Business. Sebagian tenant naik ke bridge untuk isolasi lebih.
- Enterprise datang: hidupkan hybridNyalakan
DEPLOY_MODE=hybrid+ License Manager. Inti berjalan di klien, kendali tetap di cloud. - Regulasi ketat: on-prem penuhKemas Docker Compose + aktivasi offline untuk klien air-gapped. Kode yang sama, kemasan berbeda.
Pilih model per pelanggan, bukan per produk. Satu Modular Monolith melayani seluruh tangga — dari startup yang mencoba hingga enterprise yang teregulasi — tanpa pernah bercabang menjadi dua basis kode.
Cara Buku Ini Dibaca AI
Buku ini sengaja ditulis dua arah: nyaman dibaca manusia, dan terstruktur untuk dieksekusi agent AI. Bab ini menjelaskan cara kerja "resep" agar sebuah agent dapat membangun aplikasi bertahap dari halaman-halaman sebelumnya.
Kenapa struktur ini ramah-AI
- HTML semantik & heading berjenjang. Agent bisa memetakan buku menjadi pohon: Bagian → Bab → Bagian-kecil → Langkah.
- Langkah eksplisit & bernomor. Setiap resep adalah daftar berurutan dengan kriteria selesai — tak ada yang tersirat.
- Blok kode yang bisa disalin. Perintah & potongan kode siap ditempel, dengan bahasa yang ditandai.
- Kotak "Untuk Agent AI". Instruksi yang ditujukan langsung pada asisten: prasyarat, urutan, batas.
Protokol eksekusi bertahap
Agar sebuah agent membangun aplikasi dari buku ini, ia mengikuti lingkaran kecil yang sama untuk tiap langkah:
- Ambil satu langkahBaca satu item dari resep (mis. langkah 5 Bab 6: "CRUD lewat modul"). Jangan gabungkan beberapa langkah.
- Periksa prasyaratPastikan langkah sebelumnya selesai (mis. skema data sudah ada sebelum menulis service).
- Eksekusi sesuai konvensiIkuti aturan batas modul Bab 5: buat
schema → service → controller → index. Jangan menyimpang dari konvensi. - Verifikasi kriteria selesaiJalankan jalur uji yang disebut resep (mis. "login → buat invoice → lihat daftar"). Bila gagal, perbaiki sebelum lanjut.
- Lanjut ke langkah berikutBaru setelah langkah ini benar-benar selesai.
- Perlakukan Generator Blueprint (Bab 6) sebagai spesifikasi awal: jenis app + fitur → daftar modul + langkah.
- Bangun modul dalam urutan yang tertera. Satu modul = satu folder
modules/<nama>dengan empat berkas. - Patuhi tiga aturan batas (Bab 5): impor via
index.ts, tanpa siklus, data milik satu modul. - Terapkan keamanan default (Bab 11): suntik
tenantIdotomatis, hash kata sandi, audit. - Tunda keputusan penyebaran hingga akhir; gunakan saklar
DEPLOY_MODE(Bab 10). - Berhenti pada definisi "selesai" use case. Jangan menambah fitur yang tak diminta.
Contoh prompt untuk memulai
Bangun aplikasi mengikuti blueprint berikut (dari Generator Bab 6): Jenis: SaaS Multi-Tenant Fitur: auth, admin, billing Model penyebaran: SaaS Aturan: - Arsitektur Modular Monolith (Bab 5): modules/<domain> dengan schema, service, controller, index. - Impor antar modul HANYA lewat index.ts. - Multi-tenant pola pool; suntik tenantId otomatis (Bab 11). - Kerjakan satu modul sampai selesai sebelum lanjut. - Berhenti saat jalur utama berjalan. Jangan tambah fitur.
Penutup
Kita kembali ke pertanyaan pembuka: SaaS atau on-premise? Setelah empat belas bab, jawabannya seharusnya terasa berbeda. Bukan "pilih salah satu", melainkan "bangun sesuatu yang tidak memaksa Anda memilih terlalu dini".
Modular Monolith adalah pusat gravitasi buku ini karena ia membebaskan Anda dari keputusan mahal di awal. Ia membuat "1 app semalam" mungkin, membuat "30 app sebulan" berkelanjutan, dan membuat model penyebaran menjadi sekadar saklar konfigurasi. SaaS, on-prem, dan hybrid bukan lagi tiga jalan berbeda, melainkan tiga tujuan dari satu jalan yang sama.
Cara terbaik memprediksi masa depan adalah menciptakannya.
Alan Kay
Maka mulailah. Pilih satu use case sempit malam ini. Clone template. Rakit modul. Kirim. Besok, ulangi. Biarkan disiplin — bukan drama arsitektur — yang menentukan seberapa banyak yang Anda kirim.
Untuk vibe-coder & agent-nya
Buku ini lahir dari keyakinan bahwa membangun perangkat lunak seharusnya terasa seperti merakit, bukan menuang beton. Ketika Anda punya blok-blok modul yang tepat dan jalur perakitan yang jelas, membuat aplikasi menjadi tindakan yang bisa diulang dengan tenang — malam demi malam, tanpa kepanikan arsitektur.
Bagi Anda yang bekerja bersama agent AI: buku ini adalah bahasa bersama antara Anda dan asisten Anda. Ia memberi agent aturan yang cukup untuk bergerak cepat tanpa tersesat, dan memberi Anda kerangka untuk mengevaluasi apa yang dihasilkannya. Manusia menetapkan arah & scope; agent mengeksekusi resep; buku ini menjaga keduanya sejalan.
Program harus ditulis agar dibaca manusia, dan hanya kebetulan agar dijalankan mesin.
Harold Abelson & Gerald Jay Sussman, SICP
Kalimat itu, ditulis puluhan tahun lalu, kini punya makna baru: program — dan buku tentang program — sebaiknya bisa dibaca manusia maupun agent AI. Semoga buku ini membuktikannya.
Glosarium
- SaaS (Software as a Service)
- Perangkat lunak yang berjalan di server pembuatnya dan disewa pelanggan lewat langganan; diakses via peramban.
- On-Premise (On-Prem)
- Perangkat lunak yang dipasang dan berjalan di server milik pelanggan sendiri, di dalam infrastruktur mereka.
- Hybrid
- Kombinasi: sebagian aplikasi/data di sisi klien (on-prem), sebagian layanan (lisensi, billing, sinkronisasi, AI) di cloud.
- Multi-Tenancy
- Kemampuan satu aplikasi melayani banyak pelanggan (tenant) dengan data yang terisolasi antar mereka.
- Tenant
- Satu unit pelanggan (perusahaan/tim/akun) yang datanya terpisah dari tenant lain dalam SaaS.
- Pool / Silo / Bridge
- Tiga pola isolasi tenant: berbagi tabel dengan
tenantId(pool), database terpisah per tenant (silo), atau skema terpisah (bridge). - Modular Monolith
- Satu aplikasi (satu deployable) yang dibagi menjadi modul-modul berbatas tegas yang berkomunikasi lewat pemanggilan fungsi, bukan jaringan.
- Microservices
- Arsitektur banyak layanan kecil terpisah yang berkomunikasi lewat jaringan; kuat di skala besar, mahal untuk tim kecil.
- Modul
- Unit kode berbatas untuk satu domain; berisi schema, service, controller, dan kontrak publik (
index.ts). - License Manager
- Mekanisme menerbitkan & memvalidasi lisensi (online/offline) agar distribusi on-prem & hybrid dapat ditagih dan dilindungi.
- Aktivasi Offline / Air-gapped
- Validasi lisensi tanpa internet, memakai berkas bertanda tangan kriptografis; wajib untuk lingkungan tanpa koneksi keluar.
- Feature Flag
- Saklar untuk menyalakan/mematikan fitur per tenant, plan, atau rilis bertahap.
- Webhook
- Panggilan HTTP yang dikirim penyedia (mis. pembayaran) ke aplikasi Anda saat suatu peristiwa terjadi.
- PII
- Personally Identifiable Information — data yang mengidentifikasi individu (nama, NIK, telepon); wajib dilindungi & disamarkan.
- Idempoten
- Sifat operasi yang aman diulang: menjalankannya berkali-kali menghasilkan efek yang sama seperti sekali.
- Over-engineering
- Membangun kompleksitas untuk kebutuhan yang belum ada; musuh utama kecepatan produksi.
- Docker Compose
- Berkas yang mendefinisikan beberapa kontainer (aplikasi, database, worker) sebagai satu kesatuan; cara utama mengemas on-prem.
- Presigned URL
- URL sementara bertanda tangan yang mengizinkan unggah/unduh berkas langsung ke object storage tanpa melewati server aplikasi.
- RBAC
- Role-Based Access Control — pengaturan izin berdasarkan peran pengguna (mis. owner, admin, member).
- Vibe-coder
- Sebutan santai untuk pembangun yang mengalir cepat dari ide ke produk, sering dibantu agent AI, dengan menekankan momentum & iterasi.
- Grace period (masa tenggang)
- Jendela waktu saat lisensi gagal divalidasi tetapi aplikasi tetap berjalan penuh, mencegah gangguan akibat jaringan sesaat.
Checklist Induk
- ☐ Use case ditulis dalam satu kalimat tanpa kata "dan".
- ☐ Scaffold dari stack tetap (bukan halaman kosong).
- ☐ Skema data seminimal use case.
- ☐ Auth dari pustaka jadi.
- ☐ Modul domain: schema → service → controller → index.
- ☐ UI dirakit dari ui-kit.
- ☐ Jalur utama diuji (login → aksi inti → lihat hasil).
- ☐ Deploy (Vercel/Compose sesuai model).
- ☐ Dicatat sebagai template. Berhenti menambah fitur.
- ☐ Starter kit/template tersedia & teruji.
- ☐ Modul siap pakai jadi paket, bukan salin-tempel.
- ☐
CONVENTIONS.mdditulis & dipatuhi. - ☐ Generator scaffold modul berjalan.
- ☐ Checklist harian dijalankan konsisten.
- ☐ Larangan over-engineering ditegakkan.
- ☐ Saklar
DEPLOY_MODEmenentukan SaaS/On-Prem/Hybrid. - ☐ Isolasi tenant otomatis (bukan manual).
- ☐ License Manager mendukung online & offline.
- ☐ Docker Compose siap untuk on-prem.
- ☐ Backup & restore terdokumentasi.
- ☐ Audit log & masking PII aktif.
FAQ
Bukankah monolit itu ketinggalan zaman?
Apakah "1 app semalam" realistis atau sekadar slogan?
Kapan saya benar-benar butuh microservices?
Bagaimana memilih antara pool, silo, dan bridge?
Apakah hybrid tidak terlalu rumit untuk tim kecil?
Apakah saya harus memakai stack Next.js + Postgres persis?
Bagaimana kalau aplikasi saya harus SaaS sekaligus on-prem?
DEPLOY_MODE (Bab 10), aplikasi yang sama bisa dijalankan sebagai SaaS untuk pelanggan kecil dan dikemas Docker Compose untuk pelanggan enterprise on-prem — tanpa dua basis kode.