아래는 ASP.NET Core 8에서 DI(Dependency Injection)를 활용해 Singleton, Scoped, Transient의 차이점을 이해할 수 있도록 난수(Random)를 생성하는 예제를 제공합니다. 이 예제에서는 각 서비스가 생성될 때마다 난수를 할당하고, 각 DI 라이프사이클(생명주기)에 따라 인스턴스가 어떻게 다르게 동작하는지를 보여줍니다.
1. 서비스 인터페이스 및 구현
먼저, 각 라이프사이클별로 사용할 인터페이스와 클래스를 정의합니다. 각 클래스는 생성자에서 new Random().Next(1, 1000)을 호출하여 난수를 생성하고, 이 값을 프로퍼티에 저장합니다.
namespace DIExample
{
// Singleton 인터페이스 및 구현
public interface IRandomSingleton
{
int RandomValue { get; }
}
public class RandomSingletonService : IRandomSingleton
{
public int RandomValue { get; }
public RandomSingletonService()
{
// 애플리케이션 전체에서 단 한 번 생성
RandomValue = new Random().Next(1, 1000);
}
}
// Scoped 인터페이스 및 구현
public interface IRandomScoped
{
int RandomValue { get; }
}
public class RandomScopedService : IRandomScoped
{
public int RandomValue { get; }
public RandomScopedService()
{
// HTTP 요청 당 한 번 생성
RandomValue = new Random().Next(1, 1000);
}
}
// Transient 인터페이스 및 구현
public interface IRandomTransient
{
int RandomValue { get; }
}
public class RandomTransientService : IRandomTransient
{
public int RandomValue { get; }
public RandomTransientService()
{
// 호출 시마다 새로운 인스턴스 생성
RandomValue = new Random().Next(1, 1000);
}
}
}
2. DI 컨테이너에 서비스 등록 (Program.cs)
ASP.NET Core 8의 Minimal API 스타일을 사용하여 DI 컨테이너에 각 서비스를 등록합니다.
Singleton: AddSingleton을 사용해 애플리케이션 전체에서 단 한 번 인스턴스를 생성합니다.
Scoped: AddScoped를 사용해 HTTP 요청당 한 번 인스턴스를 생성합니다.
Transient: AddTransient를 사용해 요청 시마다 새로운 인스턴스를 생성합니다
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<DIExample.IRandomSingleton, DIExample.RandomSingletonService>();
builder.Services.AddScoped<DIExample.IRandomScoped, DIExample.RandomScopedService>();
builder.Services.AddTransient<DIExample.IRandomTransient, DIExample.RandomTransientService>();
var app = builder.Build();
3. 여러 인스턴스 주입하여 검증 (Program.cs)
이번 예제에서는 각 라이프사이클에 대해 두 개의 인스턴스를 주입받습니다.
Singleton: singleton1과 singleton2는 동일 인스턴스이므로 동일한 난수 값을 반환해야 합니다.
Scoped: 같은 HTTP 요청 내에서는 scoped1과 scoped2가 동일 인스턴스를 참조합니다.
Transient: 매번 새 인스턴스가 생성되므로 transient1과 transient2는 서로 다른 난수 값을 반환합니다.
예를 들어, /test 엔드포인트에 HTTP GET 요청을 보내면 예상되는 JSON 응답은 다음과 같습니다:
{
"Singleton1": 543,
"Singleton2": 543, // 동일한 값: 단일 인스턴스
"Scoped1": 235,
"Scoped2": 235, // 동일한 요청 내에서는 동일 인스턴스 사용
"Transient1": 789,
"Transient2": 123 // 매번 새로운 인스턴스이므로 다른 값
}
Singleton
singleton1과 singleton2가 동일한 인스턴스를 주입받으므로 항상 동일한 난수 값이 출력됩니다.
애플리케이션이 시작될 때 한 번 생성된 인스턴스입니다.
이후 모든 요청에서 동일한 난수 값을 반환합니다.
Scoped
동일 HTTP 요청 내에서는 scoped1과 scoped2가 동일 인스턴스를 공유하여 같은 난수 값을 출력합니다.
만약 새로운 HTTP 요청이 발생하면 새로운 인스턴스가 생성되어 다른 값이 할당됩니다.
각 HTTP 요청마다 인스턴스가 하나 생성됩니다.
동일한 요청 내에서는 같은 값을 반환하지만, 새로운 요청이 들어오면 다른 값이 생성됩니다.
Transient
transient1과 transient2는 각각 별도의 인스턴스가 생성되어 서로 다른 난수 값을 반환합니다.
요청할 때마다 매번 새로운 인스턴스가 생성됩니다.
같은 요청 내에서도 transient1과 transient2는 서로 다른 난수 값을 가집니다.
차이점 요약
Singleton
범위: 애플리케이션 전체
특징: 최초 생성 이후 모든 요청 및 서비스에서 동일한 인스턴스 사용
사용 예: 상태를 유지해야 하거나, 인스턴스 생성 비용이 큰 경우
Scoped
범위: HTTP 요청 단위
특징: 같은 요청 내에서는 인스턴스가 공유되며, 요청이 바뀌면 새로운 인스턴스 생성
사용 예: 요청마다 별도의 상태 관리가 필요한 경우
Transient
범위: 매번 새 인스턴스 생성
특징: 요청할 때마다 새로운 인스턴스가 주입되어, 동일 요청 내에서도 여러 번 호출 시 값이 다름
사용 예: 가벼운 작업이나 상태를 저장하지 않는 서비스에 적합
5. 결론
이 예제를 통해 ASP.NET Core 8의 DI 컨테이너에서 Singleton, Scoped, Transient의 동작 방식을 명확하게 이해할 수 있습니다.
Singleton은 애플리케이션 전역에서 동일한 인스턴스를 사용하여 메모리 사용과 인스턴스 생성 비용을 절감할 수 있습니다.
Scoped는 HTTP 요청 단위로 인스턴스를 생성하여, 요청 간에 독립적인 상태를 유지할 때 유용합니다.
Transient는 매번 새 인스턴스를 제공하므로, 짧은 작업이나 무상태(stateless) 서비스에 적합합니다.
이러한 차이를 이해하고 활용하면, 애플리케이션의 상태 관리와 메모리 사용, 성능 최적화 측면에서 올바른 DI 라이프사이클을 선택할 수 있습니다.