재우니의 블로그

 

 

아래는 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는 서로 다른 난수 값을 반환합니다.
app.MapGet("/test", (
    DIExample.IRandomSingleton singleton1, DIExample.IRandomSingleton singleton2,
    DIExample.IRandomScoped scoped1, DIExample.IRandomScoped scoped2,
    DIExample.IRandomTransient transient1, DIExample.IRandomTransient transient2) =>
{
    return Results.Json(new {
        Singleton1 = singleton1.RandomValue,
        Singleton2 = singleton2.RandomValue,
        Scoped1 = scoped1.RandomValue,
        Scoped2 = scoped2.RandomValue,
        Transient1 = transient1.RandomValue,
        Transient2 = transient2.RandomValue
    });
});

app.Run();

4. 실행 결과 및 검증 방법

예를 들어, /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 라이프사이클을 선택할 수 있습니다.

 

 

 

참고 유투브

 

https://www.youtube.com/watch?v=V-8HlozCTOU&t=329s