Code4IT의 Understanding IOptions, IOptionsMonitor, and IOptionsSnapshot in .NET 7을 번역하였습니다.
.NET 애플리케이션에서 구성을 다룰 때는 다양한 전략을 선택할 수 있습니다. 예를 들어, 생성자에 IConfiguration
인스턴스를 삽입하고 이름으로 모든 값을 검색할 수 있습니다.
또는 강력한 형식 구성을 최대한 활용할 수 있습니다. 모든 것이 이미 .NET에서 자동으로 처리되므로 수동으로 값을 캐스팅하는 데 신경 쓸 필요가 없습니다.
이 글에서는 IOptions
, IOptionsSnapshot
, IOptionsMonitor
에 대해 알아보겠습니다. 비슷해 보이지만 올바른 것을 선택하기 위해 이해해야 할 몇 가지 주요 차이점이 있습니다.
이 글에서는 하나의 엔드포인트만 노출하는 더미 .NET API를 만들었습니다.
내 appsettings.json 파일에 노드를 추가했습니다:
{
"MyConfig": {
"Name": "Davide"
}
}
이것은 POCO 클래스와 매핑됩니다:
public class GeneralConfig
{
public string Name { get; set; }
}
API 프로젝트에 추가하려면 Program.cs 파일에 이 줄을 추가하면 됩니다:
builder.Services.Configure<GeneralConfig>(builder.Configuration.GetSection("MyConfig"));
보시다시피, "MyConfig"라는 이름의 콘텐츠를 가져와서 GeneralConfig
유형의 객체에 매핑합니다.
이러한 유형을 테스트하기 위해 더미 API 컨트롤러 세트를 만들었습니다. 각 컨트롤러는 구성에서 값을 읽고 객체로 반환하는 하나의 단일 메서드인 Get()
메서드를 노출합니다.
다양한 접근 방식을 시도해 볼 수 있도록 관련 컨트롤러의 생성자에 IOptions
, IOptionsSnapshot
, IOptionsMonitor
를 주입하겠습니다.
IOptions: 단순함, 싱글톤, 재로드를 지원하지 않음
IOptions<T>
는 이러한 구성을 주입하는 가장 간단한 방법입니다. 생성자에 삽입하고 Value
속성을 사용하여 실제 값에 액세스할 수 있습니다:
private readonly GeneralConfig _config;
public TestingController(IOptions<GeneralConfig> config)
{
_config = config.Value;
}
이제 appsettings.json 파일에 정의한 대로 값이 채워진 GeneralConfig
개체에 직접 액세스할 수 있습니다.
IOptions<T>
를 사용할 때 고려해야 할 몇 가지 사항이 있습니다:
이 서비스는 싱글톤 인스턴스로 주입됩니다. 전체 애플리케이션이 동일한 인스턴스를 사용하며 전체 애플리케이션 수명 기간 동안 유효합니다.
모든 구성은 시작 시점에 읽습니다. 애플리케이션이 실행되는 동안 앱 설정 파일을 업데이트하더라도 업데이트된 값은 표시되지 않습니다.
어떤 사람들은 다음과 같이 IOptions<T>
인스턴스를 비공개 필드로 저장하고 필요할 때 Value
속성을 사용하여 값에 액세스하는 것을 선호합니다:
private readonly IOptions<GeneralConfig> _config;
private int updates = 0;
public TestingController(IOptions<GeneralConfig> config)
{
_config = config;
}
[HttpGet]
public ActionResult<Result> Get()
{
var name = _config.Value.Name;
return new Result
{
MyName = name,
Updates = updates
};
}
작동은 하지만 쓸모가 없습니다. IOptions<T>
는 싱글톤이므로 매번 Value
프로퍼티에 액세스할 필요가 없습니다. 시간이 지나도 변경되지 않으므로 매번 액세스하는 것은 쓸모없는 작업입니다. 전자의 접근 방식을 사용하면 작성하기가 더 쉽고 성능도 약간 더 좋습니다.
한 가지 더 있습니다. 단위 테스트를 작성할 때 정적 메서드인 Options.Create<T>
를 사용하여 테스트 대상 시스템에 IOptions<T>
를 삽입하고 필요한 유형의 인스턴스를 전달할 수 있습니다.
[SetUp]
public void Setup()
{
var config = new GeneralConfig { Name = "Test" };
var options = Options.Create(config);
_sut = new TestingController(options);
}
데모: 런타임에 구성이 변경되지 않음
아래에서 애플리케이션이 실행 중일 때 구성이 변경되지 않음을 보여주는 GIF를 확인할 수 있습니다.
보시다시피 다음 단계를 수행했습니다:
애플리케이션을 시작합니다.
/TestingOptions 엔드포인트를 호출하면 Davide라는 이름이 반환됩니다.
이제 appsettings.json 파일의 내용을 업데이트하여 이름을 Davide Bellone으로 설정합니다.
동일한 엔드포인트를 다시 호출하면 업데이트된 값이 표시되지 않습니다.
IOptionSnapshot: 범위지정, 낮은 성능, 재로드 지원
IOptions<T>
와 유사하게 IOptionsSnapshot<T>
이 있습니다. 비슷하게 작동하지만 큰 차이가 있습니다: IOptionsSnapshot<T>
는 요청할 때마다 다시 계산됩니다.
public TestingController(IOptionsSnapshot<GeneralConfig> config)
{
_config = config.Value;
}
IOptionsSnapshot<T>
를 사용하면 항상 가장 최신의 값을 사용할 수 있습니다. 이 서비스에는 범위가 지정된 수명이 주입되어 모든 HTTP 요청 시 설정에서 값을 읽습니다. 즉, 애플리케이션이 실행되는 동안 설정 값을 업데이트할 수 있으며 업데이트된 결과를 확인할 수 있습니다.
.NET은 HTTP를 호출할 때마다 구성을 다시 빌드하므로 약간의 성능 오버헤드가 있습니다. 따라서 꼭 필요한 경우가 아니라면 항상 IOptions<T>
를 사용하세요.
IOptions<T>
에서 했던 것처럼 IOptionsSnapshot<T>
를 테스트할 방법이 없으므로 스텁이나 모의(Moq 또는 NSubstitute 🔗 사용 가능)를 사용해야 합니다.
데모: 애플리케이션이 실행되는 동안 구성이 변경됩니다.
아래 GIF를 보세요. 여기서는 애플리케이션을 실행하고 /TestingIOptionsSnapshot 엔드포인트를 호출합니다.
다음 단계를 수행했습니다:
애플리케이션을 실행합니다.
/TestingIOptionsSnapshot 엔드포인트를 호출합니다. 반환된 값은 appsettings.json 파일에서 동일합니다: Davide Bellone
그런 다음 구성 파일에서 값을 업데이트합니다.
/TestingIOptionsSnapshot를 다시 호출하면 반환된 값이 앱 설정 파일의 새 값을 반영하는 것을 확인할 수 있습니다.
IOptionsMonitor: 복합함, 싱글톤, 재로드 지원
마지막으로, 세 가지 중 마지막인 IOptionsMonitor<T>
입니다.
IOptionsMonitor<T>
를 사용하면 appsettings.json 파일에서 가장 최신의 값을 확인할 수 있습니다.
또한 구성 파일이 업데이트될 때마다 트리거되는 콜백 이벤트도 있습니다.
싱글톤 서비스로 주입되므로 전체 애플리케이션 수명 동안 동일한 인스턴스가 공유됩니다.
IOptions<T>
와 두 가지 주요 차이점이 있습니다:
구성 값을 저장하는 속성 이름은
Value
가 아닌CurrentValue
입니다설정 파일을 업데이트할 때마다 호출되는 콜백이 있습니다: `OnChange(Action<TOptions, string?> listener). 이를 사용하여 구성이 변경될 때마다 트리거되어야 하는 작업을 수행할 수 있습니다.
노트: OnChange
는 폐기해야 하는 IDisposable
을 구현하는 객체를 반환합니다. 그렇지 않으면 크리스 엘버트가 알아차렸듯이(참고로 트위터에서 그를 팔로우하세요!) IOptionsMonitor<T>
를 사용하는 클래스의 인스턴스는 절대로 처분되지 않습니다.
다시 말하지만, IOptions<T>
에서 했던 것처럼 IOptionsMonitor<T>
를 테스트할 수 있는 방법은 없습니다. 따라서 스텁과 모의 테스트에 의존해야 합니다(다시 말하지만, Moq 또는 NSubstitute 🔗를 사용해야 할 수도 있습니다).
데모: 구성이 변경되고 콜백이 호출됩니다.
아래 GIF에서는 IOptionsMonitor
의 사용법을 보여드립니다.
구성의 변경 사항을 수신하고 정적 카운터를 업데이트한 다음 API에서 최종 결과를 반환하는 API 컨트롤러를 만들었습니다:
public class TestingIOptionsMonitorController : ControllerBase
{
private static int updates = 0;
private readonly GeneralConfig _config;
public TestingIOptionsMonitorController(IOptionsMonitor<GeneralConfig> config)
{
_config = config.CurrentValue;
config.OnChange((_, _) => updates++);
}
[HttpGet]
public ActionResult<Result> Get() => new Result
{
MyName = _config.Name,
Updates = updates
};
}
애플리케이션이 실행되는 동안 애플리케이션을 실행하고 구성 콘텐츠를 수정하면 IOptionsMonitor<T>
의 전체 사용 현황을 확인할 수 있습니다:
보시다시피, 저는 이 단계를 수행했습니다:
애플리케이션을 실행합니다.
/TestionIOptionsMonitor 엔드포인트를 호출합니다. 구성에서 내 이름 필드를 읽고 업데이트는 0입니다.
그런 다음 구성 파일을 업데이트하고 저장합니다. 백그라운드에서
OnChange
콜백이 실행되고 Updates 값이 업데이트됩니다.
이상하게도 콜백이 예상보다 더 많이 호출됩니다. 파일을 두 번만 업데이트했지만 카운터는 6으로 설정되어 있습니다. 이상한 동작입니다. 왜 이런 일이 발생하는지 아신다면 아래에 메시지를 남겨주세요 📩.
.NET의 IOptions 대 IOptionsSnapshot 대 IOptionsMonitor
지금까지 IOptions
, IOptionsSnapshot
및 IOptionsMonitor
에 대해 간략하게 소개해 드렸습니다.
물론 몇 가지 차이점이 있습니다. 다음은 이 글에서 배운 내용을 요약한 표입니다.
유형 | DI 수명 | 단위 테스트에 삽입하는 가장 좋은 방법 | 실시간 재로드 허용 | 콜백 기능 유무 |
IOptions<T> | Singleton | Options.Create<T> | ❌ | ❌ |
IOptionsSnapshot<T> | Scoped | Stub / Mock | 🟢 | ❌ |
IOptionsMonitor<T> | Singleton | Stub / Mock | 🟢 | 🟢 |
예를 들어, IOptionsSnapshot
및 IOptionsMonitor
를 사용하면 명명된 옵션을 사용할 수 있으므로 JSON 파일에서 다른 노드를 참조하는 동일한 유형의 인스턴스를 더 많이 삽입할 수 있습니다.
하지만 이는 다음 글에서 다룰 주제이니 기대해 주세요 😎.
추가 읽기
구성을 주입하는 방법에 대한 자세한 내용은 다음과 같습니다.
가장 좋은 리소스 중 하나는 공식 문서입니다:
🔗 Options pattern in ASP.NET Core | Microsoft Learn
저는 IOptions
와 IOptionsMonitor
는 싱글톤이고, IOptionsSnapshot
은 범위가 지정되어 있다고 계속 설명했습니다. 이 두 가지가 무엇을 의미하는지 잘 모르시는 분들을 위해 짧지만 자세히 설명해드리겠습니다:
🔗 Dependency Injection lifetimes in .NET - Scoped vs Transient vs Singleton | Code4IT
특히, 싱글톤 서비스에 일시적 또는 범위 지정 서비스를 삽입할 때의 문제점을 설명하는 보너스 팁에 집중해 주시기 바랍니다:
🔗 Dependency Injection lifetimes in .NET - Scoped vs Transient vs Singleton | Code4IT
이 기사는 Code4IT 🐧에 처음 게재되었습니다.
이 글에서는 appsettings.json 파일에 구성을 저장했습니다. 환경 변수 및 launchSettings.json 등 더 많은 방법으로 구성 값을 설정할 수 있습니다.
🔗 3 (and more) ways to set configuration values in .NET | Code4IT
마무리
이 문서에서는 .NET 7 응용 프로그램에서 IOptions<T>
, IOptionsSnapshot<T>
및 IOptionsMonitor<T>
를 사용하는 방법에 대해 알아봤습니다.
예를 들어, 전체 객체를 싱글톤으로 주입하거나 IConfiguration
을 사용하여 단일 값을 가져오는 등 다양한 방법으로 구성을 처리할 수 있습니다.
언제 다른 접근 방식 대신 어떤 접근 방식을 선택하시겠습니까? 아래에 댓글을 남겨주세요! 📩
이 기사가 도움이 되셨기를 바랍니다! 트위터나 링크드인에서 계속 연락하세요! 🤜🤛
행복한 코딩!
🐧
Understanding IOptions, IOptionsMonitor, and IOptionsSnapshot in .NET 7 | Code4IT