Inversion of Control, container, dan binding lifetime singleton transient scoped; service locator vs constructor injection; DI bawaan NestJS dengan module providers dan request scope, wiring manual idiomatic Go plus wire compile-time dan uber-fx, service container Laravel dengan contextual binding — praktik wire dependency graph sama di 3 ekosistem

Sejak episode 4 kita menyuntikkan dependency tanpa pernah bertanya: siapa yang membuat semua objek ini? Jawabannya adalah Inversion of Control — prinsip yang membalik kontrol konstruksi dari aplikasi ke sebuah wadah (container). Episode ini membongkar mesinnya: bagaimana tiap ekosistem me-resolve graph objek, apa beda lifetime binding, dan kapan wiring manual justru lebih baik.
Tanpa IoC: service sendiri membuat repo-nya (new PrismaOrdersRepo()) → coupling permanen, mustahil dites. Dengan IoC: service hanya menyatakan kebutuhan, pihak ketiga yang mengisi. Container adalah pemetaan "abstraksi → implementasi" plus mesin pembangun objeknya.
| Lifetime | Arti | Kapan Dipakai |
|---|---|---|
| Singleton | Satu instance seumur proses | Connection pool, cache, config |
| Transient | Instance baru setiap resolve | Objek stateless ringan, handler |
| Scoped | Satu instance per unit kerja (request) | DbContext/session/request ID |
Jebakan klasik: captive dependency — singleton yang menelan scoped (mis. cache singleton memegang DB session per-request) → session bocor lintas user.
Dua gaya mendapat dependency:
class OrderService {
constructor(private locator: ServiceLocator) {}
create(dto) {
const repo = this.locator.get(OrdersRepository); // dicari diam-diam
}
}
// Masalah: dependency TERSEMBUNYI - signature tak jujur,
// test harus setup locator global.
// Constructor Injection (standar series):
class OrderService {
constructor(private repo: OrdersRepository) {} // eksplisit & jujur
}Service locator masih sah di tepi sistem (resolver route, action resolver), tapi logika bisnis harus selalu constructor injection agar graph-nya terlihat.
@Module({
providers: [
PrismaService, // singleton default
{ provide: 'REQ_ID', scope: Scope.REQUEST, // baru per request
useFactory: () => crypto.randomUUID() },
{ provide: OrdersRepository, useClass: PrismaOrdersRepository },
RegisterUserService, // resolves otomatis via type
],
})
export class UsersModule {}
@Injectable()
export class AuditService {
// custom token + decorator parameter:
constructor(@Inject('REQ_ID') private reqId: string) {}
}Spectrum filosofinya menarik: NestJS & Laravel menyembunyikan wiring di balik konvensi/refleksi; Go menuntutnya eksplisit — dan keduanya valid untuk konteksnya masing-masing.
Target outline: wire satu dependency graph sama di 3 ekosistem dan bandingkan ergonominya.
ConfigService
├── DbConnection (singleton)
│ └── OrdersRepository (Prisma/GORM/Eloquent impl)
├── HttpClient (scoped timeout beda per consumer - contextual)
└── PaymentGateway (StripeAdapter)
└── CheckoutService(repo, gateway, pricing)
Tugas:
1. Bangun graph tsb di NestJS (module providers + token),
Go (manual main.go), Laravel (bind + when/give)
2. Tambahkan logger scoped per-request yang dipakai CheckoutService
3. Uji captive dependency: sengaja injeksi request-scoped ke singleton
-> amati bug -> perbaiki -> catat aturan tim
4. Tulis 5 kalimat perbandingan ergonomi (wiring time, debugability,
onboarding junior) sebagai ADR mini di README labNote
Pertanyaan refleksi untuk ADR mini kalian: di Go, graph yang terlihat penuh di main.go membuat debugging mudah tapi boilerplate naik; di NestJS/Laravel konvensi memangkas kode tapi menyembunyikan graph. Mana yang lebih penting untuk tim kalian?
Rangkuman episode ini:
Episode 12 membahas arsitektur paling legendaris: MVC dan variannya — Model-View-Controller asli vs web-MVC modern, MTV/MVP/MVVM, dan kenapa API JSON hanya butuh M+C. Sampai jumpa!