재우니의 블로그

 .NET Core 에서 Dependency Injection 알아보기

 

 

의존성(dependency ) 이 뭘까?

 

dependency 와 퐈이팅~하기 위해 .NET 이 제공하는 것을 탐색하기 전에 용어를 정리할 필요가 있습니다.

  • Dependency Inversion Principle : 소프트웨어 설계 원리입니다. 그것은 의존성 문제에 대한 해결책을 제시하지만 그것을 구현하는 방법이나 사용할 기술에 대해서는 말하지 않습니다.
  • Inversion of Control (IoC) : Dependency Inversion Principle 을 적용하는 방법입니다. Inversion of Control은 상위 수준 components 가 하위 수준 components 의 구체적인 구현이 아닌 추상화(abstraction) 에 의존하도록 하는 실제 메커니즘입니다.

    Inversion of Control
     할리우드 원리(Hollywood Principle) 라고도 합니다 . 이 이름은 헐리우드 영화 산업에서 따온 것입니다. 배우 오디션이 끝난 후 감독은 보통 " 우리에게 전화하지 마세요, 우리가 전화할 것 입니다. "라고 말합니다 .

 

  • Dependency Injection : Inversion of Control을 구현하기 위한 design pattern 입니다. low-level component 의 구체적인 구현을 high-level component 에 주입할 수 있습니다.

  • IoC Container : DI(Dependency Injection) Container 라고도 하며 components 의 자동 Dependency Injection 을 제공하는 프로그래밍 framework  입니다.

 

 

.NET Core 및 Dependency Injection

 

앞에서 설명한 세 가지 접근 방식 중 하나 이상을 사용하여 Dependency Injection 을 수동으로 구현할 수 있습니다. 그러나 .NET Core에는 Dependency Injection management 를 단순화하는 기본 제공 IoC Container 가 함께 제공됩니다.

IoC Container는 자동 Dependency Injection 을 지원합니다. 기본 기능은 다음과 같습니다.

 

  • 등록(Registration) : IoC 컨테이너는 특정 dependency 에 대해 생성할 객체  type을 알아야 합니다. 따라서 올바른 dependency instance 를 생성할 수 있도록 type class 에 매핑하는 방법을 제공합니다.

  • 해결(Resolution) : 이 기능을 사용하면 IoC Container 가 개체를 생성하고 이를 요청하는 class 에 주입하여 종속성을 해결할 수 있습니다. 이 기능 덕분에 종속성을 관리하기 위해 수동으로 개체를 인스턴스화할 필요가 없습니다.

  • Disposition : IoC Container 는 특정 기준에 따라 종속성의 수명을 관리합니다.

 

 

잠시 후 이러한 기능이 작동하는 것을 볼 수 있습니다. 그러나 그 전에 몇 가지 기본 정보가 필요합니다.

 

 

.NET Core 기본 제공 IoC Container  IServiceProvider 인터페이스를 구현합니다 . 따라서 어떤 이유로 자신만의 IoC Container 를 만들고 싶다면 이 인터페이스를 구현해야 합니다. .NET Core에서 컨테이너에서 관리하는 종속성을 services 라고 합니다 . 두 가지 유형의 서비스가 있습니다.

 

 

  • 프레임워크 서비스(Framework services)  : 이러한 서비스는 .NET Core 프레임워크의 일부입니다. 프레임 워크 서비스입니다 몇 가지 예로 IApplicationBuilder, IConfiguration, ILoggerFactory, 등이 있습니다.

  • 애플리케이션 서비스(Application services)  : 애플리케이션에서 생성하는 서비스입니다. IoC는 그것들을 모르기 때문에 명시적으로 등록해야 합니다.

 

프레임워크 서비스(Framework services) 다루기

 

.NET Core 개발자는 이미 기본 제공 IoC Container 를 사용하여 프레임워크 서비스를 주입했습니다. 실제로 .NET Core는 이에 크게 의존합니다. 예를 들어  ASP.NET 응용 프로그램의 Startup 클래스는 Dependency Injection 을 광범위하게 사용합니다.

 

public class Startup
{
    public Startup(IConfiguration configuration)
    {
            // ... code ...
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
            // ... code ...
    }
  
        // ... code ...
}

 

이 예에서 Startup() 생성자는 IConfigure 타입을 구현하는 configuration 매개 변수를 필요로 합니다. IConfigure는 프레임워크 서비스 유형 중 하나이기 때문에 IoC 컨테이너는 해당 인스턴스를 생성하고 생성자 주입 방식을 적용하는 Startup 클래스에 주입하는 방법을 알고 있습니다. Configure() 메서드도 마찬가지입니다. 그러나 표준 ASP.NET application 의 Startup() 생성자와 Configure() 메서드에 다음 프레임워크 서비스 유형만 주입할 수 있습니다.

(NET 응용 프로그램: IWebHostEnvironment, IHostEnvironment 및 IConfigure)

해당 프레임워크 서비스는 등록할 필요가 없기 때문에 특별한 경우입니다.

 

 

프레임워크 서비스 등록

 

일반적으로 ASP.NET application 에 필요한 서비스를 등록해야 합니다. Startup  클래스의 ConfigureServices() 메서드에 있는 응용 프로그램입니다. 이 메서드에는 응용 프로그램이 의존하는 서비스 목록을 나타내는 IServiceCollection 매개 변수가 있습니다. 실제로 이 매개 변수로 표시되는 컬렉션을 통해 IoC Containerservice 를 등록할 수 있습니다. 다음 예를 생각해 보십시오.

public class Startup
{
         // ... code ...
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            });
    }
  
        // ... code ...
}

 

여기에서는 응용 프로그램에 대한 authentication 을 관리하기 위한 종속성을 등록합니다. 이 경우 사용할 authentication  유형을 설명하는 매개 변수와 함께 extension method 형식으로 AddAuthentication()을 사용하고 있습니다.

이 프레임워크는 가장 일반적인 서비스에 대한 종속성을 등록하고 구성할 수 있는 extension method 을 제공합니다. 또한 다음 예와 같이 일반 dependencies 을 등록할 수 있는 Add() 메소드를 제공합니다.

 

 

public class Startup
{
         // ... code ...
    public void ConfigureServices(IServiceCollection services)
    {
        services.Add(new ServiceDescriptor(typeof(ILog), new MyLogger()));  
    }
  
        // ... code ...
}

 

이 경우 ILog 인터페이스를 구현하는 로그 서비스를 등록하는 것입니다. Add() 메소드의 두 번째 매개 변수는 프로젝트에서 구현한 MyLogger 클래스의 인스턴스입니다. 짐작할 수 있듯이, 이 등록은 응용 프로그램에서 오는 모든 요청을 이행할 myLogger 클래스의 단일 인스턴스, 즉, singleton service 를 생성합니다.

 

서비스 수명 (Service lifetimes)

 

종속성의 이 single instance 는 애플리케이션의 전체 수명 동안 유지됩니다. logger 와 같은 서비스에는 적합할 수 있지만 다른 서비스에는 허용되지 않습니다. IoC Container 를 사용하여 등록된 서비스의 수명을 제어할 수 있습니다. 수명을 지정하는 서비스를 등록하면 컨테이너가 자동으로 그에 따라 해당 서비스를 처리합니다. 서비스 수명은 세 가지가 있습니다.

 

 

  • Singleton: 이 수명은 서비스의 인스턴스 오직 하나를 만듭니다. 위의 예에서 보듯이 서비스 인스턴스는 등록 시 Add() 메소드를 사용하여 생성할 수 있습니다. 또는 서비스 인스턴스를 처음 요청할 때 AddSingleton() 메서드를 사용하여 만들 수 있습니다.
  • Transient: 이 수명을 사용하면 서비스가 요청될 때마다 생성됩니다. 예를 들어 클래스 생성자에 삽입된 서비스는 해당 클래스 인스턴스가 존재하는 한 지속됩니다. 임시 수명으로 서비스를 생성하려면 AddTransient() 메서드를 사용해야 합니다.
  • Scoped : scoped lifetime 을 통해 각 클라이언트 요청에 대한 서비스 인스턴스를 만들 수 있습니다. 이것은 HTTP 요청 처리 기간 동안 동일한 서비스 인스턴스를 공유할 수 있으므로 ASP.NET context 에서 특히 유용합니다. scoped lifetime 을 실행하려면 AddScoped() 방법을 사용해야 합니다.

 

 

사용하려는 서비스의 올바른 수명을 선택하는 것은 애플리케이션의 올바른 동작과 더 나은 리소스 관리를 위해 모두 중요합니다.

 

 

Application Services 관리


프레임워크 서비스를 통해 배운 대부분의 개념은 여전히 애플리케이션 서비스에 유효합니다. 그러나 프레임워크 서비스는 이미 주입이 가능하도록 설계되어 있다. 프로그램에서 정의하는 클래스는 종속성 주입을 활용하고 IoC 컨테이너와 통합되도록 조정해야 합니다.

Dependency Injection 기법을 적용하는 방법을 보려면 이 글의 앞부분에 나와 있는 주문 관리 예를 다시 불러오고 이러한 클래스가  ASP.NET application 내에 있다고 가정합니다. 터미널 창에 다음 명령을 입력하여 이 GitHub 저장소에서 해당 응용 프로그램의 코드를 다운로드할 수 있습니다.

 

git clone -b starting-point --single-branch https://github.com/auth0-blog/dependency-injection-dotnet-core

 

github 을 통해 소스를 가져오며, 소스 폴더에는 ASP.NET Core project 를 포함하는 OrderManagementWeb 하위 폴더가 있습니다.  이 섹션에서는 Dependency Injection 및 Dependency  IoC Container 를 활용해 보도록 해당 프로젝트 수정하면서 진행하려 합니다.

 

 

abstractions 정의


Dependency Inversion Principle 이 시사하듯이, modules 은 추상화(abstractions) 에 의존해야 합니다. 따라서 이러한 abstractions 를 정의하기 위해 Interfaces 에 의존할 수 있습니다.

OrderManagementWeb 폴더에 Interfaces 하위 폴더를 추가합니다. Interfaces 폴더에서 다음 내용이 포함된 IOrderSender.cs 파일을 추가합니다.

 

// OrderManagementWeb/Interfaces/IOrderSender.cs

using System.Threading.Tasks;
using OrderManagement.Models;

namespace OrderManagement.Interfaces
{
    public interface IOrderSender
    {
        Task<string> Send(Order order);
    }
}

 

IOrderManager.cs 인터페이스 생성하여 아래 구문을 추가합니다.

// OrderManagementWeb/Interfaces/IOrderManager.cs

using System.Threading.Tasks;
using OrderManagement.Models;

namespace OrderManagement.Interfaces
{
    public interface IOrderManager
    {
        public Task<string> Transmit(Order order);
    }
}

 

 

abstractions(추상화)에 의존성(Depending) 하기

 

 

첫 번째 단계로 IOrderSender 인터페이스를 구현하도록 OrderSender 클래스를 재정의해야 합니다.

// OrderManagementWeb/Managers/OrderSender.cs

using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using OrderManagement.Interfaces;
using OrderManagement.Models;

namespace OrderManagement
{
    public class HttpOrderSender : IOrderSender
    {
        private static readonly HttpClient httpClient = new HttpClient();

        public async Task<string> Send(Order order)
        {
            var jsonOrder = JsonSerializer.Serialize<Order>(order);
            var stringContent = new StringContent(jsonOrder, UnicodeEncoding.UTF8, "application/json");

            //This statement calls a not existing URL. This is just an example...
            var response = await httpClient.PostAsync("https://mymicroservice.lan/myendpoint", stringContent);

            return response.Content.ReadAsStringAsync().Result;

        }
    }
}

 

 

IOrderManager interface 를 상속받고, 생성자 dependency  를 통해 new 인스턴스 코딩 기술 없이 바로 Send() 함수를 사용한걸 확인 가능합니다.

// OrderManagementWeb/Managers/OrderManager.cs

using System.Threading.Tasks;
using OrderManagement.Interfaces;
using OrderManagement.Models;

namespace OrderManagement
{
    public class OrderManager : IOrderManager
    {
        private IOrderSender orderSender;

        public OrderManager(IOrderSender sender)
        {
            orderSender = sender;
        }

        public async Task<string> Transmit(Order order)
        {
            return await orderSender.Send(order);
        }
    }
}

 

dependencies 등록하기

 

생성한 인터페이스와 이를 기반으로 기술한 클래스를 AddScoped() 를 통해 매칭 구현한 부분입니다.

추상화를 기반으로 종속성을 관리하는 방법을 IoC Container에 알려야 합니다. 즉, 종속성을 등록해야 합니다.

이 등록은 프로젝트의 Startup 클래스에서 발생합니다. 따라서 Startup.cs 파일을 열고 ConfigureServices() 메서드를 다음으로 바꿉니다. AddScoped() 메서드의 제네릭 버전을 사용하여 dependencies 을 등록했습니다.

여기서 IOrderSender 유형에 대한 요청이 감지될 때마다 HttpOrderSender 클래스의 인스턴스를 생성하도록 IoC Container에 요청합니다. 마찬가지로 IOrderManager 유형이 요청될 때 OrderManager 클래스의 인스턴스를 생성해야 합니다.

// OrderManagementWeb/Startup.cs

public class Startup
{
    // ...code ...
  
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddScoped<Interfaces.IOrderSender, HttpOrderSender>();
            services.AddScoped<Interfaces.IOrderManager, OrderManager>();
        }

    // ...code ...

}

 

의존성 주입하기 (Injecting the dependencies)

 

이제 이러한 모든 종속성을 실제로 사용할 때입니다. 따라서 Controllers 폴더에서 OrderController.cs 파일을 열고 해당 내용을 다음 코드로 바꿉니다.

 

// OrderManagementWeb/Controllers/OrderController.cs

using Microsoft.AspNetCore.Mvc;
using OrderManagement.Interfaces;
using OrderManagement.Models;

namespace OrderManagement.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        private IOrderManager orderManager;

        public OrderController(IOrderManager orderMngr)
        {
            orderManager = orderMngr;
        }

        [HttpPost]
        public ActionResult<string> Post(Order order)
        {
            return Ok(orderManager.Transmit(order));
        }
    }
}

 

Web API 컨트롤러에서 private 의 orderManager 변수를 정의합니다. 해당 값은 IOrderManager 인터페이스를 구현하는 서비스의 인스턴스를 나타냅니다. 이 변수의 값은 IOrderManager 유형 매개변수가 있는 생성자에 할당됩니다.

 

이 매개변수를 사용하여 생성자는 해당 유형의 IoC Containerservice collection 를 요청합니다. IoC 컨테이너는 service collection 에서 이 요청을 충족할 수 있는 등록을 검색하고 이러한 인스턴스를 컨트롤러에 전달합니다.

 

즉, OrderManager 클래스의 인스턴스를 생성하는 동안 IoC 컨테이너는 IOrderSender 추상화에 대한 내부 종속성도 해결합니다.

 

이 예제에서 알 수 있듯이 생성자 주입은 IoC 컨테이너가 클래스에 종속성을 주입하는 데 사용하는 기본 접근 방식입니다. 메서드 주입 방식을 사용하려면 FromServices 속성을 사용하여 명시적으로 요청해야 합니다. 이 예에서는 이 속성을 사용하는 방법을 보여줍니다.

 

[HttpPost]
public ActionResult<string> Post([FromServices]IOrderManager orderManager)
{
    return Ok(orderManager.Transmit(order));
}

 

 

출처 사이트

 

번역 : https://auth0.com/blog/dependency-injection-in-dotnet-core/

 

Understanding Dependency Injection in .NET Core

Learn what Dependency Injection and IoC are and what .NET Core provides you to support them.

auth0.com