ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ASP.NET Core] AddSingleton(), AddScoped(), AddTransient() 차이점 - 2
    .NET/ASP.NET Core 2023. 3. 15. 21:47
    반응형

    해당 포스트 작성하기 약 2년 전, ASP.NET Core 에서 3가지 생명주기에 대해 정리한 적이 있었다.

    [ASP.NET Core] AddSingleton(), AddScoped(), AddTransient() 차이점 - 1 :: 또치의 삽질 보관함 (tistory.com)

     

    [ASP.NET Core] AddSingleton(), AddScoped(), AddTransient() 차이점 - 1

    ASP.NET Core에서 의존성 주입에 의해 추가되는 Service는 크게 3가지 생명주기를 지닌다. AddSingleton AddScoped AddTransient AddSingleton의 경우는 클라이언트(보통 웹브라우저)의 접속상태에 관계없이, 웹 서

    ddochea.tistory.com

     

    지난시간에 Scoped로 주입된 서비스 객체는 Request 시작 ~ Response 작업까지 생명주기를 갖고 있다고 했었다. Transient는 Scoped 와 달리, 인젝션 된 객체마다 독립적인 메모리로 생성된다고 했었는데, 페이지가 없는 WebAPI 에서 Controller로 바로 쓰고 끝나는 서비스의 경우라면 Scoped를 썼을때와의 차이를 잘 느낄 수 없게 된다.

    그러나 Controller가 아닌 Repository와 같은 별도 서비스를 만들고, 해당 서비스내에서 의존성 주입처리한 또 다른 서비스가 존재할 경우, 그 차이점을 확인할 수 있을 것이다.

    아래 순서에 따라 예제를 구현하고 직접 확인해보도록 하자. 프로젝트는 net6.0 기준으로 생성하였다.

    1. WebApi 프로젝트 생성

    WebApi 프로젝트를 생성 후 Service 폴더를 만들어 OuterService.cs, InnerService.cs 클래스를 생성한다.

    2. InnerService.cs 코드 작성

    아래와 같이 InnerService.cs 파일에 코드를 작성한다. 현재 설정된 ServiceState 값을 가져오는게 전부인 서비스이다. enum은 편의상 cs 파일 내에 추가했다.

    public enum ServiceState {
        Init,
        Set,
        Running,
        Stopped
    }
    
    public class InnerService
    {
        public ServiceState State { get; set; } = ServiceState.Init;
        public readonly ILogger<InnerService> _logger;
    
        public InnerService(ILogger<InnerService> logger)
        {
            _logger = logger;
        }
    
        public string GetState()
        {
            _logger.LogInformation("Call State {State}", State);
            return $"State: {State}";
        }
    }

    3. OuterService.cs 코드 작성

    innerService의 State 값을 설정하는 OuterService 코드를 작성한다.

    public class OuterService
    {
        public readonly InnerService _innerService;
        public readonly ILogger<OuterService> _logger;
    
        public OuterService(
            ILogger<OuterService> logger,
            InnerService innerService)
        {
            _logger = logger;
            _innerService = innerService;        
        }
    
        public void SetInnerService(ServiceState state)
        {
            _logger.LogInformation("Set innserService State {State}", state);
            _innerService.State = state;
        }
    }

    4. Controller 작성

    의존성 주입된 OuterService와 InnerService의 설정된 State 값 이력을 반환하는 Get 메소드를 가진 Controller 코드를 작성한다.

    [ApiController]
    [Route("api/[controller]")]
    public class MyController : ControllerBase
    {
        private readonly ILogger<MyController> _logger;
        private readonly OuterService _outerService;
        private readonly InnerService _innerService;
    
        public MyController(ILogger<MyController> logger, OuterService outerService, InnerService innerService)
        {
            _logger = logger;
            _outerService = outerService;
            _innerService = innerService;
        }
    
        [HttpGet]
        public string Get()
        {
            string result = string.Empty;
            result = $"{result}{ _innerService.GetState()}{Environment.NewLine}";
            _outerService.SetInnerService(ServiceState.Set);
            result = $"{result}{ _innerService.GetState()}{Environment.NewLine}";
            _outerService.SetInnerService(ServiceState.Running);
            result = $"{result}{ _innerService.GetState()}{Environment.NewLine}";
            return result;
        }
    }

    5. 의존성 주입 코드 작성

    Program.cs 에서 OuterService와 InnerService를 AddScoped 함수로 의존성 주입하는 코드를 추가한다.

    builder.Services.AddScoped<OuterService>(); 
    builder.Services.AddScoped<InnerService>();

    6. 실행 및 Get 호출로 결과 확인

    코드 작성 후 Get 호출하면 아래와 같은 결과가 나올 것이다.

    State: Init
    State: Set
    State: Running

    dotnet run 명령어 또는 생성한 프로젝트의 profile로 실행시켰다면 콘솔창이 함께 표기되며, 호출시 아래와 같은 로그가 기록되었을 것이다.

    info: InnerService[0]
          Call State Init
    info: OuterService[0]
          Set innserService State Set
    info: InnerService[0]
          Call State Set
    info: OuterService[0]
          Set innserService State Running
    info: InnerService[0]
          Call State Running

    뭔가 이상하지 않은가? GetState를 호출한 함수는 Controller에서 가져온 innerService 객체의 것이다. 그리고 설정을 호출하는 SetInnerService 함수는 Controller의 innerService가 아닌, 자기 자신안에 있는 innerService 객체를 사용한다. 그럼에도 불구하고, Controller 내 innerService 객체의 State값이 바뀌었다.

     

    이것이 Scope 범위의 특성이다. 의존성 주입된 서비스객체는 1개의 Request ~ Response 싸이클 동안 고유하다.

     

    이번엔 InnerService를 AddTransient 로 바꿔보자. 결과는 아래와 같이 바뀔 것이다.

    State: Init
    State: Init
    State: Init

    로그를 보면 OuterService 내부에 있는 innerService 객체의 State를 각각 Set, Running으로 설정했지만, 결과는 여전히 Init으로 나오는 것을 확인할 수 있다.

    info: InnerService[0]
          Call State Init
    info: OuterService[0]
          Set innserService State Set
    info: InnerService[0]
          Call State Init
    info: OuterService[0]
          Set innserService State Running
    info: InnerService[0]
          Call State Init

    지난 포스트에서 Transient 는 아래와 같이 설명했었다.

     

    Transient는 요청,응답 관계없이 사용순간마다 생성되므로, 주입된 2개의 GuidService가 각각 다른 객체로 사용될 수 있었던 것이다.

     

    좀더 명확하게 설명하자면 Transient로 선언된 innerService는 Controller 에서 주입된 객체와, outerService에서 주입된 객체가 각각 다름을 의미한다. 따라서 요청(request)과 응답(response)에 관계가 없다. 주입된 위치에서 각각 고유한 객체가 된다.

    반응형

    댓글

Designed by Tistory.