📌 .NET에서 Dependency Injection(DI) 기초 가이드
안녕하세요😊
오늘은 .NET에서 제공하는 의존성 주입(Dependency Injection, DI)에 대해 깊이 있게 다뤄보겠습니다.
DI는 현대적인 .NET 애플리케이션에서 유지보수성과 확장성을 높이는 핵심 개념입니다.
이 글을 끝까지 읽으시면 DI의 개념부터 고급 활용법까지 완벽히 이해하실 수 있을 거예요!
✅ 1. 의존성 주입(DI)이란?
🔹 1.1 의존성 문제와 DI의 필요성
개발을 하다 보면 클래스 간의 의존성(Dependency)이 강해지는 경우가 많습니다.
예를 들어, OrderService에서 PaymentService를 직접 생성하면 다음과 같은 문제가 발생합니다.
public class OrderService
{
private readonly PaymentService _paymentService;
public OrderService()
{
_paymentService = new PaymentService(); // 강한 결합 (Tightly Coupled)
}
}
❌ 문제점
- OrderService가 PaymentService의 구체적인 구현을 직접 알고 있어야 함
- PaymentService의 변경이 OrderService에도 영향을 줌
- 테스트가 어렵다! → PaymentService를 Mocking하기 어려움
이러한 문제를 해결하기 위해 의존성 주입(DI)이 등장했습니다.
🔹 1.2 DI 적용 후
DI를 적용하면 다음과 같이 코드를 개선할 수 있습니다.
public class OrderService
{
private readonly IPaymentService _paymentService;
public OrderService(IPaymentService paymentService) // 생성자 주입
{
_paymentService = paymentService;
}
}
✅ DI의 장점
✔ 객체 간 결합도 감소 → 변경이 유연해짐
✔ Mocking을 쉽게 적용 가능 → 단위 테스트(Unit Test) 가능
✔ 유지보수성과 확장성이 높아짐
✅ 2. .NET의 DI 컨테이너 사용법
🔹 2.1 기본 DI 컨테이너 설정
.NET에서는 내장된 DI 컨테이너(IServiceCollection)를 제공합니다.
서비스 등록은 Program.cs에서 아래처럼 하면 됩니다.
var builder = WebApplication.CreateBuilder(args);
// 서비스 등록
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddSingleton<IPaymentService, PaymentService>();
var app = builder.Build();
app.Run();
🔹 2.2 서비스 등록 방법
.NET DI 컨테이너에서는 세 가지 수명 주기(Lifetime)를 지원합니다.
수명 주기메서드특징
Transient | AddTransient<TInterface, TImplementation>() | 요청 시마다 새 인스턴스 생성 |
Scoped | AddScoped<TInterface, TImplementation>() | HTTP 요청당 하나의 인스턴스 유지 |
Singleton | AddSingleton<TInterface, TImplementation>() | 애플리케이션 종료 시까지 동일한 인스턴스 사용 |
✔ 수명 주기 예제
builder.Services.AddTransient<IProductService, ProductService>(); // 요청마다 새 객체
builder.Services.AddScoped<IUserService, UserService>(); // 요청 내에서 유지
builder.Services.AddSingleton<ILoggerService, LoggerService>(); // 앱 실행 동안 동일 객체
✅ 3. DI 컨테이너의 동작 원리
🔹 3.1 DI 컨테이너 내부 원리
DI 컨테이너는 다음 3단계 과정으로 동작합니다.
1️⃣ 서비스 등록 → IServiceCollection을 사용해 인터페이스-구현체를 등록
2️⃣ 객체 생성 → IServiceProvider가 의존성을 분석하여 객체를 생성
3️⃣ 주입(Injection) 실행 → 필요한 곳에 인스턴스를 전달
🔹 3.2 IServiceProvider 활용
IServiceProvider를 직접 활용해 수동으로 인스턴스를 생성할 수도 있습니다.
var serviceProvider = new ServiceCollection()
.AddSingleton<ILoggerService, LoggerService>()
.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService<ILoggerService>();
logger.Log("DI 컨테이너 직접 사용");
✅ 4. DI를 활용한 패턴과 실전 예제
🔹 4.1 Factory 패턴과 DI 활용
Factory 패턴을 활용하면 동적으로 객체를 생성할 수 있습니다.
public interface ICar { void Drive(); }
public class BMW : ICar { public void Drive() => Console.WriteLine("BMW Driving..."); }
public class CarFactory
{
private readonly IServiceProvider _serviceProvider;
public CarFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
public ICar CreateCar() => _serviceProvider.GetRequiredService<ICar>();
}
🔹 4.2 Middleware에서 DI 사용
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILoggerService _logger;
public LoggingMiddleware(RequestDelegate next, ILoggerService logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
_logger.Log("Request Started");
await _next(context);
_logger.Log("Request Ended");
}
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<LoggingMiddleware>();
}
🔹 4.3 HostedService에서 DI 사용
public class MyBackgroundService : BackgroundService
{
private readonly ILogger<MyBackgroundService> _logger;
public MyBackgroundService(ILogger<MyBackgroundService> logger) => _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Background task running...");
await Task.Delay(1000, stoppingToken);
}
}
}
builder.Services.AddHostedService<MyBackgroundService>();
✅ 5. Keyed DI (NET 8 이상 기능)
.NET 8에서는 Keyed DI 기능이 추가되었습니다.
builder.Services.AddKeyedSingleton<IMessageSender, EmailSender>("email");
builder.Services.AddKeyedSingleton<IMessageSender, SmsSender>("sms");
var provider = builder.Build().Services;
var emailSender = provider.GetKeyedService<IMessageSender>("email");
emailSender.SendMessage("Hello Email");
✅ 6. 정리 및 공식 문서 링크
오늘은 .NET의 DI(Dependency Injection) 개념부터 고급 패턴까지 살펴보았습니다.
DI를 적극 활용하면 코드 유지보수성, 테스트 용이성, 확장성이 크게 향상됩니다! 🚀
📌 관련 공식 문서
🔹 .NET Dependency Injection 개요
🔹 서비스 수명 주기 (Lifetime) 공식 문서
🔹 ASP.NET Core에서 DI 적용하기
'Language > C#' 카테고리의 다른 글
[C#] C# 클로저(Closure) 쉽게 이해하기 (0) | 2025.02.07 |
---|---|
[C#] DotNetty로 TCP 소켓 통신 구현: Unity와 게임 서버 간 패킷 교환하기 (0) | 2025.01.03 |
[C#] protobuf-net 프로토콜 (0) | 2025.01.02 |
[C#] 서버 성능 테스트(Performance Test) 체험 (0) | 2024.12.19 |
[C#] 데드락(Deadlock)과 예시 (1) | 2024.06.14 |