개발노트

3. [ASP .NET Core] 의존성 주입(DI), IoC 란 무엇인가? (with Simple Injector) 본문

서버 개발/ASP .NET Core

3. [ASP .NET Core] 의존성 주입(DI), IoC 란 무엇인가? (with Simple Injector)

mroh1226 2024. 11. 17. 01:48
반응형

1. 의존성 주입(DI)란?

**의존성 주입(Dependency Injection)**은 객체 간의 의존성을 외부에서 주입하여 객체를 생성하고 관리하는 디자인 패턴입니다.
이를 통해 코드의 재사용성, 테스트 용이성, 유지 보수성을 높일 수 있습니다.

왜 DI를 사용하는가?

  • 객체가 서로 강하게 결합(Coupling)되어 있으면 변경이 어렵습니다.
  • 의존성을 분리하면 코드 변경테스트가 용이해집니다.
  • 서비스, 리포지토리, 데이터베이스와 같은 의존성을 명확히 관리할 수 있습니다.

2. IoC란?

IoC(Inversion of Control, 제어의 역전)은 객체의 생성과 생명 주기를 개발자가 직접 관리하지 않고, 컨테이너 또는 프레임워크가 관리하는 것을 말합니다. (제어권이 컨테이너에게 있음)
이를 통해 코드가 DI 컨테이너에 제어를 위임하며, 객체 간 결합도를 낮출 수 있습니다.

IoC와 DI의 관계

IoC는 개념이고, DI는 이를 구현하는 방법 중 하나입니다.


3. Simple Injector란?

Simple Injector는 .NET 환경에서 사용 가능한 경량 DI 컨테이너입니다.
다른 DI 컨테이너(AutoFac, Ninject)와 비교하여 사용법이 단순하고, 성능이 뛰어난 것이 특징입니다.


4. 간단한 예제: DI와 Simple Injector 사용하기

예제 목표

  • DI로 분리된 구조IOrderService와 OrderService 구현.
  • OrderService → IProductService ← ProductService (Simple Injector 관리)
  • Simple Injector를 이용해 의존성 주입 설정.

1) 서비스 정의

IProductService.cs
public interface IProductService
{
    string GetProductName(int productId);
}

public class ProductService : IProductService
{
    public string GetProductName(int productId)
    {
        return $"Product #{productId}";
    }
}

 

IOrderService.cs
public interface IOrderService
{
    void PlaceOrder(int productId);
}

public class OrderService : IOrderService
{
    private readonly IProductService _productService;

    // 의존성을 주입받는 생성자
    public OrderService(IProductService productService)
    {
        _productService = productService;
    }

    public void PlaceOrder(int productId)
    {
        var productName = _productService.GetProductName(productId);
        Console.WriteLine($"Order placed for: {productName}");
    }
}

 

2) 컨테이너에 의존성 등록

Program.cs
using SimpleInjector;

class Program
{
    static void Main(string[] args)
    {
        // 1. Simple Injector 컨테이너 생성
        var container = new Container();

        // 2. 의존성 등록
        container.Register<IProductService, ProductService>();
        container.Register<IOrderService, OrderService>();

        // 3. 의존성 검증
        container.Verify();

        // 4. 의존성 해결 및 실행
        var orderService = container.GetInstance<IOrderService>();
        orderService.PlaceOrder(1);

        Console.ReadLine();
    }
}

 

 

 

  • 클래스 분리
    • IProductService는 제품 정보를 관리하고, IOrderService는 주문 정보를 관리합니다.
    • OrderService는 IProductService를 의존하지만, 인터페이스를 통해 의존성을 분리했습니다.
  • Simple Injector로 의존성 관리
    • Container.Register<IProductService, ProductService>(): IProductService를 호출하면 ProductService의 인스턴스를 반환.
    • Container.Register<IOrderService, OrderService>(): IOrderService를 호출하면 OrderService의 인스턴스를 반환하며, IProductService가 자동으로 주입됩니다.
  • DI의 효과
    • OrderService가 ProductService에 강하게 결합되지 않으므로, ProductService를 교체할 수 있습니다.
    • MockProductService를 테스트 시 주입하여 별도의 단위 테스트 작성이 가능합니다.
  •  이 구조의 장점
    1. 클래스 간 결합도 감소:
      • OrderService는 IProductService 인터페이스에 의존하므로, 실제 구현체가 무엇인지 신경 쓰지 않습니다.
    2. 테스트 용이성:
      • 다른 환경(Mock 등)에서 사용할 수 있는 대체 구현체를 주입할 수 있습니다.
    3. 확장성:
      • 새로운 ProductService 구현체(예: DatabaseProductService)를 추가해도 OrderService는 수정할 필요가 없습니다.
    4. 코드의 재사용성 증가:
      • 각 클래스는 자신의 책임에만 집중하므로, 독립적으로 재사용이 가능합니다.

 


 

6. DI를 사용한 설계의 장점

  1. 결합도 감소: 클래스 간 의존성을 명확히 분리.
  2. 유연성 증가: 인터페이스 교체가 용이.
  3. 테스트 용이성: Mock 객체 주입 가능.

단위 테스트를 위한 Mock 클래스를 주입하는 예시

Mock 클래스 생성

public class MockProductService : IProductService
{
    public string GetProductName(int productId)
    {
        return $"Mock Product #{productId}";
    }
}
 
테스트 코드에서 DI 주입
var container = new Container();

// 테스트 환경에서 MockProductService 등록
container.Register<IProductService, MockProductService>();
container.Register<IOrderService, OrderService>();

container.Verify();

var orderService = container.GetInstance<IOrderService>();
orderService.PlaceOrder(99); // Output: Order placed for: Mock Product #99

 


7. Simple Injector의 장점

  1. 간결한 코드: DI 컨테이너 설정이 쉽고 코드가 간단합니다.
  2. 성능 최적화: 다른 DI 컨테이너보다 빠른 속도를 제공합니다.
  3. 강력한 검증: 컨테이너 설정 시 잘못된 의존성을 미리 검증합니다.
  • DI는 객체 간의 의존성을 외부에서 관리하여 코드 결합도를 줄이는 디자인 패턴입니다.
  • IoC는 제어권을 컨테이너가 가져가는 방식으로, DI로 구현됩니다.
  • Simple Injector는 .NET 환경에서 DI를 간단히 적용할 수 있는 경량 IoC 컨테이너입니다.

만약 DI를 사용하지 않는 다면..?

class Program
{
    static void Main(string[] args)
    {
        // 직접 객체 생성 (강결합)
        var orderService = new OrderService();
        orderService.PlaceOrder("Laptop");
    }
}

문제점

  1. OrderService가 변경되면 호출하는 모든 코드도 수정해야 합니다.
  2. 테스트 시 Mocking이 어려워집니다.
반응형
Comments