센로그

9. Dependency Injection (DI) 본문

게임/ASP.NET Core

9. Dependency Injection (DI)

seeyoun 2024. 4. 15. 23:16

◆ Dependency Injection

종속성 주입. 웹 개발시 유용한 패턴이라 많은 웹 서버 플랫폼에 도입된 기능이다.

 

디자인 패턴에서, 우리는 코드간의 종속성을 줄이는 것을 중요하게 생각함 (loosely coupled)

 

그러면 종속성이란 무엇이냐?

 

우선 종속성과 관련된 예시를 보자.

public class FileLogSettings
{
    string _filename;
    public FileLogSettings(string filename)
    {
        _filename = filename; 
    }
}
public class FileLogger
{
    FileLogSettings _settings;
    public FileLogger(FileLogSettings settings)
    {
        _settings = settings;
    }

    public void Log(string log)
    {
        Console.Write($"Log OK {log}");
    }
}
public class HomeController : Controller
{

    public FileLogger _logger;

     public HomeController()
     {
         _logger = new FileLogger(new FileLogSettings("log.txt"));
     }

}
  • FileLogger가 FileLogSettings를 사용하고, HomeController가 FileLogger을 사용하는 경우.
  • 의존성이 강하다.
    • FileLogger가 바뀌는 경우 HomeController에도 영향을 줄 수 있다.
    • 만약 시스템이 바뀌어서 FileLogger가 아닌 DBLogger로 로깅하도록 바뀌는 경우, 코드를 많이 수정해야 함.
  • 매번 새로운 인스턴스를 생성해서 사용해야 하는게 좀 걸린다.

 


.NET 에서는 이런 경우 종속성을 줄이기 위해, 종속성 주입(DI)를 제공한다.

 

종속성 주입이란?

  • (위 예시처럼) 필요할 때마다 new로 객체 인스턴스를 만들어주는 것이 아니라,
    객체 인스턴스들을 미리 어딘가에서 관리하다가 필요한 경우 꽂아주는 것. 
    • 기본적으로 생성자에서 꽂아주는 게 정석.

 

참고) 일반적으로 생성자에 DI를 하는 게 국룰이지만 .. Action에 대해서 DI를 할 수도 있긴 하다.

  • [FromServices] 이용하면 됨

가능은 한데 뭐.. 별로 안 씀

 


DI 동작 과정

Request가 Routing이 완료되면, DI가 동작한다.

 

DI 동작 과정은 다음과 같다.

  1. Controller Activator 작동
    • DI Container에게 Controller 생성 및 알맞는 Dependency 연결 위탁
      • 만약 요청한 Dependency를 못 찾으면 Error
  2. DI Container 임무 실시
  3. Controller 생성됨

 


DI를 통해 서비스 추가

DI는 Program.cs에서 builder.Services.AddControllersWithViews(); 밑에다 추가해주면 된다.

 

이때 다음 세 가지를 지정하여 등록해주면 된다.

  • Service Type (인터페이스 or 클래스)
  • Implementation Type (클래스)
  • LifeTime (Transient, Scoped, Singleton)
    • AddTransient
      • 항상 새로운 서비스 instance를 만든다
    • AddScoped
      • 한 요청 안에서는 동일한 서비스 instance를 사용한다
      • 가장 일반적으로 많이 사용됨.
      • DbContext, Authentication 등에 사용. 
    • AddSingleton
      • 항상 동일한 서비스 instance를 사용한다
      • 웹에서의 싱글톤은 항상 thread-safe 해야함!! 주의할 것.

 

 

위의 예시 코드를 DI 패턴을 활용해 수정한 코드를 보자.

public interface IBaseLogger
{
    public void Log(string log);
}
public class FileLogSettings
{
    string _filename;
    public FileLogSettings(string filename)
    {
        _filename = filename; 
    }
}
public class FileLogger : IBaseLogger
{
    FileLogSettings _settings;
    public FileLogger(FileLogSettings settings)
    {
        _settings = settings;
    }

    public void Log(string log)
    {
        Console.WriteLine($"Log OK {log}");
    }
}
public class HomeController : Controller
{
    public IBaseLogger _logger;

    public HomeController(IBaseLogger logger)
    {
        _logger = logger;
    }
}

 

만약 위 코드에서 HomeController의 _logger에다 FileLogger을 넣고싶다고 하자.

Program.cs에 다음 DI 코드를 추가해주기만 하면 된다,

 

// 각종 서비스 추가 (DI)
builder.Services.AddControllersWithViews();

//IBaseLogger을 요청한 경우 FileLogger로 연결해주도록 함
builder.Services.AddSingleton<IBaseLogger, FileLogger>();

//FileLogSettings는 일괄적으로 "log.txt"를 파라미터로 생성한 이 객체를 쓰기로 함
builder.Services.AddSingleton(new FileLogSettings("log.txt"));
  • 매번 new 코드를 작성하는 것이 아니라, DI를 통해 각각 어떤 클래스와 연결할지 지정해줄 수 있다.
  • FileLogger가 DBLogger로 바뀌는 경우, DI 시점에서 <IBaseLogger, DBLogger>로 교체해주기만 하면 된다.
  • AddSingleton방식인 건 그냥 예제라 쓴 것임. 경우에 맞는 Lifetime을 적용하면 된다.

 


DI를 통해 다수의 서비스 추가

원한다면, 동일한 인터페이스에 대해 다수의 서비스를 등록할 수도 있다.

IEnumerable 형식을 사용하면 된다.

 

  • FileLogger도 찍고 싶고, DBLogger도 찍고싶은 경우를 의미한다.
  • 컨트롤러에서 IBaseLogger 형태로 로거를 받아오던 걸 IEnumerable<IBaseLogger> 이런 식으로 바꿔주면 됨.
    • 물론 로깅 구현할 때도 foreach 써서 로깅해줘야 함
  • DI 시에는, 다음과 같이 그냥 여러 개 연결해주면 됨
    builder.Services.AddControllersWithViews();
    
    builder.Services.AddSingleton<IBaseLogger, FileLogger>();
    builder.Services.AddSingleton<IBaseLogger, DBLogger>();
    • 만약 기존처럼 IEnumerable 형태가 아닌 단일 변수인 경우에는, 나중에 연결한 부분(DBLogger)이 적용됨.

 


Razor View Template (.cshtml)에서도 서비스가 필요하다면?

 

여기서는 생성자라는 개념이 따로 없으므로, 다른 걸 사용한다.

  • @inject

 


DI 사용시 주의점 - 생명 주기

주의해야 할 점은, 당연한 얘기지만, 어떤 서비스에서 DI 부품을 사용한다면

부품들의 생명 주기는 최소한 서비스의 생명 주기보다는 길거나 같아야 한다.

 

다음과 같이 쓰면 안된다는 얘기다.

builder.Services.AddControllersWithViews();

builder.Services.AddSingleton<IBaseLogger, FileLogger>();

builder.Services.AddTransient(new FileLogSettings("log.txt"));
  • FileLogger 내부에서 FileLogSettings를 사용하는 구조일 때,
    위와 같이 FileLogSettings의 생명 주기가 FileLogger보다 짧으면 안된다.

 

'게임 > ASP.NET Core' 카테고리의 다른 글

11. Filter  (0) 2024.04.15
10. Configuration  (0) 2024.04.15
8. WebAPI (+Routing Attributes)  (0) 2024.04.15
7. TagHelper  (0) 2024.04.15
6. View  (0) 2024.04.15
Comments