Belajar .NET - Dependency Injection & Service Lifetimes
Episode 8 of 23

Belajar .NET - Dependency Injection & Service Lifetimes

Episode ini mengajarkan dependency injection di .NET: container Microsoft.Extensions.DependencyInjection, lifetimes scoped, transient, dan singleton, pola registrasi layanan, serta cara mengonfigurasi layanan di dalam Generic Host. DI adalah tulang punggung aplikasi .NET modern.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Di episode 5 kalian belajar interface; episode 8 menunjukkan kenapa interface begitu penting di .NET: mereka adalah bahan utama dependency injection (DI). DI adalah pola di mana object menerima dependensinya dari luar, bukan menciptakannya sendiri — dan container DI .NET mengelola seluruh proses ini.

Dengan DI, kode menjadi mudah diuji, mudah diganti, dan struktur aplikasi menjadi jelas. Container Microsoft.Extensions.DependencyInjection sudah tertanam di Generic Host, sehingga setiap aplikasi .NET modern memakainya tanpa konfigurasi tambahan.

Microsoft.Extensions.DependencyInjection

Cara Kerja Container

Container menyimpan registrasi berupa pasangan kontrak dan implementasi. Saat sebuah layanan diminta, container membuat implementasi yang sesuai dan menyuntikkan dependensinya. Contoh layanan dan kontraknya:

Kontrak dan implementasi
public interface IEmailSender
{
    Task KirimAsync(string ke, string pesan);
}
 
public class SmtpEmailSender : IEmailSender
{
    public Task KirimAsync(string ke, string pesan)
    {
        Console.WriteLine($"Email ke {ke}: {pesan}");
        return Task.CompletedTask;
    }
}

SmtpEmailSender mengimplementasikan IEmailSender. Kode konsumen hanya bergantung pada interface, sehingga implementasi bisa diganti tanpa mengubah pemanggil — misalnya dari SMTP ke layanan cloud.

Registrasi di Service Collection

Registrasi dilakukan pada IServiceCollection di dalam Program.cs:

Registrasi layanan
var builder = Host.CreateApplicationBuilder(args);
 
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
 
var host = builder.Build();

builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>() memberi tahu container: saat IEmailSender diminta, kembalikan SmtpEmailSender. builder.Build() mengonversi collection menjadi container yang siap digunakan.

Service Lifetimes

Transient, Scoped, dan Singleton

Lifetime menentukan berapa lama sebuah instance hidup:

  • Transient: instance baru setiap kali diminta.
  • Scoped: satu instance per scope (biasanya satu request HTTP).
  • Singleton: satu instance untuk seluruh proses aplikasi.
Memilih lifetime
builder.Services.AddTransient<IPesanan, PesananService>();
builder.Services.AddScoped<IKontek, KontekService>();
builder.Services.AddSingleton<ILog, LogService>();

AddTransient, AddScoped, dan AddSingleton adalah tiga method registrasi utama. Aturan emasnya: scoped tidak boleh disuntikkan ke singleton — karena singleton hidup lebih lama dari scope, kebocoran referensi akan merusak perilaku dan memori.

Memilih Lifetime yang Tepat

Panduan:

  • Transient: layanan ringan tanpa state, seperti helper dan validasi.
  • Scoped: state per request, seperti DbContext dan unit of work.
  • Singleton: state global yang aman dibaca, cache, dan konfigurasi.

Service Registration Patterns

Constructor Injection

Konsumsi layanan dilakukan lewat konstruktor — container mengisinya otomatis:

Constructor injection
public class OrderService
{
    private readonly IEmailSender _sender;
 
    public OrderService(IEmailSender sender)
    {
        _sender = sender;
    }
 
    public async Task PesanAsync(string email)
    {
        await _sender.KirimAsync(email, "Pesanan diterima");
    }
}

OrderService meminta IEmailSender lewat konstruktor. Container mendeteksi parameter ini dan menyuntikkan instance yang sesuai. Constructor injection adalah bentuk DI yang paling umum dan paling mudah diuji.

Factory dan Registrasi Kondisional

Untuk pilihan implementasi saat runtime, gunakan factory:

Registrasi dengan factory
builder.Services.AddSingleton<IEmailSender>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return config["Mail:Provider"] == "SendGrid"
        ? new SendGridEmailSender()
        : new SmtpEmailSender();
});

Factory menerima IServiceProvider (sp) sehingga bisa membaca layanan lain — di sini IConfiguration — sebelum memutuskan implementasi. Pattern ini berguna untuk provider yang berbeda antar environment.

Configuring Services in Generic Host

AddSingleton versus AddScoped di Host

Semua layanan host — logging, configuration, dan kustom — diregistrasi lewat builder.Services:

Mengonfigurasi layanan host
using var host = Host.CreateApplicationBuilder(args)
    .Build();
 
var sender = host.Services.GetRequiredService<IEmailSender>();
await sender.KirimAsync("admin@example.com", "Test");

host.Services.GetRequiredService<IEmailSender>() mengambil layanan langsung dari container — berguna di titik masuk aplikasi. Untuk kode biasa, andalkan constructor injection; mengakses container secara langsung sebaiknya dibatasi.

Validasi Registrasi di Development

Aktifkan scope validation untuk mendeteksi registrasi yang salah lebih awal:

Validasi scope
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddScoped<IData, DataService>();
builder.Services.AddSingleton<ICache, CacheService>();

AddScoped pada layanan yang bergantung pada AddSingleton valid; sebaliknya singleton yang bergantung pada scoped akan memicu error saat scope validation aktif. Biasakan menjalankan aplikasi di development dengan validasi ini agar masalah lifetime ketahuan sebelum produksi.

Warning

Perhatikan lifetime saat menyuntikkan DbContext. Defaultnya scoped agar satu request memakai satu context — menyimpannya di singleton akan membuat connection dan state bocor antar request.

Ringkasan Praktik DI

  • Selalu mendaftarkan melalui interface, bukan class konkret langsung.
  • Pilih lifetime berdasarkan state: transient, scoped, atau singleton.
  • Gunakan constructor injection; hindari service locator di dalam method.
  • Factory untuk pilihan implementasi berdasarkan konfigurasi.
  • Aktifkan scope validation di environment development.

Penutup

Inti yang harus dibawa pulang:

  • DI memisahkan kontrak dari implementasi dan membuat kode mudah diuji.
  • Registrasi dilakukan lewat builder.Services dengan AddTransient, AddScoped, dan AddSingleton.
  • Transient selalu baru, scoped per request, singleton satu untuk proses.
  • Scoped tidak boleh disuntikkan ke singleton.
  • Constructor injection adalah bentuk DI yang paling disarankan.
  • Factory menyediakan pilihan implementasi berdasarkan konfigurasi.

Di episode 9 selanjutnya kita akan membahas data access dan persistence — Entity Framework Core, DbContext, mapping entitas, migrations, query dengan LINQ dan raw SQL, serta provider database SQL Server, PostgreSQL, dan SQLite.

Belajar .NET - Dependency Injection & Service Lifetimes | Belajar .NET