ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [.NET] Dependency Injection(DI)를 쓰는 이유
    .NET/개념 및 유용한 팁 2021. 8. 7. 12:46
    반응형

    ASP.NET Core는 기본적으로 DI가 적용된 구조로 생성된다. 그런데 왜 DI 가 적용되어있을까? 인터넷에서 보면 다음과 같은 이유로 DI가 좋다고 되어있다.

     

    • 코드의 재사용성을 높인다.
    • 결합도를 낮춘다.

     

    여러 블로그글을 보면 위 내용들이 많다. 그런데 왜 재사용성이 높아지고, 결합도가 낮아지는 건지 와닿진 않는다. 그리고 굳이 DI를 써야하나라는 의구심이 들기도 한다.

    솔직히 필자도 DI 가 좋은 이유에 대해 제대로 설명해보라고하면 못할 듯 싶다. 그래서 아래 간단한 실습을 통해 DI가 어떻게 동작하며, 왜 좋은지 포스트로 남겨보고자 한다.

     

    1. 프로젝트 생성

    "WhyUseDI" 라는 이름의 솔루션을 생성하고, "WhyUseDI.API" webapi 와 "Services" classlib 프로젝트를 생성한다.  

     

    2. WhyUseDI.API 에 Services 참조설정

    WhyUseDI.API에서 웹 요청(Request)를 받을때, Services 프로젝트의 메소드를 호출에 데이터를 가져오게 구현할 것이므로, WhyUseDI.API 프로젝트에 Services 프로젝트를 참조시키도록 한다. 

     

    3. Services 프로젝트에 GetHelloWorldQuery 클래스 추가

    Services 프로젝트에 아래와 같이 클래스와 소스코드를 추가한다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Services
    {
        public class GetHelloWorldQuery
        {
            public GetHelloWorldQuery()
            {
    
            }
    
            public string Get()
            {
                return "HelloWorld";
            }
        }
    }

     

    4. AddScoped<GetHelloWorldQuery>() 추가

    WhyUseDI.API 프로젝트의 Startup.cs 에서 ConfigureServices 함수내 AddScoped<GetHelloWorldQuery>() 를 추가하여 의존성 주입(DI)를 처리한다.

    public void ConfigureServices(IServiceCollection services)
    {
    
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "WhyUseDI.API", Version = "v1" });
        });
        services.AddScoped<GetHelloWorldQuery>();
    }

     

    5. HelloWorldController 추가

    WhyUseDI.API 프로젝트에 HelloWorldController를 아래 소스와 같이 추가한다.

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using Services;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace WhyUseDI.API.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class HelloWorldController : ControllerBase
        {
            private readonly ILogger<HelloWorldController> _logger;
            private readonly GetHelloWorldQuery _service;
    
            public HelloWorldController(ILogger<HelloWorldController> logger, GetHelloWorldQuery service)
            {
                _logger = logger;
                _service = service;
            }
    
            [HttpGet]
            public string Get()
            {
                return _service.Get();
            }
        }
    }


    6. 테스트

    디버깅(F5)을 실행시키고 https://localhost:{테스트Port}/api/HelloWorld 로 접속하여 "Helloworld"가 나오는지 확인한다.

    여기까지 왔다면 본격적인 DI 이점에 대해 알아볼 준비가 된 것이다.

     

    7. Services 프로젝트에 Microsoft.Extensions.Logging 추가

    GetHelloWorldQuery 의 Get 함수에서 Log를 출력하기위해 Microsoft.Extensions.Logging 를 Nuget에서 찾아 추가해준다. Microsoft.Extensions.Logging 는 WebAPI 프로젝트에서 기본적으로 추가된다.

     

    8. GetHelloWorldQuery 수정

    로그출력을 위해 GetHelloWorldQuery 클래스를 아래와 같이 수정한다.

    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Services
    {
        public class GetHelloWorldQuery
        {
            private readonly ILogger<GetHelloWorldQuery> _logger;
    
            public GetHelloWorldQuery(ILogger<GetHelloWorldQuery> logger)
            {
                _logger = logger;
            }
    
            public string Get()
            {
                _logger.LogInformation("call HelloWorld");
                return "HelloWorld";
            }
        }
    }

     

    7. 로그 출력 테스트

    로그가 출력되는지 확인하기위해 디버깅을 실행시켜준다. 실행시킬 때 콘솔창이 나올 수 있도록 디버그 실행프로필을 "WhyUseDI.API"로 선택해주자

    테스트 후 6단계와 같이 `https://localhost:{테스트Port}/api/HelloWorld`에 접속하면 콘솔창에 로그가 표시되는 것을 확인할 수 있다.

    의존성 주입에 의해 WhyUseDI.API 프로젝트에 설정된 로그출력 방식에 따라, Services 프로젝트의 로그가 출력되고 있다. 잘 와닿지 않는가? 그렇다면 다음 단계를 진행해보자.

     

    8. NLog 추가

    NLog는 오래전부터 .NET 프로젝트에서 로그시스템으로 애용하고 있는 로깅 라이브러리이다. 현재까지도 활성화되어있는 오픈소스 라이브러리이며 간단한 설정을 통해 file, DB, email 등 여러가지 방법으로 로그를 남겨준다.

    WhyUseDI.API 프로젝트에 "NLog.Web.AspNetCore" 를 Nuget에서 찾아 추가해준다.

    설치 후 표시되는 안내메시지를 통해 NLog를 적용시킨다. 필자는 .NET5 프로젝트에 NLog.Web.AspNetCore 4.13.0 라이브러리 기준으로 설정하였다.

    WhyUseDI.API 프로젝트에 nlog.config 추가

    [nlog.config]

    <?xml version="1.0" encoding="utf-8" ?>
    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          autoReload="true"
          internalLogLevel="Info"
          internalLogFile="c:\temp\internal-nlog-AspNetCore.txt">
    
      <!-- enable asp.net core layout renderers -->
      <extensions>
        <add assembly="NLog.Web.AspNetCore"/>
      </extensions>
    
      <!-- the targets to write to -->
      <targets>
        <!-- File Target for all log messages with basic details -->
        <target xsi:type="File" name="allfile" fileName="nlog-AspNetCore-all-${shortdate}.log"
                layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />
    
        <!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
        <target xsi:type="File" name="ownFile-web" fileName="nlog-AspNetCore-own-${shortdate}.log"
                layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}| body: ${aspnet-request-posted-body}" />
    
        <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
        <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:tolower=true}\: ${logger}[0]${newline}      ${message}${exception:format=tostring}" />
      </targets>
    
      <!-- rules to map from logger name to target -->
      <rules>
        <!--All logs, including from Microsoft-->
        <logger name="*" minlevel="Trace" writeTo="allfile" />
    
        <!--Output hosting lifetime messages to console target for faster startup detection -->
        <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />
    
        <!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
        <logger name="Microsoft.*" maxlevel="Info" final="true" />
        <logger name="System.Net.Http.*" maxlevel="Info" final="true" />
    
        <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
      </rules>
    </nlog>

     

     

    [Program.cs]

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using NLog.Web;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace WhyUseDI.API
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    })
                .UseNLog(); // 추가된 소스코드
        }
    }

     

    9. NLog 적용 후 로그출력 테스트

    적용이 완료되었다면 디버그를 실행시켜 nlog 설정에 따라 로그파일이 생성되었는지 확인해보자.

    로그파일 생성 확인
    콘솔 출력로그와 파일로그 비교

    확인되었다면 `https://localhost:{테스트Port}/api/HelloWorld`로 접속해보자. 이전 단계에서 적용한 로그가 콘솔창에 표시될 것이다.

    그렇다면 파일은 어떨까? 마찬가지로 기록되었을까?

    기록되어있는 Services 프로젝트 로그

    여기서 뭔가 이상한 점을 눈치챘는가? NLog 라이브러리는 WhyUseDI.API 프로젝트에만 적용시켰다. Services 프로젝트는 적용시키지 않았다. 그럼에도 불구하고 NLog 라이브러리에서 사용하는 nlog.config 설정에 따라 로그가 텍스트파일에도 남는 것을 확인할 수 있었다.

     

    이것이 DI의 이점인 "낮은 결합도"이다. 실습을 통해 구현한 Services 라이브러리가 참조되는 라이브러리에 NLog가 붙었는지, Nog4NET을 붙였는지, Serilog를 붙였는지 Services 프로젝트가 알 필요가 없다. 그저 Services 프로젝트와 API 프로젝트가 공통적으로 주입 및 사용되고 있는 Microsoft.Extensions.Logging 구현체이기만 하면 된다.

    직접적으로 추가하진 않았지만, 실습을 통해 주입시킨 GetHelloWorldQuery처럼 Microsoft.Extensions.Logging 관련 인터페이스도 ConfigureWebHostDefaults() 을 통해 WhyUseDI.API에 주입되어있다

    그리고 Services 프로젝트는 WebAPI가 아닌 gRPC, Worker 프로젝트에서도 공통적으로 사용하는 인터페이스의 의존성 주입에 따라 별도 수정없이 사용이 가능하다.

     

    이것으로 DI를 사용하는 이유와 이점에 대해 실습으로 알아보았다.

    반응형

    댓글

Designed by Tistory.