# .NET 7의 IOptions, IOptionsMonitor 및 IOptionsSnapshot 이해하기 | Code4IT

> [Code4IT](https://code4it.dev/)의 [Understanding IOptions, IOptionsMonitor, and IOptionsSnapshot in .NET 7](https://code4it.dev/blog/ioptions-ioptionsmonitor-ioptionssnapshot/)을 번역하였습니다.

---

.NET 애플리케이션에서 구성을 다룰 때는 다양한 전략을 선택할 수 있습니다. 예를 들어, 생성자에 `IConfiguration` 인스턴스를 삽입하고 이름으로 모든 값을 검색할 수 있습니다.

또는 **강력한 형식 구성**을 최대한 활용할 수 있습니다. 모든 것이 이미 .NET에서 자동으로 처리되므로 수동으로 값을 캐스팅하는 데 신경 쓸 필요가 없습니다.

이 글에서는 `IOptions`, `IOptionsSnapshot`, `IOptionsMonitor`에 대해 알아보겠습니다. 비슷해 보이지만 올바른 것을 선택하기 위해 이해해야 할 몇 가지 주요 차이점이 있습니다.

이 글에서는 하나의 엔드포인트만 노출하는 더미 .NET API를 만들었습니다.

내 appsettings.json 파일에 노드를 추가했습니다:

```json
{
    "MyConfig": {
        "Name": "Davide"
    }
}
```

이것은 POCO 클래스와 매핑됩니다:

```csharp
public class GeneralConfig
{
    public string Name { get; set; }
}
```

API 프로젝트에 추가하려면 Program.cs 파일에 이 줄을 추가하면 됩니다:

```csharp
builder.Services.Configure<GeneralConfig>(builder.Configuration.GetSection("MyConfig"));
```

보시다시피, "MyConfig"라는 이름의 콘텐츠를 가져와서 `GeneralConfig` 유형의 객체에 매핑합니다.

이러한 유형을 테스트하기 위해 더미 API 컨트롤러 세트를 만들었습니다. 각 컨트롤러는 구성에서 값을 읽고 객체로 반환하는 하나의 단일 메서드인 `Get()` 메서드를 노출합니다.

다양한 접근 방식을 시도해 볼 수 있도록 관련 컨트롤러의 생성자에 `IOptions`, `IOptionsSnapshot`, `IOptionsMonitor`를 주입하겠습니다.

## IOptions: 단순함, 싱글톤, 재로드를 지원하지 않음

`IOptions<T>`는 이러한 구성을 주입하는 가장 간단한 방법입니다. 생성자에 삽입하고 `Value` 속성을 사용하여 실제 값에 액세스할 수 있습니다:

```csharp
private readonly GeneralConfig _config;

public TestingController(IOptions<GeneralConfig> config)
{
    _config = config.Value;
}
```

이제 *appsettings.json* 파일에 정의한 대로 값이 채워진 `GeneralConfig` 개체에 직접 액세스할 수 있습니다.

`IOptions<T>`를 사용할 때 고려해야 할 몇 가지 사항이 있습니다:

* 이 서비스는 **싱글톤 인스턴스**로 주입됩니다. 전체 애플리케이션이 동일한 인스턴스를 사용하며 전체 애플리케이션 수명 기간 동안 유효합니다.
    
* **모든 구성은 시작 시점에 읽습니다**. 애플리케이션이 실행되는 동안 앱 설정 파일을 업데이트하더라도 업데이트된 값은 표시되지 않습니다.
    

어떤 사람들은 다음과 같이 `IOptions<T>` 인스턴스를 비공개 필드로 저장하고 필요할 때 `Value` 속성을 사용하여 값에 액세스하는 것을 선호합니다:

```csharp
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>`를 삽입하고 필요한 유형의 인스턴스를 전달할 수 있습니다.

```csharp
[SetUp]
public void Setup()
{
    var config = new GeneralConfig { Name = "Test" };

    var options = Options.Create(config);
    
    _sut = new TestingController(options);
}
```

### 데모: 런타임에 구성이 변경되지 않음

아래에서 애플리케이션이 실행 중일 때 구성이 변경되지 않음을 보여주는 GIF를 확인할 수 있습니다.

![With IOptions the configuration does not change at runtime](https://code4it.dev/blog/ioptions-ioptionsmonitor-ioptionssnapshot/ioptions.gif align="left")

보시다시피 다음 단계를 수행했습니다:

1. 애플리케이션을 시작합니다.
    
2. */TestingOptions* 엔드포인트를 호출하면 Davide라는 이름이 반환됩니다.
    
3. 이제 *appsettings.json* 파일의 내용을 업데이트하여 이름을 Davide Bellone으로 설정합니다.
    
4. 동일한 엔드포인트를 다시 호출하면 업데이트된 값이 표시되지 않습니다.
    

## IOptionSnapshot: 범위지정, 낮은 성능, 재로드 지원

`IOptions<T>`와 유사하게 `IOptionsSnapshot<T>`이 있습니다. 비슷하게 작동하지만 큰 차이가 있습니다: `IOptionsSnapshot<T>`**는 요청할 때마다 다시 계산됩니다**.

```csharp
public TestingController(IOptionsSnapshot<GeneralConfig> config)
{
    _config = config.Value;
}
```

`IOptionsSnapshot<T>`를 사용하면 항상 가장 최신의 값을 사용할 수 있습니다. **이 서비스에는 범위가 지정된 수명이 주입**되어 모든 HTTP 요청 시 설정에서 값을 읽습니다. 즉, **애플리케이션이 실행되는 동안 설정 값을 업데이트할 수 있으며** 업데이트된 결과를 확인할 수 있습니다.

.NET은 HTTP를 호출할 때마다 구성을 다시 빌드하므로 **약간의 성능 오버헤드**가 있습니다. 따라서 꼭 필요한 경우가 아니라면 항상 `IOptions<T>`를 사용하세요.

`IOptions<T>`에서 했던 것처럼 `IOptionsSnapshot<T>`를 테스트할 방법이 없으므로 스텁이나 모의([Moq 또는 NSubstitute 🔗](https://code4it.dev/blog/moq-vs-nsubstitute-syntax/) 사용 가능)를 사용해야 합니다.

### 데모: 애플리케이션이 실행되는 동안 구성이 변경됩니다.

아래 GIF를 보세요. 여기서는 애플리케이션을 실행하고 */TestingIOptionsSnapshot* 엔드포인트를 호출합니다.

![With IOptionsSnapshot the configuration changes while the application is running](https://code4it.dev/blog/ioptions-ioptionsmonitor-ioptionssnapshot/ioptionssnapshot.gif align="left")

다음 단계를 수행했습니다:

1. 애플리케이션을 실행합니다.
    
2. */TestingIOptionsSnapshot* 엔드포인트를 호출합니다. 반환된 값은 *appsettings.json* 파일에서 동일합니다: Davide Bellone
    
3. 그런 다음 구성 파일에서 값을 업데이트합니다.
    
4. */TestingIOptionsSnapshot*를 다시 호출하면 반환된 값이 앱 설정 파일의 새 값을 반영하는 것을 확인할 수 있습니다.
    

## IOptionsMonitor: 복합함, 싱글톤, 재로드 지원

마지막으로, 세 가지 중 마지막인 `IOptionsMonitor<T>`입니다.

`IOptionsMonitor<T>`를 사용하면 *appsettings.json* 파일에서 가장 최신의 값을 확인할 수 있습니다.

또한 구성 파일이 업데이트될 때마다 트리거되는 콜백 이벤트도 있습니다.

싱글톤 서비스로 주입되므로 전체 애플리케이션 수명 동안 동일한 인스턴스가 공유됩니다.

`IOptions<T>`와 두 가지 주요 차이점이 있습니다:

1. 구성 값을 저장하는 속성 이름은 `Value`가 아닌 `CurrentValue`입니다
    
2. 설정 파일을 업데이트할 때마다 호출되는 콜백이 있습니다: \`OnChange(Action&lt;TOptions, string?&gt; listener). 이를 사용하여 구성이 변경될 때마다 트리거되어야 하는 작업을 수행할 수 있습니다.
    

노트: `OnChange`**는 폐기해야 하는** `IDisposable`**을 구현하는 객체를 반환합니다**. 그렇지 않으면 [크리스 엘버트가 알아차렸듯이(참고로 트위터에서 그를 팔로우하세요!)](https://twitter.com/realchrisebert/status/1403304311510208526) `IOptionsMonitor<T>`를 사용하는 클래스의 인스턴스는 절대로 처분되지 않습니다.

다시 말하지만, `IOptions<T>`에서 했던 것처럼 `IOptionsMonitor<T>`를 테스트할 수 있는 방법은 없습니다. 따라서 스텁과 모의 테스트에 의존해야 합니다(다시 말하지만, [Moq 또는 NSubstitute 🔗](https://code4it.dev/blog/moq-vs-nsubstitute-syntax/)를 사용해야 할 수도 있습니다).

### 데모: 구성이 변경되고 콜백이 호출됩니다.

아래 GIF에서는 `IOptionsMonitor`의 사용법을 보여드립니다.

구성의 변경 사항을 수신하고 정적 카운터를 업데이트한 다음 API에서 최종 결과를 반환하는 API 컨트롤러를 만들었습니다:

```csharp
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>`의 전체 사용 현황을 확인할 수 있습니다:

![With IOptionsMonitor the configuration changes, the callback is called](https://code4it.dev/blog/ioptions-ioptionsmonitor-ioptionssnapshot/ioptionsmonitor.gif align="left")

보시다시피, 저는 이 단계를 수행했습니다:

1. 애플리케이션을 실행합니다.
    
2. */TestionIOptionsMonitor* 엔드포인트를 호출합니다. 구성에서 내 이름 필드를 읽고 업데이트는 0입니다.
    
3. 그런 다음 구성 파일을 업데이트하고 저장합니다. 백그라운드에서 `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](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0) [ASP.NET](http://ASP.NET) [Core | Microsoft Learn](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0)

저는 `IOptions`와 `IOptionsMonitor`는 싱글톤이고, `IOptionsSnapshot`은 범위가 지정되어 있다고 계속 설명했습니다. 이 두 가지가 무엇을 의미하는지 잘 모르시는 분들을 위해 짧지만 자세히 설명해드리겠습니다:

🔗 [Dependency Injection lifetimes in .NET - Scoped vs Transient vs Singleton | Code4IT](https://code4it.dev/blog/dependency-injection-lifetimes/)

특히, 싱글톤 서비스에 일시적 또는 범위 지정 서비스를 삽입할 때의 문제점을 설명하는 보너스 팁에 집중해 주시기 바랍니다:

🔗 [Dependency Injection lifetimes in .NET - Scoped vs Transient vs Singleton | Code4IT](https://code4it.dev/blog/dependency-injection-lifetimes/#bonus-tip-transient-dependency-inside-a-singleton)

이 기사는 [Code4IT](https://code4it.dev/) 🐧에 처음 게재되었습니다.

이 글에서는 *appsettings.json* 파일에 구성을 저장했습니다. 환경 변수 및 *launchSettings.json* 등 더 많은 방법으로 구성 값을 설정할 수 있습니다.

🔗 [3 (and more) ways to set configuration values in .NET | Code4IT](https://code4it.dev/blog/how-to-set-configurations-values-dotnet/)

## 마무리

이 문서에서는 .NET 7 응용 프로그램에서 `IOptions<T>`, `IOptionsSnapshot<T>` 및 `IOptionsMonitor<T>`를 사용하는 방법에 대해 알아봤습니다.

예를 들어, 전체 객체를 싱글톤으로 주입하거나 `IConfiguration`을 사용하여 단일 값을 가져오는 등 다양한 방법으로 구성을 처리할 수 있습니다.

언제 다른 접근 방식 대신 어떤 접근 방식을 선택하시겠습니까? 아래에 댓글을 남겨주세요! 📩

이 기사가 도움이 되셨기를 바랍니다! [트위터](https://twitter.com/BelloneDavide)나 [링크드인](https://www.linkedin.com/in/BelloneDavide/)에서 계속 연락하세요! 🤜🤛

행복한 코딩!

🐧

---

[Understanding IOptions, IOptionsMonitor, and IOptionsSnapshot in .NET 7 | Code4IT](https://code4it.dev/blog/ioptions-ioptionsmonitor-ioptionssnapshot/#ioptions-simple-singleton-doesnt-support-config--reloads)
