Clean Architecture, dan perlukah Repository?
Satu gambar, satu aturan, satu debat yang sering dipancing pewawancara
1. Satu gambar: Application Core di pusat
Microsoft: "Clean architecture puts the business logic and application model at the center of the application." Arah dependency-nya dibalik: "infrastructure and implementation details depend on the Application Core." [1] Nama lainnya Hexagonal Architecture, Ports-and-Adapters, dan Onion Architecture. Itu nama yang mungkin Anda kenal dari NestJS.
┌──────────────── UI (Web API) ─────────────────┐ ┌──────── Infrastructure ────────┐
│ Controllers, Middleware, Filters, Program.cs │ │ AppDbContext, Migrations │
│ (composition root) │ │ ProductRepository, SmtpNotifier│
└──────────────────────┬────────────────────────┘ └───────────────┬────────────────┘
│ bergantung ke dalam │ bergantung ke dalam
▼ ▼
┌──────────────────────── Application Core ────────────────────────┐
│ Entity (Product, Category) · interface IProductRepository │
│ domain service (ProductService) · DTO · custom exception │
│ tidak mereferensikan project lain, tidak tahu EF Core │
└───────────────────────────────────────────────────────────────────┘
| Project | Isi [1] | Mereferensikan | Padanan Node |
|---|---|---|---|
| Application Core | Entity, interface, domain service, DTO, custom exception | tidak ada | folder domain/ dengan TS interface ("port") |
| Infrastructure | DbContext, Migration, implementasi Repository, SmtpNotifier | Application Core | adapter Prisma / Nodemailer |
| UI | Controller, filter, middleware, Program.cs | Application Core (+ Infrastructure hanya untuk wiring DI) | route Express + app.js |
2. Satu aturan: dependency rule
Robert C. Martin: "source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle." [2] Di artikel aslinya ada empat lingkaran (Entities, Use Cases, Interface Adapters, Frameworks and Drivers), tapi "the circles are schematic". Microsoft meringkasnya menjadi tiga project di atas.
Masalahnya, saat runtime Service memanggil keluar ke database. Martin menyelesaikannya dengan DIP dari lesson 0008: "We usually resolve this apparent contradiction by using the Dependency Inversion Principle." [2]
Gambar DIP yang sama dengan lesson 0008, hanya sekarang setiap kotak tinggal di project yang berbeda. Interface "are defined in the Application Core, which are then implemented by types defined in the Infrastructure layer." [1] Yang menyambungkan keduanya tetap composition root: UI boleh mereferensikan Infrastructure, tapi "developers should limit actual references to types in the Infrastructure project to the app's composition root". [1]
3. Bedanya dengan layered
Di N-Layer tradisional (UI → BLL → DAL), "compile-time dependencies run from the top to the bottom", sehingga BLL "is dependent on data access implementation details (and often on the existence of a database). Testing business logic in such an architecture is often difficult, requiring a test database." [1]
| Layered (lesson 0001) | Clean Architecture | |
|---|---|---|
| Pusat | Data access di dasar tumpukan | Application Core (aturan bisnis) |
| Arah dependency | Atas ke bawah: UI → BLL → DAL | Semua ke dalam, ke Core |
| Interface repository | Opsional; kalau ada, di folder yang sama | Wajib, di Core; implementasi di Infrastructure |
| Penegak aturan | Disiplin tim (folder) | Referensi antar project (compiler) |
| Cocok untuk | CRUD kecil-menengah, seperti Product Catalog | "non-trivial monolithic applications" [1] |
Jawaban jujur yang terdengar senior: layered Product Catalog Anda sudah memakai IProductRepository yang diinjeksi, jadi sudah setengah jalan.
Di satu project, "there's no clear indication of which classes in which folders should depend on which others"
[1];
memecah ke project membuat compiler yang menjaga arahnya. Biayanya: lebih banyak project dan boilerplate. CQRS/MediatR cukup disebut namanya; bukan syarat Clean Architecture.
4. Debat: Repository di atas EF Core
Pertanyaan ini memancing perdebatan karena dua sumber resmi Microsoft terdengar bertentangan. Kuncinya: keduanya benar, untuk situasi berbeda.
Kontra: EF Core sudah memberikannya
Dokumentasi API: "DbContext is a combination of the Unit Of Work and Repository patterns."
[3]
DbSet<Product> sudah seperti repository, dan SaveChanges sudah unit of work (ingat change tracker di lesson 0006).
Repository generik yang hanya meneruskan Add/Find menambah boilerplate tanpa manfaat.
Panduan arsitektur Microsoft bahkan menulis repository "isn't an essential pattern", lalu mengutip Jimmy Bogard (pembuat MediatR):
"they hide the important details of the underlying persistence mechanism."
[4]
Pro: test double yang bisa dipercaya
Dokumentasi testing EF Core: "If you've decided to use a test double, we recommend implementing the repository pattern".
Alternatifnya lebih buruk: mock DbSet untuk query sebaiknya dihindari, dan in-memory provider "highly discouraged".
[5]
Repository juga memastikan "all data access code is concentrated in one place".
[5]
Dan ini contoh paling nyata DIP: unit test ProductService cukup new Mock<IProductRepository>(), persis jest.fn() di Node.
Jebakan: Repository yang bocor
// ✗ abstraksi bocor: pemanggil masih menyusun query LINQ, EF Core tetap terlibat
public interface IProductRepository { IQueryable<Product> Query(); }
// ✓ method spesifik, hasil jadi; mudah di-stub di unit test
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IReadOnlyList<Product>> GetLowStockAsync(int threshold);
Task AddAsync(Product product);
}
EF Core: mengembalikan IQueryable "would mean that query operators can still be composed over the result, requiring that EF Core still be involved in translating the query; this would defeat the purpose of having a repository in the first place."
[6]
(Ini IQueryable dari lesson 0003: expression tree yang diterjemahkan provider.)
Kapan tidak perlu
EF Core merekomendasikan test ke database produksi lebih dulu: "Since these tests are required in any case, it's a good idea to start there".
Repository "can incur significant cost to implement and maintain".
[5]
Jadi untuk CRUD kecil, atau tim yang memilih integration test dengan database nyata (Testcontainers, SQLite; lihat lesson 0010),
memakai DbContext langsung di Service itu sah.
Padanan Node: membungkus Prisma client di repositories/. Banyak tim Node melewatkannya karena Prisma client sendiri sudah terasa seperti repository; argumennya sama persis.
Latihan 1: Gambar dan aturannya
Coba gambar diagram di bagian 1 di kertas dulu tanpa melihat, lalu jawab. Soal ini muncul lagi, diacak, di review campuran 0012.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di review campuran 0012.
Latihan 2: Clean vs layered
Latihan 3: Debat Repository
Latihan 4: Ucapkan dengan suara keras
Pasang timer 60 detik dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Repository di atas EF Core, perlu atau tidak?"
Bandingkan dengan jawaban contoh dan checklist-nya
"Tergantung. Pro-nya: dokumentasi EF Core sendiri merekomendasikan repository kalau kita mau test double, karena unit test Service cukup me-mock
IProductRepository, bukan me-mock DbSet atau memakai in-memory provider yang tidak setia pada database asli.
Semua query juga terkumpul di satu tempat, dan ini contoh nyata DIP.
Kontra-nya: DbContext sudah gabungan Unit of Work dan Repository, jadi repository generik sering cuma membungkus ulang dan menambah boilerplate.
Kalau repository mengembalikan IQueryable, abstraksinya bocor dan EF Core tetap terlibat.
Kapan tidak perlu: aplikasi CRUD kecil, atau tim yang memilih integration test ke database nyata, misalnya Testcontainers atau SQLite.
Di mini project saya tetap pakai repository spesifik per Entity, dengan method yang mengembalikan hasil jadi.
Padanannya di Node: membungkus Prisma client di data-access layer, padahal Prisma client sendiri sudah mirip repository."
Checklist: apakah Anda menyebut…
- pro: mudah di-mock untuk unit test Service (rekomendasi dokumentasi EF Core)?
- pro: query terkumpul di satu tempat / contoh DIP?
- kontra:
DbContextsudah Unit of Work + Repository, sehingga ada boilerplate? - kontra: repository yang mengembalikan
IQueryablemembocorkan abstraksi? - kapan tidak perlu: CRUD kecil atau integration test dengan database nyata?
- posisi sendiri yang jelas, plus padanan Node (membungkus Prisma client)?
Lulus kalau minimal 4 dari 6. Kalau kurang, ulangi besok pagi sebelum review 0012 (spacing).
Pemanasan wajib (60 detik)
"Gambarkan Clean Architecture dan jelaskan bedanya dengan layered architecture."
Bandingkan dengan jawaban contoh dan checklist-nya
"Clean Architecture menaruh aturan bisnis di pusat: Application Core berisi entity, interface seperti IProductRepository, dan domain service.
Di luarnya ada Infrastructure, yang berisi DbContext, Migration, dan implementasi repository, serta UI yang berisi Controller dan Program.cs.
Dependency rule-nya: dependency source code hanya mengarah ke dalam, jadi Core tidak tahu EF Core.
Caranya lewat DIP: interface di Core, implementasi di Infrastructure, dan disambungkan di composition root.
Bedanya dengan layered tradisional: di sana UI bergantung ke BLL dan BLL ke DAL, sehingga logika bisnis bergantung pada akses data dan sulit dites tanpa database.
Trade-off-nya, Clean menambah project dan boilerplate, jadi saya pakai untuk domain kompleks yang berumur panjang.
Untuk CRUD kecil, layered satu project dengan interface sudah cukup.
Di Node, ini mirip hexagonal atau ports and adapters di NestJS, dengan Prisma sebagai adapter."
Checklist:
- Application Core di pusat berisi entity dan interface (mis.
IProductRepository) - Infrastructure berisi
DbContextdan implementasi repository; UI berisi Controller - dependency rule: dependency source code hanya mengarah ke dalam
- DIP sebagai caranya, disambungkan di composition root (
Program.cs) - beda dengan layered: BLL bergantung pada DAL dan database
- trade-off: boilerplate vs domain kompleks, plus padanan Node (hexagonal/ports and adapters)
Ringkasan untuk dicetak: Reference: Clean Architecture dan debat Repository · Istilah: GLOSSARY.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "di Clean Architecture, DTO taruh di mana?" atau "kalau Service memakai DbContext langsung, bagaimana unit test-nya?"
Sumber: [1] Microsoft Learn: Common web application architectures · [2] Robert C. Martin: The Clean Architecture · [3] EF Core API: DbContext Class · [4] Microsoft Learn: Designing the infrastructure persistence layer · [5] EF Core: Choosing a testing strategy · [6] EF Core: Testing without your production database system