Lesson 0011 · Hari 2 · Desain · ±14 menit

Clean Architecture, dan perlukah Repository?

Satu gambar, satu aturan, satu debat yang sering dipancing pewawancara

Target lesson ini: Anda bisa menjawab pertanyaan interview "Repository di atas EF Core, perlu atau tidak?" dalam 60 detik, dengan pro, kontra, dan kapan tidak perlu. Supaya jawabannya punya dasar, Anda juga bisa menggambar Clean Architecture dan menjelaskan bedanya dengan layered dari lesson 0001.

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            │
                 └───────────────────────────────────────────────────────────────────┘
ProjectIsi [1]MereferensikanPadanan Node
Application CoreEntity, interface, domain service, DTO, custom exceptiontidak adafolder domain/ dengan TS interface ("port")
InfrastructureDbContext, Migration, implementasi Repository, SmtpNotifierApplication Coreadapter Prisma / Nodemailer
UIController, filter, middleware, Program.csApplication 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]

ProductService (Core)→IProductRepository (Core)←ProductRepository (Infrastructure)

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
PusatData access di dasar tumpukanApplication Core (aturan bisnis)
Arah dependencyAtas ke bawah: UI → BLL → DALSemua ke dalam, ke Core
Interface repositoryOpsional; kalau ada, di folder yang samaWajib, di Core; implementasi di Infrastructure
Penegak aturanDisiplin tim (folder)Referensi antar project (compiler)
Cocok untukCRUD 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: DbContext sudah Unit of Work + Repository, sehingga ada boilerplate?
  • kontra: repository yang mengembalikan IQueryable membocorkan 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 DbContext dan 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)
Bacaan utama (±10 menit) Microsoft Learn: Common web application architectures, bagian "Clean architecture" sampai "UI Layer". Untuk debat Repository, lanjut ke EF Core: Choosing a testing strategy, bagian "Repository pattern" dan "Summary".

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