Seluruh chapter sebelum section ini membahas cara menyusun struktur: memisahkan concern, menyusun lapisan, menyuntik dependency, memodelkan domain. Pertanyaan yang belum terjawab justru yang paling sering menentukan keberhasilan proyek: seberapa banyak struktur itu yang benar-benar Anda butuhkan? Arsitektur mengurangi biaya perubahan, tetapi menambah biaya pembacaan — setiap lapisan, interface, dan indirection menuntut pemahaman sebelum seseorang bisa berkontribusi. Chapter penutup ini bersifat keputusan, bukan teknik: kita akan memetakan kebutuhan ke pendekatan, menghitung biaya kompleksitas secara jujur, dan belajar mengenali tanda-tanda bahwa Anda terlalu sedikit atau terlalu banyak membangun struktur.
Biaya Setiap Lapisan #
Tidak ada arsitektur gratis. Sebuah aplikasi satu berkas bisa dipahami dengan satu kali gulir layar; aplikasi berlapis menuntut pembaca memahami konvensi folder, alur dependency, dan letak tiap tanggung jawab sebelum bisa mengubah apa pun. Biaya itu nyata dan dibayar setiap hari oleh setiap orang yang menyentuh kode — bukan sekali saat proyek dimulai. Karena itu keputusan arsitektur pada dasarnya adalah pertukaran: Anda membayar kompleksitas hari ini untuk mendapatkan kemudahan perubahan besok. Kalau perubahan besar itu tidak pernah datang, kompleksitas yang Anda bayar menjadi rugi murni; kalau perubahan itu pasti datang, menunda struktur berarti menumpuk utang.
Konsekuensi praktisnya: pilih struktur paling sederhana yang masih sanggup menampung perkiraan perubahan enam hingga dua belas bulan ke depan, bukan struktur paling canggih yang bisa Anda rancang. Struktur bisa ditambah belakangan; membuang struktur yang tidak terpakai jauh lebih menyakitkan karena sudah meresap ke seluruh kode.
Peta Kebutuhan ke Pendekatan #
Tabel berikut merangkum panduan kasar berdasarkan ukuran proyek dan tim. Baris pertama bukan aib — sebagian besar program di dunia memang cukup selesai sebagai skrip yang rapi:
| Kondisi proyek | Pendekatan yang cukup | Contoh nyata |
|---|---|---|
| Satu berkas, logika lurus, satu pengguna | prosedural terstruktur dengan fungsi bernama jelas | cron job, skrip impor CSV, webhook tunggal |
| CRUD satu domain, satu tim kecil | MVC sederhana atau layered tipis | admin katalog, blog, form pendaftaran |
| Beberapa domain saling berkaitan, tim > 3 orang | layered penuh dengan controller-service-repository | toko online, sistem reservasi |
| Aturan bisnis tebal dan terus berubah, banyak tim | modular per domain dengan domain modeling kental | payroll, tarif asuransi, sistem logistik |
Kode berikut mengubah tabel di atas menjadi heuristik yang bisa dieksekusi. Fungsi seperti ini tidak menggantikan penilaian Anda — ia hanya memaksa keputusan-keputusan yang biasanya samar menjadi parameter eksplisit:
<?php
function rekomendasiPendekatan(
int $jumlahFitur,
int $jumlahProgrammer,
bool $aturanBisnisKompleks,
): string {
if ($jumlahFitur <= 5 && $jumlahProgrammer === 1) {
return "skrip terstruktur";
}
if (!$aturanBisnisKompleks) {
return "layered tipis (controller-service-repository)";
}
if ($jumlahProgrammer >= 5) {
return "modular per domain";
}
return "layered penuh dengan domain modeling";
}
echo rekomendasiPendekatan(3, 1, false), "\n";
echo rekomendasiPendekatan(20, 2, false), "\n";
echo rekomendasiPendekatan(40, 6, true), "\n";
echo rekomendasiPendekatan(40, 3, true), "\n";Output yang Dihasilkan:
skrip terstruktur
layered tipis (controller-service-repository)
modular per domain
layered penuh dengan domain modelingPerhatikan dua hal dari hasil eksekusinya. Pertama, proyek dua puluh fitur tanpa aturan bisnis rumit tetap direkomendasikan ke layered tipis — aplikasi CRUD memang tidak butuh lebih. Kedua, jumlah programmer menjadi pendorong modularisasi yang lebih kuat daripada jumlah fitur: struktur per domain terutama membeli kemandirian tim, bukan kebenaran teknis.
Faktor Tim dan Organisasi #
Keputusan arsitektur sering kali lebih dipengaruhi manusia daripada kode. Tim besar membutuhkan batas modul agar tidak saling menginjak; tim satu orang justru paling diuntungkan oleh struktur datar yang cepat dipindai. Komposisi senior-junior juga berpengaruh: layered architecture dengan konvensi jelas memudahkan programmer baru berkontribusi di tempat yang aman, sementara arsitektur yang sangat pintar bisa menyandera tim yang belum memahaminya. Tingkat pergantian orang layak dihitung — struktur yang eksplisit adalah dokumen; struktur yang hanya hidup di kepala satu orang adalah risiko.
Ada juga hukum Conway yang bekerja diam-diam: struktur sistem cenderung meniru struktur komunikasi tim yang membangunnya. Tiga squad yang masing-masing memiliki produk, pembayaran, dan pengiriman akan secara alami menghasilkan modul untuk ketiganya — dan itu sehat. Melawan arah ini dengan memaksakan arsitektur teknis yang tidak cocok dengan pembagian tim biasanya berakhir pada modul yang saling bocor.
CRUD Sederhana Tidak Butuh Pendekatan Berat #
Katakan dengan jujur: aplikasi yang menampilkan formulir, menyimpan baris, dan menampilkan kembali baris itu tidak membutuhkan Domain-Driven Design. DDD, hexagonal, dan sejenisnya membayar dirinya ketika aturan bisnisnya sendiri rumit — invarian lintas entitas, perhitungan bertingkat, siklus hidup panjang. Pada aplikasi CRUD, kerumitan sesungguhnya ada di luar kode domain: pada keamanan, migrasi skema, dan pengalaman pengguna. Menambahkan lapisan aggregate, event sourcing, dan repository generik ke aplikasi demikian hanya memindah kerumitan, bukan menguranginya.
Bukan berarti aplikasi CRUD boleh jadi sarang spaghetti. Ia tetap butuh pemisahan sederhana: input divalidasi di controller, kueri terkumpul di satu tempat, dan output tidak dicampur dengan logika. Bedanya dengan aplikasi kompleks hanya derajat: dua atau tiga lapisan eksplisit sudah menutup seluruh kebutuhan.
Jalan Evolusi: Mulai Sederhana #
Evolusi yang sehat berpindah struktur saat rasa sakitnya muncul, bukan saat membaca artikel. Mari kita lihat wujudnya. Berikut aplikasi katalog dalam bentuk skrip tunggal — jujur, masih sehat untuk ukurannya:
<?php
// catalog.php - seluruh aplikasi dalam satu berkas
$db = new PDO("sqlite::memory:");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT, harga INTEGER)");
$stmt = $db->prepare("INSERT INTO produk (nama, harga) VALUES (?, ?)");
$stmt->execute(["Kopi Gayo 250g", 85000]);
$stmt->execute(["Teh Melati 100g", 32000]);
foreach ($db->query("SELECT nama, harga FROM produk ORDER BY harga") as $baris) {
echo str_pad($baris["nama"], 20), " Rp", number_format($baris["harga"], 0, ",", "."), "\n";
}Output yang Dihasilkan:
Teh Melati 100g Rp32.000
Kopi Gayo 250g Rp85.000Saat skrip ini tumbuh — halaman baru, aturan diskon, laporan — rasa sakit pertama muncul sebagai kode yang saling menempel. Refactor pertama yang layak bukan membangun arsitektur berlapis, melainkan mengekstraksi fungsi bernama jelas:
<?php
// catalog-refactor.php - perilaku sama, tanggung jawab dipisah
function buatKoneksi(): PDO
{
$db = new PDO("sqlite::memory:");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT, harga INTEGER)");
return $db;
}
function simpanProduk(PDO $db, string $nama, int $harga): void
{
$stmt = $db->prepare("INSERT INTO produk (nama, harga) VALUES (?, ?)");
$stmt->execute([$nama, $harga]);
}
function daftarProdukTermurahDulu(PDO $db): array
{
return $db->query("SELECT nama, harga FROM produk ORDER BY harga")
->fetchAll(PDO::FETCH_ASSOC);
}
// --- alur utama kini hanya komposisi langkah-langkah kecil ---
$db = buatKoneksi();
simpanProduk($db, "Kopi Gayo 250g", 85000);
simpanProduk($db, "Teh Melati 100g", 32000);
foreach (daftarProdukTermurahDulu($db) as $baris) {
echo str_pad($baris["nama"], 20), " Rp", number_format($baris["harga"], 0, ",", "."), "\n";
}Output yang Dihasilkan:
Teh Melati 100g Rp32.000
Kopi Gayo 250g Rp85.000Output kedua berkas identik — refactor yang benar tidak mengubah perilaku, hanya menata ulang tempat kode tinggal. Jalur evolusinya berlanjut bertahap: fungsi menjadi class saat data dan perilakunya mulai berkelompok, class berkelompok menjadi lapisan saat pola akses datanya menetap, dan lapisan menjadi modul domain saat tim Anda ikut membesar. Setiap langkah dipicu rasa sakit yang konkret: test yang sulit ditulis, fitur yang saling merusak, atau berkas yang tak bisa digulir lagi.
Indikator Over-Engineering dan Under-Engineering #
Kalau ragan arah, gejala di lapangan lebih bisa dipercaya daripada teori. Dua tabel berikut memetakan tanda-tanda paling umum dari kedua arah yang salah:
| Gejala over-engineering | Artinya |
|---|---|
| Interface dan abstraksi hanya punya satu implementasi, tanpa rencana kedua | struktur dibangun untuk masa depan yang belum jelas |
| Menambah satu field biasa menyentuh lima berkas atau lebih | lapisan menghasilkan biaya perubahan, bukan menghemat |
Folder berisi lebih banyak berkas Factory dan Builder daripada kode nyata | pola dipakai sebagai hiasan, bukan solusi masalah |
| Waktu menyiapkan fondasi mengalahkan waktu mengirim fitur pertama | investasi struktur tidak sebanding dengan nilai produk |
| Gejala under-engineering | Artinya |
|---|---|
| Satu berkas ribuan baris yang tidak bisa digulir selesai | tanggung jawab menumpuk tanpa batas |
| Test sulit ditulis karena semuanya tercampur | dependensi tidak bisa disuntik atau diganti |
| Bug di satu fitur merusak fitur lain yang tak berhubungan | tidak ada pemisahan yang bisa menahan kerusakan |
| Dua programmer selalu berkonflik di berkas yang sama | struktur tidak mengikuti jalur kerja tim |
Skor 2-3 gejala pada satu sisi adalah sinyal untuk bergerak ke arah berlawanan. Kerjakan langkah evolusi terkecil yang menyingkirkan gejala paling menyakitkan, lalu amati lagi — bukan membangun ulang segalanya sekaligus.
Ringkasan #
Arsitektur adalah keputusan biaya: struktur yang Anda pilih hari ini menentukan berapa mahal perubahan besok. Mulai dari pendekatan paling sederhana yang jujur terhadap ukuran proyek dan tim, biarkan rasa sakit — bukan tutorial — yang memicu evolusi, dan pantau gejala over- maupun under-engineering secara berkala. Dengan selesainya section ini, Anda memiliki gambaran lengkap dari konsep hingga keputusan. Section berikutnya membawa kita ke bengkel detail: prinsip SOLID dan design pattern, peralatan yang membuat setiap lapisan arsitektur tadi ditulis dengan rapi.