SOLID, dari kode Product Catalog
SRP dan DIP dibahas paling dalam; OCP, LSP, dan ISP lewat satu contoh masing-masing
1. Lima prinsip, lima kalimat
Rumusan di bawah adalah versi ringkas dari Robert C. Martin sendiri, yang memperkenalkan SOLID. [4] Hafalkan kolom "Contoh"; itu yang membuat jawaban Anda terdengar berpengalaman, bukan hafalan.
| Prinsip | Satu kalimat [4] | Contoh di Product Catalog | Padanan Node/React |
|---|---|---|---|
| Single Responsibility | "Gather together the things that change for the same reasons. Separate things that change for different reasons." | Controller = HTTP, Service = aturan bisnis, Repository = akses data | Route handler Express yang tipis; logika di services/ |
| Open/Closed | "A Module should be open for extension but closed for modification." | IDiscountStrategy: diskon baru = class baru | Object map strategi { member: p => p * 0.9 } |
| Liskov Substitution | "A program that uses an interface must not be confused by an implementation of that interface." | IProductRepository versi SQL Server dan in-memory berperilaku sama | Fake repo in-memory di Jest yang setia pada kontrak |
| Interface Segregation | "Keep interfaces small so that users don't end up depending on things they don't need." | IProductReader terpisah dari IProductWriter | Props komponen Pick<Store, "products">, bukan seluruh store |
| Dependency Inversion | "Depend in the direction of abstraction. High level modules should not depend upon low level details." | ProductService bergantung pada IProductRepository, bukan AppDbContext | Provider NestJS, atau createProductService(repo) |
2. SRP: satu alasan untuk berubah
Martin: "each software module should have one and only one reason to change". Lalu ia menjelaskan apa itu "alasan": "This principle is about people." [2] Jadi pertanyaannya bukan "berapa method?", tapi "siapa saja yang bisa meminta class ini diubah?".
// ✗ Tiga pihak bisa meminta perubahan: tim produk, tim laporan, tim marketing
public class ProductService(IProductRepository repo)
{
public Task CreateAsync(CreateProductRequest r) { /* harga > 0, stok ≥ 0, nama unik */ }
public string ExportToCsv(IEnumerable<Product> ps) { /* urutan kolom laporan */ }
public void SendLowStockEmail(Product p) { /* template HTML + SMTP */ }
}
// ✓ Satu class per alasan berubah
public class ProductService(IProductRepository repo) { /* aturan bisnis saja */ }
public class ProductCsvExporter { /* format laporan */ }
public class StockAlertNotifier(IEmailSender email) { /* isi email */ }
Di level arsitektur, SRP yang sama memberi Anda pembagian layer dari lesson 0001: Microsoft menyebut SRP "similar to separation of concerns", dengan presentation, data access, dan business logic dipisah. [1] Keuntungannya juga mereka tulis: "Adding new classes is always safer than changing existing classes, since no code yet depends on the new classes."
Gejala yang bisa Anda sebut di interview: constructor yang gemuk. "If a class has many injected dependencies, it might be a sign that the class has too many responsibilities and violates the Single Responsibility Principle (SRP)." [5] Padanan Express: route handler 200 baris yang memvalidasi, query Prisma, membuat CSV, dan mengirim email sekaligus.
3. DIP: arah dependency ke abstraksi
Tanpa DIP, dependency compile-time mengikuti arah eksekusi: Service memanggil Repository, jadi Service bergantung pada Repository. Dengan DIP, Service memanggil interface, dan Repository-lah yang bergantung pada interface itu: B "depend[s] on an interface controlled by A at compile time (thus, inverting the typical compile-time dependency)". [1]
Panah = "bergantung pada" saat compile. "At run time, the flow of program execution remains unchanged": Service tetap memanggil Repository. Yang terbalik hanya arah dependency-nya. [1]
// ✗ Service (level tinggi) membuat detailnya sendiri
public class ProductService
{
private readonly ProductRepository _repo = new ProductRepository(new AppDbContext(/* … */));
}
// ✓ Service hanya kenal abstraksi; composition root memilih implementasinya
public class ProductService(IProductRepository repo) : IProductService { /* … */ }
// Program.cs
builder.Services.AddScoped<IProductRepository, ProductRepository>();
Kenapa ini jawaban terkuat Anda
Versi ✗ "is difficult to unit test because it directly creates" dependency-nya; versi ✓ memakai interface, registrasi di container, dan injeksi.
[5]
Hasilnya, unit test cukup new ProductService(mockRepo.Object) dengan Moq, persis seperti memberi jest.fn() ke service di Node.
Microsoft merangkumnya: aplikasi hasil DIP "are more testable, modular, and maintainable".
[1]
Itulah sebabnya ROADMAP menyarankan menjawab "SRP dan DIP": keduanya yang membuat test Hari 2 mungkin.
Jebakan istilah: Dependency Inversion adalah prinsip (arah dependency). Dependency Injection adalah tekniknya:
"The practice of dependency injection is made possible by following the dependency inversion principle."
[1]
DI container ASP.NET Core dan composition root (Program.cs) adalah alat untuk menerapkan DIP.
4. OCP, LSP, ISP: satu contoh masing-masing
OCP: tambah perilaku tanpa membuka class lama
"You should be able to extend the behavior of a system without having to modify that system." [3]
public interface IDiscountStrategy { decimal Apply(decimal price); }
public class NoDiscount : IDiscountStrategy { public decimal Apply(decimal p) => p; }
public class MemberDiscount : IDiscountStrategy { public decimal Apply(decimal p) => p * 0.9m; }
public class PriceCalculator(IDiscountStrategy strategy)
{
public decimal Final(decimal price) => strategy.Apply(price); // tidak berubah saat ada promo baru
}
Pelanggarannya: switch (customerType) yang bertambah satu case setiap ada promo.
LSP: implementasi tidak boleh mengejutkan
"A program that uses an interface must not be confused by an implementation of that interface."
[4]
Kalau GetByIdAsync versi SQL Server mengembalikan null untuk id yang tidak ada, versi in-memory untuk test juga harus begitu.
Dua gejala klasik: implementasi yang melempar NotImplementedException, atau melempar exception di tempat kontraknya mengembalikan null.
ISP: interface sekecil kebutuhan konsumennya
"Keep interfaces small so that users don't end up depending on things they don't need." [4]
public interface IProductReader { Task<Product?> GetByIdAsync(int id); Task<IReadOnlyList<Product>> GetAllAsync(); }
public interface IProductWriter { Task AddAsync(Product p); Task SaveChangesAsync(); }
public class ProductRepository(AppDbContext db) : IProductReader, IProductWriter { /* … */ }
public class ProductReportService(IProductReader reader) { /* hanya membaca */ }
ISP dan LSP sering muncul bersama: ReadOnlyProductRepository yang melempar NotImplementedException di AddAsync
melanggar LSP, dan akar masalahnya adalah interface yang terlalu besar (ISP).
Latihan 1: Prinsip mana yang dilanggar?
Baca potongan kodenya, lalu pilih satu prinsip. Jangan scroll ke atas dulu. 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: Alasannya
Pertanyaan "kenapa" yang biasa menyusul setelah Anda menyebut sebuah prinsip.
Latihan 3: Ucapkan dengan suara keras
Pasang timer 60 detik dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Prinsip SOLID mana yang paling sering Anda terapkan?"
Bandingkan dengan jawaban contoh dan checklist-nya
"Paling sering SRP dan DIP, karena keduanya yang membuat kode saya bisa di-unit-test.
SRP: satu class, satu alasan untuk berubah. Di Product Catalog, Controller hanya mengurus HTTP, ProductService memegang aturan bisnis
seperti harga harus lebih dari nol, dan Repository mengurus akses data lewat EF Core. Kalau ada permintaan export CSV, saya buat class baru, bukan menambah method di Service.
DIP: ProductService bergantung pada IProductRepository, bukan pada AppDbContext atau new ProductRepository().
Implementasinya didaftarkan di Program.cs lewat AddScoped, dan DI container menyuntikkannya lewat constructor.
Hasilnya, di unit test saya cukup memberi mock IProductRepository dengan Moq, mirip jest.fn() di Node.
Tandanya SRP mulai dilanggar biasanya constructor dengan terlalu banyak dependency."
Checklist: apakah Anda menyebut…
- SRP dan DIP, dengan alasan: keduanya membuat kode bisa di-unit-test?
- SRP = satu alasan untuk berubah, dengan pembagian Controller / Service / Repository?
- contoh SRP konkret (export CSV atau email jadi class terpisah)?
- DIP = Service bergantung pada
IProductRepository, bukan class konkret ataunew? - implementasi didaftarkan di
Program.cs(composition root) dan diinjeksi lewat constructor? - unit test memakai mock (Moq), dengan padanan
jest.fn()atau gejala constructor gemuk?
Lulus kalau minimal 4 dari 6. Kalau kurang, ulangi besok pagi sebelum review 0012 (spacing).
Bonus (90 detik)
"Sebutkan kelima prinsip SOLID, masing-masing dengan satu contoh dari Product Catalog."
Bandingkan dengan jawaban contoh dan checklist-nya
"S: Controller hanya HTTP, Service aturan bisnis, Repository akses data.
O: PriceCalculator menerima IDiscountStrategy, jadi diskon baru cukup class baru.
L: implementasi SQL Server dan in-memory dari IProductRepository berperilaku sama, misalnya sama-sama mengembalikan null kalau id tidak ada.
I: konsumen read-only bergantung pada IProductReader, bukan interface gemuk.
D: ProductService bergantung pada IProductRepository yang disuntikkan DI container dari Program.cs."
Checklist:
- SRP dengan pembagian Controller / Service / Repository
- OCP dengan
IDiscountStrategytanpa mengubahPriceCalculator - LSP: implementasi repository saling menggantikan tanpa perilaku mengejutkan
- ISP: interface kecil seperti
IProductReader/IProductWriter - DIP: Service bergantung pada abstraksi yang diinjeksi lewat DI
- kelimanya urut dan selesai dalam 90 detik tanpa melihat catatan
Ringkasan untuk dicetak: Reference: Prinsip SOLID di Product Catalog · Istilah: GLOSSARY.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa bedanya SRP dengan separation of concerns?" atau "kalau Repository di atas EF Core, apakah itu masih DIP?" (yang terakhir dibahas lagi di lesson 0011).
Sumber: [1] Microsoft Learn: Architectural principles · [2] Robert C. Martin: The Single Responsibility Principle · [3] Robert C. Martin: The Open Closed Principle · [4] Robert C. Martin: Solid Relevance · [5] Dependency injection in ASP.NET Core