모듈 별 Configuration

닷넷은 앱의 재시동만으로 앱을 설정을 변경할 수 있는 Configuration API를 제공합니다.

기본적으로 환경 변수와, “appsettings.json” 파일을 인식하게끔 설정되어 있습니다.

시스템을 아래와 같이 관심사 별로 나눈다고 할 때,

  • WebApp
  • Infra.Database

각 관심사를 책임지는 팀 별로 업무 분장이 이뤄질 것입니다.
예를 들면, WebApp 은 웹팀, Infra.Database 는 데이터베이스 팀 처럼 말이죠.

위와 같이 업무 분장을 하지 않더라도, 각 모듈에 관한 설정은 각 모듈에 두는 게, 아무래도 관리 포인트가 명확하게 됩니다.

  • WebApp
    - appsettings.json
  • Infra.Database
    - dbsettings.json

모든 설정 파일은 엔트리 포인트인 WebApp의 빌드 결과물의 루트 폴더에 저장됩니다.

\bin\release\.net8.0\appsettings.json
\bin\release\.net8.0\dbsettings.json

이 경우, 하위 모듈에 있는 설정이 소비되는 방식은 두 가지입니다.

내부 소비

하위 모듈에 관한 설정이기 때문에, 다른 모듈에 이 설정을 노출할 필요가 없는 게 일반적이라 할 수 있습니다.

이 경우, dbsettings.json 에서 설정을 가져 오는 건 예상 외로 쉽습니다.

using Microsoft.Extensions.Configuration; // 패키지 필요.

//...

var localConfiguraiton = new Configuration()
   .AddJsonFile("dbsettings.json")
   .Build();

var subSectionValue = localConfiguraiton.GetRequiredSection("{TopSection}:{SubSection}")?.Value;

// subSectionValue  소비 코드.

주의할 점은, new Configuration() 는 반드시 하위 모듈에서 실행되어야 그 모듈에서 파일을 찾습니다.

외부 공개

외부 공개하는 방법은 어딘가 값을 노출하여, 이 모듈을 참조하는 다른 모듈에서 읽어 가게 만들면 됩니다.

namespace Infra.Database

public static class DbSettings
{
    public static string? Value { get; internal set; }
}
// 설정
var localConfiguraiton = new Configuration()
   .AddJsonFile("dbsettings.json")
   .Build();

DbSettings.Value = localConfiguraiton.GetRequiredSection("{TopSection}:{SubSection}")?.Value;

그런데, 이 방법의 문제는 DbSettings.Value 에 접근하는 시점에 따라, 설정값을 받을 수도 있고 받지 못할 수도 있다(설정하는 코드보다 접근하는 코드가 먼저 실행되는 경우)는 점입니다.

그렇다고 아래와 같이 처리하면, 파일에 접근 코드가 항상 실행되기 때문에, 성능 문제가 발생합니다.
(그러나, 적시성은 좋아 집니다.)

namespace Infra.Database

public static class DbSettings
{
   private static IConfigurationRoot Config = new Configuration()
         .AddJsonFile("dbsettings.json")
         .Build();

   public static string? Value => Config      
         .GetSection("{TopSection}:{SubSection}").Value
}

Dependency Injection

설정을 객체로 캡슐화해서, 컨테이터에 등록하면, 앱 실행 초기에 등록되기 때문에, 성능 문제나 접근 시차 문제를 해결할 수 있습니다.

캡슐화

namespace Infra.Database.Contracts;

public class TopSection
{
   public string? SubSection { get; set; }
}

서비스 등록

상위 모듈의 DI에 설정 객체를 Singleton 으로 등록합니다.

using Microsoft.Extensions.Configuration; // 패키지 필요
using Microsoft.Extensions.DependencyInjection; // 패키지 필요
using Infra.Database.Contracts;

namespace Infra.Database;

public static class IServiceCollectonExtensions
{
   public static IServiceCollection AddInfraDatabaseServices(this IServiceCollection services)
   {
      var localConfiguraiton = new Configuration()
         .AddJsonFile("dbsettings.json")
         .Build();

      var topSection = new TopSection
      {
         SubSection = localConfiguraiton.GetRequiredSection("{TopSection}:SubSection}")?.Value,
      }

      service.AddSingleton(sp => topSection);

      // 추가 등록

      return services;
   }
}

상위 모듈

// program.cs
using Infra.Database;
// ...
 builder.Services.AddInfraDatabaseServices();

// ...
// minimal api

app.MapGet("/my-page", (TopSection dbSetttings) => {
   // ...
});
@*MyPage.razor*@
@inject TopSection DbSettings

@*
....
*@
4개의 좋아요

구성의 best practice는 옵션 패턴을 사용하는 건데요.

런타임에 구성 값을 바꿀 수 있을 정도로 강력한 기능을 제공합니다.

3개의 좋아요

다른 모듈의 설정 값을 옵션 패턴으로 구현해보신 적이 있으신지요?

예를 들면, 예제의 infra.Database 모듈의 dbsettings.json 을 WebApp에서 옵션 바인딩하는 것입니다.
해보셨다면, 코드 공유 부탁드립니다.

옵션 패턴 적용 이전에, 다른 모듈에서 구성을 가져 온다는 게 생각보다 간단하지 않아, 간소한 방식을 소개한 것입니다.

1개의 좋아요