Antecedentes
Nossa empresa, entre outras coisas, desenvolveu vários serviços (mais precisamente - 12) que funcionam como o back-end de nossos sistemas. Cada um dos serviços é um serviço do Windows e executa suas tarefas específicas.
Eu gostaria de transferir todos esses serviços para o * nix-OS. Para fazer isso, abandone o wrapper na forma de serviços do Windows e alterne do .NET Framework para o .NET Standard.
O último requisito leva à necessidade de se livrar de algum código herdado, que não é suportado no .NET Standard, incluindo do suporte à configuração de nossos servidores via XML, implementado usando as classes do System.Configuration. Ao mesmo tempo, isso resolve o problema de longa data relacionado ao fato de que, nas configurações de XML, cometemos erros de tempos em tempos ao alterar as configurações (por exemplo, às vezes colocamos a tag de fechamento no lugar errado ou esquecemos de tudo), mas um maravilhoso leitor de configurações de System.Xml XML. XmlDocument silenciosamente engole essas configurações, produzindo um resultado completamente imprevisível.
Decidiu-se mudar para a configuração através da moderna YAML. Que problemas enfrentamos e como os resolvemos? Neste artigo.
O que temos
Como lemos a configuração do XML
Lemos XML de maneira padrão para a maioria dos outros projetos.
Cada serviço possui um arquivo de configurações para projetos .NET, chamado AppSettings.cs, que contém todas as configurações exigidas pelo serviço. Algo assim:
[System.Configuration.SettingsProvider(typeof(PortableSettingsProvider))] internal sealed partial class AppSettings : IServerManagerConfigStorage, IWebSettingsStorage, IServerSettingsStorage, IGraphiteAddressStorage, IDatabaseConfigStorage, IBlackListStorage, IKeyCloackConfigFilePathProvider, IPrometheusSettingsStorage, IMetricsConfig { }
Uma técnica semelhante para separar configurações em interfaces torna conveniente usá-las posteriormente através de um contêiner de DI.
Toda a mágica principal de armazenar configurações está realmente oculta no PortableSettingsProvider (consulte o atributo de classe), bem como no arquivo de designer AppSettings.Designer.cs:
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] internal sealed partial class AppSettings : global::System.Configuration.ApplicationSettingsBase { private static AppSettings defaultInstance = ((AppSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new AppSettings()))); public static AppSettings Default { get { return defaultInstance; } } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("35016")] public int ListenPort { get { return ((int)(this["ListenPort"])); } set { this["ListenPort"] = value; } } ...
Como você pode ver, “nos bastidores” estão ocultas todas as propriedades que adicionamos à configuração do servidor quando a editamos através do designer de configurações no Visual Studio.
Nossa classe PortableSettingsProvider, mencionada acima, lê diretamente o arquivo XML, e o resultado da leitura já é usado no SettingsProvider para gravar configurações nas propriedades do AppSettings.
Um exemplo da configuração XML que estamos lendo (a maioria das configurações está oculta por motivos de segurança):
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup"> <section name="MetricServer.Properties.Settings" type="System.Configuration.ClientSettingsSection" /> </sectionGroup> </configSections> <userSettings> <MetricServer.Properties.Settings> <setting name="MCXSettings" serializeAs="String"> <value>Inactive, ChartLen: 1000, PrintLen: 50, UseProxy: False</value> </setting> <setting name="KickUnknownAfter" serializeAs="String"> <value>00:00:10</value> </setting> ... </MetricServer.Properties.Settings> </userSettings> </configuration>
Quais arquivos YAML eu gostaria de ler
Algo assim:
VirtualFeed: MaxChartHistoryLength: 10 Port: 35016 UseThrottling: True ThrottlingIntervalMs: 50000 UseHistoryBroadcast: True CalendarName: "EmptyCalendar" UsMarketFeed: UseImbalances: True
Problemas de transição
Primeiramente, as configurações em XML são "simples", mas em YAML elas não são (seções e subseções são suportadas). Isso é claramente visto nos exemplos acima. Usando XML, resolvemos o problema de configurações simples introduzindo nossos próprios analisadores que podem converter cadeias de caracteres de um determinado tipo em nossas classes mais complexas. Um exemplo de uma cadeia tão complexa:
<setting name="MCXSettings" serializeAs="String"> <value>Inactive, ChartLen: 1000, PrintLen: 50, UseProxy: False</value> </setting>
Não tenho vontade de fazer essas transformações ao trabalhar com o YAML. Mas, ao mesmo tempo, somos limitados pela estrutura "plana" existente da classe AppSettings: todas as propriedades das configurações nela são empilhadas em uma pilha.
Em segundo lugar, as configurações de nossos servidores não são um monólito estático, nós as alteramos de tempos em tempos, no decorrer do trabalho do servidor, ou seja, essas mudanças precisam ser ativadas em tempo real. Para fazer isso, na implementação XML, herdamos nosso AppSettings de INotifyPropertyChanged (de fato, toda interface que implementa o AppSettings é herdada dele) e nos inscrevemos para atualizar eventos de configurações de propriedades. Essa abordagem funciona porque a classe base System.Configuration.ApplicationSettingsBase pronto para uso implementa INotifyPropertyChanged. Um comportamento semelhante deve ser mantido após a transição para o YAML.
Em terceiro lugar, na verdade, não temos um arquivo de configuração para cada servidor, mas dois como: um com configurações padrão e outro com configurações substituídas. Isso é necessário para que, em cada uma das várias instâncias de servidores do mesmo tipo, escute portas diferentes e tenha configurações ligeiramente diferentes, você não precise copiar completamente todo o conjunto de configurações.
E mais um problema : o acesso às configurações passa não apenas pelas interfaces, mas também pelo acesso direto ao AppSettings.Default. Deixe-me lembrá-lo de como é declarado nos AppSettings.Designer.cs nos bastidores:
private static AppSettings defaultInstance = ((AppSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new AppSettings()))); public static AppSettings Default { get { return defaultInstance; } }
Com base no exposto, foi necessário criar uma nova abordagem para armazenar configurações no AppSettings.
Solução
Toolkit
Para leitura direta, o YAML decidiu usar bibliotecas prontas disponíveis no NuGet:
- YamlDotNet - github.com/aaubry/YamlDotNet . Na descrição da biblioteca (tradução):
YamlDotNet é a biblioteca .NET para YAML. O YamlDotNet fornece um analisador de baixo nível e um gerador YAML, além de um modelo de objeto de alto nível semelhante ao XmlDocument. Também está incluída uma biblioteca de serialização que permite ler e gravar objetos de / para fluxos YAML.
- NetEscapades.Configuration - github.com/andrewlock/NetEscapades.Configuration . Esse é o próprio provedor de configuração (no sentido de Microsoft.Extensions.Configuration.IConfigurationSource, que é usado ativamente nos aplicativos ASP.NET Core), que lê arquivos YAML usando apenas o mencionado acima no YamlDotNet.
Leia mais sobre como usar essas bibliotecas
aqui .
Transição para YAML
A transição em si foi realizada em dois estágios: no início, simplesmente mudamos de XML para YAML, mas mantendo uma hierarquia plana de arquivos de configuração e, em seguida, inserimos seções nos arquivos YAML. Esses estágios podem, em princípio, ser combinados em um e, para simplificar a apresentação, farei exatamente isso. Todas as ações descritas abaixo foram aplicadas seqüencialmente a cada serviço.
Preparando um arquivo YML
Primeiro, você precisa preparar o arquivo YAML. Nós o chamamos de nome do projeto (útil para testes de integração futuros, que devem poder trabalhar com servidores diferentes e distinguir entre suas configurações), colocar o arquivo na raiz do projeto, ao lado de AppSettings:

No arquivo YML, para iniciantes, vamos salvar uma estrutura "plana":
VirtualFeed: "MaxChartHistoryLength: 10, UseThrottling: True, ThrottlingIntervalMs: 50000, UseHistoryBroadcast: True, CalendarName: EmptyCalendar" VirtualFeedPort: 35016 UsMarketFeedUseImbalances: True
Preenchendo configurações de aplicativos com propriedades de configurações
Transferimos todas as propriedades de AppSettings.Designer.cs para AppSettings.cs, eliminando simultaneamente os atributos supérfluos do designer e o próprio código em get / set-parts.
Foi:
[global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("35016")] public int VirtualFeedPort{ get { return ((int)(this["VirtualFeedPort"])); } set { this["VirtualFeedPort"] = value; } }
Tornou-se:
public int VirtualFeedPort { get; set; }
Removeremos completamente o AppSettings
.Designer .cs como desnecessário. Agora, a propósito, você pode se livrar completamente da seção userSettings no arquivo app.config, se estiver no projeto - as mesmas configurações padrão são armazenadas lá, que especificamos através do designer de configurações.
Vá em frente.
Controlar as configurações em tempo real
Como precisamos conseguir atualizações de nossas configurações em tempo de execução, precisamos implementar INotifyPropertyChanged em nossas AppSettings. A base System.Configuration.ApplicationSettingsBase não está mais lá, respectivamente, você não pode contar com nenhuma mágica.
Você pode implementá-lo “na testa”: adicionando uma implementação de um método que lança o evento desejado e o chama no setter de cada propriedade. Mas essas são linhas de código extras, que além disso precisarão ser copiadas em todos os serviços.
Vamos fazer o melhor: introduza o AutoNotifier da classe base auxiliar, que na verdade faz a mesma coisa, mas nos bastidores, assim como System.Configuration.ApplicationSettingsBase fez antes:
Aqui, o atributo [CallerMemberName] permite obter automaticamente o nome da propriedade do objeto que está chamando, ou seja, AppSettings
Agora podemos herdar nosso AppSettings dessa AutoNotifier de classe base e, em seguida, cada propriedade é ligeiramente modificada:
public int VirtualFeedPort { get { return Get<int>(); } set { Set(value); } }
Com essa abordagem, nossas classes AppSettings, mesmo contendo muitas configurações, parecem compactas e, ao mesmo tempo, implementam completamente INotifyPropertyChanged.
Sim, eu sei que seria possível introduzir um pouco mais de mágica, usando, por exemplo, o Castle.DynamicProxy.IInterceptor, interceptando alterações nas propriedades necessárias e gerando eventos lá. Mas essa decisão me pareceu muito sobrecarregada.
Lendo configurações de um arquivo YAML
A próxima etapa é adicionar o leitor da própria configuração YAML. Isso acontece em algum lugar mais próximo do início do serviço. Ocultando detalhes desnecessários que não estão relacionados ao tópico em discussão, obtemos algo semelhante:
public static IServerConfigurationProvider LoadServerConfiguration(IReadOnlyDictionary<Type, string> allSections) { IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(ConfigFiles.BasePath); foreach (string configFile in configFiles) { string directory = Path.GetDirectoryName(configFile); if (!string.IsNullOrEmpty(directory))
No código apresentado, o ConfigurationBuilder provavelmente não é de interesse especial - todo o trabalho com ele é semelhante ao trabalho com configurações no ASP.NET Core. Mas os seguintes pontos são de interesse. Primeiro, “pronto para usar” também tivemos a oportunidade de combinar configurações de vários arquivos. Isso fornece o requisito de ter pelo menos dois arquivos de configuração por servidor, como mencionei acima. Em segundo lugar, passamos toda a configuração de leitura para um determinado ServerConfigurationProvider. Porque
Seções no arquivo YAML
Responderemos a essa pergunta mais tarde e agora voltamos ao requisito de armazenar configurações estruturadas hierarquicamente em um arquivo YML.
Em princípio, implementar isso é bastante simples. Primeiro, no arquivo YML, apresentamos a estrutura de que precisamos:
VirtualFeed: MaxChartHistoryLength: 10 Port: 35016 UseThrottling: True ThrottlingIntervalMs: 50000 UseHistoryBroadcast: True CalendarName: "EmptyCalendar" UsMarketFeed: UseImbalances: True
Agora vamos para AppSettings e ensiná-lo a dividir nossas propriedades em seções. Algo assim:
public sealed class AppSettings : AutoNotifier, IWebSettingsStorage, IServerSettingsStorage, IServerManagerAddressStorage, IGlobalCredentialsStorage, IGraphiteAddressStorage, IDatabaseConfigStorage, IBlackListStorage, IKeyCloackConfigFilePathProvider, IPrometheusSettingsStorage, IHeartBeatConfig, IConcurrentAcceptorProperties, IMetricsConfig { public static IReadOnlyDictionary<Type, string> Sections { get; } = new Dictionary<Type, string> { {typeof(IDatabaseConfigStorage), "Database"}, {typeof(IWebSettingsStorage), "Web"}, {typeof(IServerSettingsStorage), "Server"}, {typeof(IConcurrentAcceptorProperties), "ConcurrentAcceptor"}, {typeof(IGraphiteAddressStorage), "Graphite"}, {typeof(IKeyCloackConfigFilePathProvider), "Keycloak"}, {typeof(IPrometheusSettingsStorage), "Prometheus"}, {typeof(IHeartBeatConfig), "Heartbeat"}, {typeof(IServerManagerAddressStorage), "ServerManager"}, {typeof(IGlobalCredentialsStorage), "GlobalCredentials"}, {typeof(IBlackListStorage), "Blacklist"}, {typeof(IMetricsConfig), "Metrics"} }; ...
Como você pode ver, adicionamos um dicionário diretamente ao AppSettings, onde as chaves são os tipos de interfaces que a classe AppSettings implementa e os valores são os cabeçalhos das seções correspondentes. Agora podemos comparar a hierarquia no arquivo YML com a hierarquia de propriedades no AppSettings (embora não seja mais profunda que um nível de aninhamento, mas, no nosso caso, isso foi suficiente).
Por que estamos fazendo isso aqui - no AppSettings? Porque, dessa maneira, não divulgamos as informações sobre as configurações para diferentes entidades e, além disso, este é o local mais natural, porque em cada serviço e, consequentemente, em cada AppSettings, sua própria seção de configurações.
Se você não precisa de uma hierarquia nas configurações?
Em princípio, é um caso estranho, mas o tivemos exatamente no primeiro estágio, quando simplesmente mudamos de XML para YAML, sem usar as vantagens do YAML.
Nesse caso, toda a lista de seções não pode ser armazenada e o ServerConfigurationProvider será muito mais simples (discutido posteriormente).
Mas o ponto importante é que, se decidirmos deixar uma hierarquia simples, podemos apenas cumprir o requisito de manter a capacidade de acessar configurações por meio do AppSettings.Default. Para fazer isso, adicione aqui um construtor público tão simples no AppSettings:
public static AppSettings Default { get; } public AppSettings() { Default = this; }
Agora, podemos continuar acessando a classe de configurações em qualquer lugar através do AppSettings.Default (desde que as configurações já tenham sido lidas através de IConfigurationRoot no ServerConfigurationProvider e, consequentemente, o AppSettings foi instanciado).
Se uma hierarquia plana é inaceitável, você precisa se livrar do AppSettings.Default em todos os lugares por código e trabalhar com as configurações apenas por meio de interfaces (o que é bom em princípio). Por que isso - ficará mais claro.
ServerConfigurationProvider
A classe ServerConfigurationProvider especial mencionada anteriormente lida com a própria magia que permite trabalhar totalmente com a nova configuração hierárquica do YAML com apenas um AppSettings simples.
Se você não pode esperar, aqui está.
Código completo do ServerConfigurationProvider ServerConfigurationProvider é parametrizado pela classe de configurações AppSettings:
public class ServerConfigurationProvider<TAppSettings> : IServerConfigurationProvider where TAppSettings : new()
Isso, como você pode imaginar, permite usá-lo imediatamente em todos os serviços.
A própria configuração de leitura (IConfigurationRoot), bem como o dicionário de seção mencionado acima (AppSettings.Sections), são passados para o construtor. Há uma assinatura para atualizações de arquivo (queremos extrair imediatamente essas alterações para nós em caso de alteração no arquivo YML?):
_configuration.GetReloadToken()?.RegisterChangeCallback(OnConfigurationFileChanged, null); ... private void OnConfigurationFileChanged(object _) { foreach (string sectionName in _cachedSections.Keys) { Type sectionInterface = _interfacesBySections[sectionName]; TAppSettings newSection = ReadSection(sectionName, sectionInterface); TAppSettings oldSection; if (_cachedSections.TryGetValue(sectionName, out oldSection)) { UpdateSection(oldSection, newSection); } } }
Como você pode ver aqui, no caso de atualizar o arquivo YML, examinamos todas as seções que conhecemos e lemos cada uma. Então, se a seção já foi lida anteriormente no cache (ou seja, já foi solicitada em algum lugar do código por alguma classe), reescrevemos os valores antigos no cache por novos.
Parece - por que ler cada seção, por que não ler apenas aqueles que estão no cache (ou seja, exigidos)? Porque, ao ler a seção, implementamos uma verificação para a configuração correta. E, no caso de configurações incorretas, os alertas correspondentes são lançados, os problemas são registrados. É melhor aprender sobre os problemas nas alterações de configuração o mais rápido possível, a partir das quais lemos todas as seções imediatamente.
Atualizar valores antigos no cache com novos valores é bastante trivial:
private void UpdateSection(TAppSettings oldConfig, TAppSettings newConfig) { foreach (PropertyInfo propertyInfo in typeof(TAppSettings).GetProperties().Where(p => p.GetMethod != null && p.SetMethod != null)) { propertyInfo.SetValue(newConfig, propertyInfo.GetValue(oldConfig)); } }
Mas ler seções não é tão simples:
private TAppSettings ReadSection(string sectionName, Type sectionInterface) { TAppSettings parsed; try { IConfigurationSection section = _configuration.GetSection(sectionName); CheckSection(section, sectionName, sectionInterface); parsed = section.Get<TAppSettings>(); if (parsed == null) {
Aqui, em primeiro lugar, lemos a própria seção usando o IConfigurationRoot.GetSection padrão.
Em seguida, basta verificar a exatidão da seção de leitura.Em seguida, lemos a seção bindim para o tipo de nossas configurações: section.Get Aqui encontramos um recurso do analisador YAML - ele não faz distinção entre uma seção vazia (sem parâmetros, ou seja, ausente) de uma seção na qual todos os parâmetros estão vazios.Aqui está um caso semelhante: VirtualFeed: Names: []
Aqui na seção VirtualFeed, há um parâmetro Names com uma lista vazia de valores, mas o analisador YAML, infelizmente, dirá que a seção VirtualFeed geralmente está completamente vazia. Isso é triste
E, finalmente, nesse método, um pouco de magia de rua é implementada para dar suporte às propriedades IEnumerable nas configurações. Não conseguimos obter uma leitura normal das listas "prontas para uso". ReadArrays(parsed, section); ...
Como você pode ver, encontramos todas as propriedades cujo tipo é herdado de IEnumerable e atribuímos valores a elas na "seção" fictícia, também chamada de configuração em que estamos interessados. Mas antes disso, não se esqueça de verificar: existe um valor substituído dessa propriedade enumerada no segundo arquivo de configuração? Se houver, nós apenas o aceitamos e limpamos as configurações lidas no arquivo de configuração base. Se isso não for feito, as duas propriedades (do arquivo base e do substituído) serão automaticamente mescladas em uma matriz no nível IConfigurationSection, e os índices da matriz servirão como chaves para a combinação. Isso resultará em algum tipo de hash em vez do valor normal substituído.O método ReadSection mostrado é finalmente utilizado no método principal da classe: FindSection. [CanBeNull] public object FindSection(Type sectionInterface) { string sectionName = FindSectionName(sectionInterface); if (sectionName == null) { return null; }
Em princípio, fica claro por que, com o suporte das seções, não podemos oferecer suporte a AppSettings.Default de qualquer maneira: todo acesso a uma nova seção de configurações (não lida anteriormente) através do FindSection nos fornecerá uma nova instância da classe AppSettings, embora anexada à interface desejada e, portanto, se usássemos AppSettings.Default, ela seria redefinida toda vez que uma nova seção fosse lida e conteria apenas as configurações que pertencem à última seção de leitura (o restante teria valores padrão - NULL e 0).A validação das configurações na seção é implementada da seguinte maneira: private void CheckSection(IConfigurationSection section, string sectionName, Type sectionInterface) { ICollection<PropertyInfo> properties = GetPublicProperties(sectionInterface, needSetters: false); var configProperties = new HashSet<string>(section.GetChildren().Select(c => c.Key)); foreach (PropertyInfo propertyInfo in properties) { if (!configProperties.Remove(propertyInfo.Name)) { if (propertyInfo.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType)) {
Aqui, primeiro, todas as propriedades públicas da interface em que estamos interessados são extraídas (seções de configurações de leitura). E para cada uma dessas propriedades, uma correspondência é encontrada nas configurações de leitura: se nenhuma correspondência for encontrada, o problema correspondente será registrado, porque isso significa que falta alguma configuração no arquivo de configuração. No final, é verificado adicionalmente se alguma das configurações de leitura permaneceu incomparável com a interface. Se houver, o problema será registrado novamente, porque isso significa que as propriedades que não são descritas na interface foram encontradas no arquivo de configuração, o que também não deve estar em uma situação normal.Surge a pergunta - de onde vem o requisito, que no arquivo de leitura todas as configurações correspondam às disponíveis na interface individualmente? O fato é que, como mencionado acima, naquele momento, nenhum arquivo foi lido, mas dois de uma vez - um com as configurações padrão e outro com os substituídos, e ambos são contíguos. Portanto, de fato, não estamos analisando as configurações de um arquivo, mas as completas. E, nesse caso, é claro, seu conjunto deve corresponder às configurações esperadas um a um.Preste atenção também nas fontes acima ao método GetPublicProperties, que, ao que parece, retorna apenas todas as propriedades públicas da interface. Mas não é tão simples quanto poderia ser, porque às vezes temos uma interface que descreve as configurações do servidor que são herdadas de outra interface e, portanto, é necessário examinar toda a hierarquia de interfaces para encontrar todas as propriedades públicas.Obtendo configurações do servidor
Com base no exposto, para obter as configurações do servidor em qualquer lugar por código, passamos à seguinte interface:
O primeiro método dessa interface - FindSection - permite acessar a seção de configurações de seu interesse. Algo assim:
IThreadPoolProperties threadPoolProperties = ConfigurationProvider.FindSection<IThreadPoolProperties>();
Por que o segundo e o terceiro método são necessários - explicarei mais adiante.Registro de interfaces de configurações
Em nosso projeto, o Castle Windsor é usado como um contêiner de IoC. É ele quem fornece, incluindo as interfaces de configuração do servidor. Portanto, essas interfaces devem ser registradas nele.Para esse propósito, uma classe Extension simples foi escrita, o que simplifica esse procedimento para não gravar o registro de todo o conjunto de interfaces em cada servidor: public static class ServerConfigurationProviderExtensions { public static void RegisterAllConfigurationSections(this IWindsorContainer container, IServerConfigurationProvider configurationProvider) { Register(container, configurationProvider, configurationProvider.AllSections.ToArray()); } public static void Register(this IWindsorContainer container, IServerConfigurationProvider configurationProvider, params Type[] configSections) { var registrations = new IRegistration[configSections.Length]; for (int i = 0; i < registrations.Length; i++) { Type configSection = configSections[i]; object section = configurationProvider.FindSection(configSection); registrations[i] = Component.For(configSection).Instance(section).Named(configSection.FullName); } container.Register(registrations); } }
O primeiro método permite registrar todas as seções das configurações (para isso, você precisa da propriedade AllSections na interface IServerConfigurationProvider).E o segundo método é usado no primeiro, e ele lê automaticamente a seção de configurações especificadas usando nosso ServerConfigurationProvider, gravando-o imediatamente no cache ServerConfigurationProvider e registrando-o em Windsor.É aqui que o segundo método FindSection, não parametrizado, de IServerConfigurationProvider é usado.Resta apenas chamar nosso método de extensão no código de registro de contêiner de Windsor: container.RegisterAllConfigurationSections(configProvider);
Conclusão
O que aconteceu
Da maneira apresentada, foi possível transferir sem esforço todas as configurações de nossos servidores de XML para YAML, enquanto fazia um mínimo de alterações no código do servidor existente.As configurações de YAML, diferentemente do XML, acabaram sendo mais legíveis devido não apenas à maior concisão, mas também ao suporte ao particionamento.Não inventamos nossas próprias bicicletas para analisar o YAML, mas usamos soluções prontas. No entanto, para integrá-los às realidades do nosso projeto, foram necessários alguns dos truques descritos neste artigo. Espero que sejam úteis para os leitores.Foi possível manter a capacidade de detectar alterações nas configurações nos focinhos da web de nossos servidores em tempo real. Além disso, como bônus, também foi possível capturar alterações no arquivo YAML em tempo real (anteriormente, era necessário reiniciar o servidor para quaisquer alterações nos arquivos de configuração).Mantivemos a capacidade de mesclar dois arquivos de configuração - as configurações padrão e substituídas, e fizemos isso usando soluções de terceiros prontas para uso.O que não deu muito certo
Eu tive que abandonar a capacidade disponível anteriormente para salvar as alterações aplicadas das faces da web de nossos servidores nos arquivos de configuração, porque o suporte a essa funcionalidade exigiria grandes gestos, e a tarefa comercial diante de nós em geral não era essa.Bem, eu também tive que recusar o acesso às configurações por meio do AppSettings.Default, mas isso é mais um plus que um menos.