Clean Architecture Uncle Bob: Entities, Use Cases, Interface Adapters, Frameworks & Drivers; The Dependency Rule; kemiripan Onion architecture; pragmatisme vs ceremony — struktur src domain application infrastructure presentation di NestJS, cmd internal usecase Fiber, dan folder di luar struktur default Laravel — praktik use case PlaceOrder dengan dependency rule divalidasi architecture test

Hexagonal (ep.15) menempatkan domain di pusat. Clean Architecture (Robert C. Martin) dan Onion Architecture (Jeffrey Palermo) merumuskan ide yang sama dengan bahasa berbeda — dan menambahkan satu aturan yang membuat semuanya bisa ditegakkan otomatis: The Dependency Rule.
Episode ini menyatukan ketiganya, lalu membahas bagian yang jarang dibahas jujur: kapan ceremony-nya tidak sepadan.
Satu kalimat yang mengikat semua lingkaran: dependensi kode hanya boleh mengarah ke dalam. Lingkaran luar tahu lingkaran dalam; kebalikannya mustahil. Data pun harus dikonversi di batas agar bentuk ORM/HTTP tak bocor masuk.
Onion Architecture menyebut hal serupa (domain core → domain services → application services → outer ring); hexagonal menyebutnya ports & adapters. Ketiganya adalah satu gagasan: proteksi logika bisnis dari detail teknis via arah dependensi.
hexagonal : fokus KONTRAK (ports) - cocok saat banyak sisi I/O
clean : fokus USE CASE sebagai unit utama - vocabulary kaya utk tim besar
onion : fokus LAYERING model domain - asal-usul historisnya
Semua membagi aturan yang sama: dependensi menuju inti.Kejujuran engineering yang wajib: clean architecture punya biaya nyata — file mapper lebih banyak, interface untuk segalanya, boilerplate mapping DTO↔entity↔row. Untuk CRUD sederhana ia overkill (ingat catatan repository ep.9). Nilainya muncul ketika:
Aturan series ini: pragmatic clean — terapkan penuh pada modul kompleks, layered/modular biasa untuk modul tipis (episode 17 membahas matriks keputusannya).
// application/place-order.usecase.ts - TIDAK mengenal NestJS:
export class PlaceOrder {
constructor(
private orders: OrdersRepoPort, // interface milik domain
private pricing: PricingPort,
) {}
async exec(cmd: PlaceOrderCmd): Promise<PlaceOrderResult> {
const total = await this.pricing.quote(cmd.items);
if (!total.ok) throw new DomainError(total.reason);
const order = Order.place(cmd.customerId, cmd.items, total.amount);
await this.orders.save(order);
return { orderId: order.id, totalMinor: total.amount.minor };
}
}Target outline: use case PlaceOrder dengan dependency rule divalidasi lewat architecture test.
// tests/architecture/dependency-rule.test.ts (dependency-cruiser)
forbidden: [
{
name: 'domain-imports-nothing-outer',
from: { path: '^src/domain/' },
to: { path: '^src/(application|infrastructure|presentation)|(@nestjs|prisma)' },
},
{
name: 'application-no-infrastructure',
from: { path: '^src/application/' },
to: { path: '^src/(infrastructure|presentation)' },
},
];
// Gagal build jika use case mengimpor adapter -> aturan TIDAK bisa dilanggar diam-diam.Checklist akhir episode:
[x] Struktur 4 lingkaran eksplisit di ketiga lab
[x] Use case PlaceOrder murni (nol import framework) - lolos arch test
[x] Mapping data terjadi di batas lingkaran (bukan dump entity)
[x] Arch test masuk CI - dependency rule otomatis
[x] Catatan ADR singkat: modul mana layak clean, mana cukup modular-layeredNote
Jika arch test kalian pertama kali gagal dan menemukan 3–5 pelanggaran nyata — selamat, tool itu sudah membayar dirinya sendiri di hari pertama.
Rangkuman episode ini:
Episode 17 menjawab pertanyaan besar yang tertunda: Layered vs Hexagonal vs Clean — kapan pakai apa? Matriks kompleksitas, studi kasus startup/fintech/enterprise, dan latihan ADR lengkap. Sampai jumpa!