Not içeriği yükleniyor...
// Markdown dosyası okunuyor
// İçerik işleniyor
// Syntax highlighting hazırlanıyor
// Markdown dosyası okunuyor
// İçerik işleniyor
// Syntax highlighting hazırlanıyor
Dependency Injection kavramı, frontend ve backend geliştiriciler için gerçek hayat örnekleriyle anlaşılır bir şekilde açıklanmış. Bağımlılık yönetimi, IoC container, service lifetime, tight/loose coupling ve ASP.NET Core DI container kullanımı ile kapsamlı rehber.
builder.Services.AddScoped<> gibi şeyler görüp “Bu ne ya?” diye bakakaldıysan, yalnız değilsin. Bugün bunlardan en önemlisini, yani Dependency Injection’ı anlatacağız. Ama klasik “bağımlılıkların enjekte edilmesidir” gibi süslü cümlelerle değil; gerçekten neden DI diye bir şeye ihtiyaç duyuyoruz onu öğreneceğiz.bashfunction UserProfile() { const api = new Axios(); // her component'te kendi axios'unu oluşturuyor api.get("/user"); }
new lemekten bıktım, baseURL değiştirsem 20 dosyayı ellemem lazım.”bashfunction RegisterForm() { const emailService = new EmailService(); // Her yerde new! const handleRegister = () => { emailService.send("user@example.com", "Hoş geldiniz!"); }; return <button onClick={handleRegister}>Kayıt Ol</button>; }
EmailService’i değiştireceksinif-else ekleyip çorba yapacaksınRegisterForm component’i nasıl bildirim gönderileceğine kendisi karar veriyordu. Bu da component’i sadece EmailService’e sıkı sıkıya bağlı hale getiriyordu. Yani esnekliği yoktu, test yazmak zordu ve ileride yeni bildirim tipleri eklemek maliyetli oluyordu.new EmailService() oluşturmak yerine, bağımlılığı dışarıdan veriyoruz. Böylece component “bildirim gönderilmesini istiyorum” diyor, “nasıl gönderildiği” ile ilgilenmiyor.bashfunction RegisterForm({ messageService }) { const handleRegister = () => { messageService.send("user@example.com", "Hoş geldiniz!"); }; - return <button onClick={handleRegister}>Kayıt Ol</button>; } //Kullanımı <RegisterForm messageService={new EmailService()} /> <RegisterForm messageService={new SmsService()} />
RegisterForm her türlü mesaj servisiyle çalışabilir (email, SMS, push…)RegisterForm veya UserService sadece kendi işine odaklanıyor; “nasıl” yapıldığıyla ilgilenmiyor. Bu yaklaşım, hem test yazmayı kolaylaştırıyor, hem de uygulamayı değişime ve yeni gereksinimlere açık hale getiriyor. Örneğin ileride SMS, push notification veya başka bir servis eklemek istediğinde, mevcut component veya sınıfın içini değiştirmene gerek kalmadan sadece yeni servisi uygulayıp dışarıdan enjekte etmek yeterli oluyor. Kısacası, bu yöntem yazılımın sürdürülebilirliğini ve yeniden kullanılabilirliğini ciddi şekilde artırıyor.RegisterForm içinde new EmailService() kullanmak.bashfunction RegisterForm() { const emailService = new EmailService(); // Sabit bağımlılık const handleRegister = () => { emailService.send("user@example.com", "Hoş geldiniz!"); }; return <button onClick={handleRegister}>Kayıt Ol</button>; }
RegisterForm’a messageService prop olarak vermek, UserService’e IMessageService constructor ile enjekte etmek.bashfunction RegisterForm({ messageService }) { const handleRegister = () => { messageService.send("user@example.com", "Hoş geldiniz!"); }; return <button onClick={handleRegister}>Kayıt Ol</button>; } // Kullanım: <RegisterForm messageService={new EmailService()} /> <RegisterForm messageService={new SmsService()} />
| Özellik | Tightly Coupled | Loosely Coupled |
|---|---|---|
| Esneklik | Düşük | Yüksek |
| Test kolaylığı | Zor | Kolay |
| Değişiklik maliyeti | Yüksek (component/sınıf içini değiştir) | Düşük (sadece bağımlılığı değiştir) |
| Sınıfın sorumluluğu | Hem iş hem bağımlılığı yönetir | Sadece işi yönetir |
bashpublic class UserService{ private readonly EmailService _emailService = new EmailService(); public void RegisterUser(string email) { _emailService.Send(email, "Hoş geldin!"); } }
UserService nasıl bildirim gönderileceğine kendisi karar veriyordu. Bu durum sınıfı EmailService’e sıkı sıkıya bağlı hale getiriyordu. Çözüm basit: sınıfın kendi içinde bağımlılığı yaratmak yerine, bağımlılığı dışarıdan veriyoruz.bash//Ufak bir kod örneği public interface IMessageService { void Send(string to, string message); //Bu interface'i kullanan her class Send işlemi yapmak zorunda. }
IMessageService diyor ki: “Beni implement eden bir sınıf varsa, Send metodunu içermeli.” Ama interface kendisi nasıl gönderileceğini bilmez. Sadece bir sözleşme tanımlar. IMessageService interface’i sayesinde UserService’in hangi mesaj servisi kullanılacağını bilmesine gerek yok. Artık sadece “bir mesaj gönderilmesini istiyorum” diyor ne şekilde nasıl gönderildiği beni ilgilendirmez demek istiyor .bashpublic class EmailService : IMessageService { public void Send(string to, string message) { Console.WriteLine($"{to} adresine mail gönderildi: {message}"); } } public class SmsService : IMessageService { public void Send(string to, string message) { Console.WriteLine($"{to} numarasına SMS gönderildi: {message}"); } }
IMessageService sözleşmesini uyguluyor yani daha önceden oluşturduğımuz bu interface'i implement ediyoruz ama içlerinde farklı davranış var:
EmailService → mail gönderiyorSmsService → SMS gönderiyorbashpublic class UserService { private readonly IMessageService _messageService; public UserService(IMessageService messageService) // Dependency Injection burada gerçekleşiyor { _messageService = messageService; } public void RegisterUser(string email) { _messageService.Send(email, "Hoş geldin!"); } }
_messageService interface türünde (IMessageService) tutuluyor. Gerçek servis örneği (EmailService veya SmsService) dışarıdan veriliyor (constructor üzerinden).UserService artık loosely coupled: sadece kullanıcı kaydını yapıyor, mesaj servisi seçimi dışarıya bırakılmış.bashvar userService1 = new UserService(new EmailService()); userService1.RegisterUser("deneme@mail.com"); var userService2 = new UserService(new SmsService()); userService2.RegisterUser("5551234567");
new EmailService() veya new SmsService() yazmak yerine bu bağımlılıkları otomatik olarak yönetmenin bir yolu yok mu? İşte bu noktada IoC (Inversion of Control) ve DI Container devreye giriyor.UserService, kullanıcı kaydı yaparken bir mail servisi kullanabilir. Eğer UserService her zaman kendi içinde new EmailService() oluşturuyorsa, sınıf hem kendi işini yapmakla hem de hangi servisin kullanılacağını yönetmekle yükümlüdür. Bu durum, sınıfın sorumluluğunu gereksiz yere artırır, test yazmayı zorlaştırır ve değişiklikleri maliyetli hale getirir.UserService, sadece kullanıcı kaydı yapmakla kalmıyor, aynı zamanda hangi servisin kullanılacağını da kendisi belirleyecek şekilde geliştirilmişti. Eğer ileride SMS göndermek veya farklı bir mesaj servisi eklemek istersek, sınıfın içine dokunmamız gerekiyordu. İsterseniz tekrar hatırlayalım.bashpublic class UserService { private readonly EmailService _emailService = new EmailService(); public void RegisterUser(string email) { _emailService.Send(email, "Hoş geldiniz!"); } }
IMessageService adında bir interface tanımlamıştık.EmailService ve SmsService olmak üzere.UserService içinde Constructor üzerinden bağımlılığı alıyorduk değil mi ?IMessageService oluşturduğunda aslında sınıfın bağımlılığı soyutlanıyor.UserService, hangi servis kullanılacağını bilmek zorunda değil; sadece bir mesaj servisi ile çalışması gerektiğini biliyor.bashvar userService1 = new UserService(new EmailService()); var userService2 = new UserService(new SMSService()); var userService3 = new UserService(new NotificationService());
UserService 1 EmailService ile çalışacakUserService 2 SMSService ile çalışacaUserService 3 NotificationServiceile çalışacakUserService örneğinde gördüğümüz gibi, interface ile bağımlılığı soyutlamak ve constructor üzerinden enjekte etmek IoC prensibini manuel olarak uygulamamıza olanak sağladı. Peki ya büyük bir projede her sınıf için sürekli new EmailService() veya new SmsService() yazmak zorunda olsaydık? Bu hem zahmetli olur hem de servislerin yaşam döngülerini yönetmeyi çok karmaşık hale getirirdi. İşte bu noktada DI Container (Dependency Injection Container) devreye giriyor. DI Container, IoC mantığını otomatikleştirerek, uygulamanın ihtiyacı olan servisleri oluşturur, yönetir ve doğru sınıflara enjekte eder.”new ile), kod karmaşıklaşır ve bakımı zorlaşır.Program.cs veya Startup.cs dosyasında yapılır. Burada temel olarak üç farklı yaşam döngüsüne sahip servis kaydı yapabiliriz:bashvar builder = WebApplication.CreateBuilder(args); // Scoped: her HTTP isteğinde yeni örnek builder.Services.AddScoped<IMessageService, EmailService>(); // Singleton: uygulama boyunca tek bir örnek builder.Services.AddSingleton<ILogger, ConsoleLogger>(); // Transient: her kullanımda yeni örnek builder.Services.AddTransient<NotificationService>();
bashpublic class UserService { private readonly IMessageService _messageService; public UserService(IMessageService messageService) { _messageService = messageService; } public void RegisterUser(string email) { _messageService.Send(email, "Hoş geldiniz!"); } }
UserService, bir IMessageService bağımlılığına ihtiyaç duyuyor. DI Container, uygulama başında IMessageService için hangi implementasyonun (bu örnekte EmailService) kullanılacağını biliyor ve bunu otomatik olarak UserService sınıfının constructor’ına enjekte ediyor.UserService içinde hangi servisin kullanılacağına dair hiçbir karar bulunmaz. Bu, Inversion of Control (IoC) prensibinin pratik bir uygulamasıdır: kontrol, sınıfın kendisinden alınmış ve container’a verilmiştir. Sonuç olarak kod daha esnek, test edilebilir ve sürdürülebilir olur.bashbuilder.Services.AddSingleton<ILogger, ConsoleLogger>(); //Uygulama boyunca loglamak için sadece tek bir örnek olması yeterlidir.
bashpublic class AppSettings { public string ApplicationName { get; set; } = "MyApp"; public DateTime StartedAt { get; } = DateTime.Now; }
bash//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım. builder.Services.AddSingleton<AppSettings>();
bashpublic class HomeController : Controller { private readonly AppSettings _settings; public HomeController(AppSettings settings) => _settings = settings; - [HttpGet("/info")] public IActionResult Info() => Ok(_settings); }
bashpublic class AdminController : Controller { private readonly AppSettings _settings; public AdminController(AppSettings settings) => _settings = settings; - [HttpGet("/admin-info")] public IActionResult Info() => Ok(_settings); }
bashbuilder.Services.AddScoped<IMessageService, EmailService>(); //Her HTTP isteği için bir kere kullanılır ve daha sonra otomatik olarak garbage collector tarafından silinir.
bash//Ufak bir kod örneği public class RequestContext { public Guid RequestId { get; } = Guid.NewGuid(); }
bash//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım. builder.Services.AddScoped<RequestContext>();
bashpublic class OrderController : Controller { private readonly RequestContext _context1; private readonly RequestContext _context2; - public OrderController(RequestContext c1, RequestContext c2) { _context1 = c1; _context2 = c2; } - [HttpGet("/order")] public IActionResult Test() => Ok(new { _context1.RequestId, _context2.RequestId }); }
bash//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım. builder.Services.AddTransient<NotificationService>();
bash//Ufak bir kod örneği public class PriceCalculator { public Guid InstanceId { get; } = Guid.NewGuid(); }
bash//`Program.cs` veya `Startup.cs` dosyasında ayarlamamızı yapalım. builder.Services.AddTransient<PriceCalculator>();
bashpublic class CartController : Controller { private readonly PriceCalculator _p1; private readonly PriceCalculator _p2; - public CartController(PriceCalculator p1, PriceCalculator p2) { _p1 = p1; _p2 = p2; } - [HttpGet("/cart")] public IActionResult Test() => Ok(new { _p1.InstanceId, _p2.InstanceId }); }
p1 ve p2 farklıdır.