Padrão de Arquivo de Configuração ExternoExternal Configuration Store pattern

Mova as informações de configuração para fora do pacote de implementação de aplicação para uma localização centralizada.Move configuration information out of the application deployment package to a centralized location. Assim, pode poderá gerir e controlar os dados de configuração de forma mais simples e partilhar os dados de configuração entre as aplicações e as instâncias da aplicação.This can provide opportunities for easier management and control of configuration data, and for sharing configuration data across applications and application instances.

Contexto e problemaContext and problem

A maioria dos ambientes de tempo de execução das aplicações incluem informações de configuração que são mantidas em ficheiros implementados com a aplicação.The majority of application runtime environments include configuration information that's held in files deployed with the application. Em alguns casos, é possível editar estes ficheiros para alterar o comportamento da aplicação depois da implementação.In some cases, it's possible to edit these files to change the application behavior after it's been deployed. No entanto, as alterações à configuração precisam que a aplicação seja reimplementada, o que dá origem a frequentes períodos de indisponibilidade inaceitáveis e outras sobrecargas administrativas.However, changes to the configuration require the application be redeployed, often resulting in unacceptable downtime and other administrative overhead.

Os ficheiros de configuração local também limitam a configuração a uma única aplicação. Contudo, por vezes, poderá ser útil partilhar as definições de configuração entre as várias aplicações.Local configuration files also limit the configuration to a single application, but sometimes it would be useful to share configuration settings across multiple applications. Os exemplos incluem as cadeias de ligação de base de dados, as informações relativas a temas da IU ou os URLs das filas e o armazenamento utilizados por um conjunto relacionado de aplicações.Examples include database connection strings, UI theme information, or the URLs of queues and storage used by a related set of applications.

É difícil gerir as alterações às configurações locais em várias instâncias em execução da aplicação, especialmente num cenário de alojamento na cloud.It's challenging to manage changes to local configurations across multiple running instances of the application, especially in a cloud-hosted scenario. Tal pode resultar em instâncias com diferentes definições de configuração enquanto a atualização está a ser implementada.It can result in instances using different configuration settings while the update is being deployed.

Além disso, as atualizações das aplicações e dos componentes podem exigir alterações aos esquemas de configuração.In addition, updates to applications and components might require changes to configuration schemas. Muitos sistemas de configuração não suportam versões de informações de configuração diferentes.Many configuration systems don't support different versions of configuration information.

SoluçãoSolution

Armazene as informações de configuração no armazenamento externo e disponibilize uma interface que pode ser utilizada para ler e atualizar as definições de configuração de forma rápida e eficiente.Store the configuration information in external storage, and provide an interface that can be used to quickly and efficiently read and update configuration settings. O tipo de arquivo externo depende do ambiente de alojamento e de tempo de execução da aplicação.The type of external store depends on the hosting and runtime environment of the application. Num cenário de alojamento na cloud, é normalmente um serviço de armazenamento baseado na cloud, mas pode ser uma base de dados alojada ou outro sistema.In a cloud-hosted scenario it's typically a cloud-based storage service, but could be a hosted database or other system.

O armazenamento de segurança que escolher para as informações de configuração deve ter uma interface que disponibiliza um acesso consistente e fácil de utilizar.The backing store you choose for configuration information should have an interface that provides consistent and easy-to-use access. Este deve expor as informações num formato estruturado e escrito corretamente.It should expose the information in a correctly typed and structured format. A implementação pode também precisar de autorizar o acesso dos utilizadores para proteger os dados de configuração e ser flexível o suficiente para permitir o armazenamento de várias versões da configuração (por exemplo, o desenvolvimento, o teste ou a produção, incluindo várias versões de cada um).The implementation might also need to authorize users’ access in order to protect configuration data, and be flexible enough to allow storage of multiple versions of the configuration (such as development, staging, or production, including multiple release versions of each one).

Muitos sistemas de configuração incorporados leem os dados quando a aplicação é iniciada e colocam os dados em cache na memória para fornecer um acesso rápido e minimizar o impacto no desempenho da aplicação.Many built-in configuration systems read the data when the application starts up, and cache the data in memory to provide fast access and minimize the impact on application performance. Dependendo do tipo de armazenamento de segurança utilizado e da latência deste arquivo, pode ser útil implementar um mecanismo de colocação em cache dentro do arquivo de configuração externo.Depending on the type of backing store used, and the latency of this store, it might be helpful to implement a caching mechanism within the external configuration store. Para obter mais informações, veja Orientações sobre a Colocação em Cache.For more information, see the Caching Guidance. A figura mostra uma descrição geral do padrão de Arquivo de Configuração Externo com a cache local opcional.The figure illustrates an overview of the External Configuration Store pattern with optional local cache.

Descrição geral do padrão de Arquivo de Configuração Externo com a cache local opcional

Problemas e consideraçõesIssues and considerations

Na altura de decidir como implementar este padrão, considere os seguintes pontos:Consider the following points when deciding how to implement this pattern:

Escolha um armazenamento de segurança que oferece um desempenho aceitável, elevada disponibilidade, robustez e permite a criação de uma cópia de segurança como parte do processo de manutenção e administração da aplicação.Choose a backing store that offers acceptable performance, high availability, robustness, and can be backed up as part of the application maintenance and administration process. Numa aplicação alojada na cloud, é normalmente uma boa opção utilizar um mecanismo de armazenamento na cloud para cumprir estes requisitos.In a cloud-hosted application, using a cloud storage mechanism is usually a good choice to meet these requirements.

Crie o esquema do armazenamento de segurança para permitir flexibilidade nos tipos de informações que pode conter.Design the schema of the backing store to allow flexibility in the types of information it can hold. Confirme se este cumpre todos os requisitos de configuração, tal como dados digitados, coleções de definições, várias versões de definições e quaisquer outras funcionalidades que as aplicações que o utilizam precisam.Ensure that it provides for all configuration requirements such as typed data, collections of settings, multiple versions of settings, and any other features that the applications using it require. O esquema deve ser fácil de expandir para suportar definições adicionais à medida que os requisitos se vão alterando.The schema should be easy to extend to support additional settings as requirements change.

Considere as capacidades físicas do armazenamento de segurança, como se relaciona com a forma como as informações de configuração são armazenadas e os efeitos no desempenho.Consider the physical capabilities of the backing store, how it relates to the way configuration information is stored, and the effects on performance. Por exemplo, o armazenamento de um documento XML que contém informações de configuração exigirá que a interface de configuração ou a aplicação analise o documento para ler as definições individuais.For example, storing an XML document containing configuration information will require either the configuration interface or the application to parse the document in order to read individual settings. Tal vai tornar a atualização de uma definição mais complicada, embora a colocação das definições em cache possa ajudar a compensar o desempenho de leitura mais lento.It'll make updating a setting more complicated, though caching the settings can help to offset slower read performance.

Considere a forma como a interface de configuração vai permitir o controlo do âmbito e da herança das definições de configuração.Consider how the configuration interface will permit control of the scope and inheritance of configuration settings. Por exemplo, pode ser um requisito definir o âmbito das definições de configuração ao nível da organização, da aplicação e do computador.For example, it might be a requirement to scope configuration settings at the organization, application, and the machine level. Pode ter de suportar a delegação do controlo sobre o acesso a diferentes âmbitos e impedir ou permitir que as aplicações individuais substituam as definições.It might need to support delegation of control over access to different scopes, and to prevent or allow individual applications to override settings.

Confirme se a interface de configuração pode expor os dados de configuração nos formatos necessários, tal como valores digitados, coleções, pares chave/valor ou matrizes de propriedades.Ensure that the configuration interface can expose the configuration data in the required formats such as typed values, collections, key/value pairs, or property bags.

Considere a forma como a interface do arquivo de configuração se vai comportar quando as definições contêm erros ou não existem no armazenamento de segurança.Consider how the configuration store interface will behave when settings contain errors, or don't exist in the backing store. Talvez seja adequado reverter para as predefinições e registar os erros.It might be appropriate to return default settings and log errors. Considere também aspetos como a sensibilidade às maiúsculas e minúsculas das chaves ou dos nomes da definição de configuração, o armazenamento e o processamento de dados binários e a forma como os valores nulos ou vazios são processados.Also consider aspects such as the case sensitivity of configuration setting keys or names, the storage and handling of binary data, and the ways that null or empty values are handled.

Considere como proteger os dados de configuração para permitir o acesso apenas aos utilizadores e às aplicações adequados.Consider how to protect the configuration data to allow access to only the appropriate users and applications. Trata-se provavelmente de uma funcionalidade da interface do arquivo de configuração, mas também é necessária para garantir que os dados no armazenamento de segurança não podem ser acedidos diretamente sem a permissão adequada.This is likely a feature of the configuration store interface, but it's also necessary to ensure that the data in the backing store can't be accessed directly without the appropriate permission. Confirme que existe uma separação restrita entre as permissões necessárias para ler e para escrever dados de configuração.Ensure strict separation between the permissions required to read and to write configuration data. Considere também a necessidade de encriptar algumas ou todas as definições de configuração e como isto vai ser implementado na interface do arquivo de configuração.Also consider whether you need to encrypt some or all of the configuration settings, and how this'll be implemented in the configuration store interface.

As configurações armazenadas centralmente, que alteram o comportamento da aplicação durante o tempo de execução, são extremamente importantes e devem ser implementadas, atualizadas e geridas com os mesmos mecanismos da implementação do código da aplicação.Centrally stored configurations, which change application behavior during runtime, are critically important and should be deployed, updated, and managed using the same mechanisms as deploying application code. Por exemplo, as alterações que podem afetar mais do que uma aplicação devem ser realizadas com um teste completo e uma abordagem de implementação de teste para garantir que a alteração é adequada para todas as aplicações que utilizam esta configuração.For example, changes that can affect more than one application must be carried out using a full test and staged deployment approach to ensure that the change is appropriate for all applications that use this configuration. Se um administrador editar uma definição para atualizar uma aplicação, esta operação poderá afetar negativamente as outras aplicações que utilizam a mesma definição.If an administrator edits a setting to update one application, it could adversely impact other applications that use the same setting.

Se uma aplicação colocar as informações de configuração em cache, a aplicação terá de ser alertada caso a configuração seja alterada.If an application caches configuration information, the application needs to be alerted if the configuration changes. Pode ser possível implementar uma política de expiração para os dados de configuração em cache para que estas informações sejam automaticamente atualizadas de forma periódica e quando são detetadas (e abordadas) quaisquer alterações.It might be possible to implement an expiration policy over cached configuration data so that this information is automatically refreshed periodically and any changes picked up (and acted on).

Quando utilizar este padrãoWhen to use this pattern

Este padrão é útil nas seguintes situações:This pattern is useful for:

  • Definições de configuração partilhadas entre várias aplicações e instâncias da aplicação ou quando uma configuração padrão tem de ser imposta em várias aplicações e instâncias da aplicação.Configuration settings that are shared between multiple applications and application instances, or where a standard configuration must be enforced across multiple applications and application instances.

  • Um sistema de configuração padrão que não suporta todas as definições de configuração necessárias, tal como armazenar imagens ou tipos de dados complexos.A standard configuration system that doesn't support all of the required configuration settings, such as storing images or complex data types.

  • Enquanto arquivo complementar de algumas das definições das aplicações, com a eventual possibilidade de as aplicações substituírem algumas ou todas as definições armazenadas centralmente.As a complementary store for some of the settings for applications, perhaps allowing applications to override some or all of the centrally-stored settings.

  • Como forma de simplificar a administração de várias aplicações e, opcionalmente, para monitorizar a utilização de definições de configuração através do registo de alguns ou de todos os tipos de acesso ao arquivo de configuração.As a way to simplify administration of multiple applications, and optionally for monitoring use of configuration settings by logging some or all types of access to the configuration store.

ExemploExample

Numa aplicação alojada no Microsoft Azure, uma opção típica para armazenar informações de configuração externamente consiste na utilização do Armazenamento do Microsoft Azure.In a Microsoft Azure hosted application, a typical choice for storing configuration information externally is to use Azure Storage. Este é resiliente, oferece um desempenho elevado e é replicado três vezes com ativação pós-falha automática para oferecer elevada disponibilidade.This is resilient, offers high performance, and is replicated three times with automatic failover to offer high availability. O Armazenamento de tabelas do Azure fornece um arquivo chave/valor com a possibilidade de utilizar um esquema flexível para os valores.Azure Table storage provides a key/value store with the ability to use a flexible schema for the values. O Armazenamento de blobs do Azure fornece um arquivo hierárquico baseado em contentor que pode conter qualquer tipo de dados em blobs com nomes individuais.Azure Blob storage provides a hierarchical, container-based store that can hold any type of data in individually named blobs.

O exemplo seguinte mostra como um arquivo de configuração pode ser implementado no Armazenamento de blobs para armazenar e expor as informações de configuração.The following example shows how a configuration store can be implemented over Blob storage to store and expose configuration information. A classe BlobSettingsStore extrai o Armazenamento de Blobs para conter as informações de configuração e implementa a interface ISettingsStore ilustrada no seguinte código.The BlobSettingsStore class abstracts Blob storage for holding configuration information, and implements the ISettingsStore interface shown in the following code.

Este código é fornecido no projeto ExternalConfigurationStore.Cloud na solução ExternalConfigurationStore, disponível no GitHub.This code is provided in the ExternalConfigurationStore.Cloud project in the ExternalConfigurationStore solution, available from GitHub.

public interface ISettingsStore
{
    Task<string> GetVersionAsync();

    Task<Dictionary<string, string>> FindAllAsync();
}

Esta interface define métodos para obter e atualizar as definições de configuração contidas no arquivo de configuração e inclui um número da versão que pode ser utilizado para detetar se as definições de configuração foram modificadas recentemente.This interface defines methods for retrieving and updating configuration settings held in the configuration store, and includes a version number that can be used to detect whether any configuration settings have been modified recently. A classe BlobSettingsStore utiliza a propriedade ETag do blob para implementar o controlo de versões.The BlobSettingsStore class uses the ETag property of the blob to implement versioning. A propriedade ETag é atualizada automaticamente sempre que o blob é escrito.The ETag property is updated automatically each time the blob is written.

Por predefinição, esta solução simples expõe todas as definições de configuração como valores de cadeia em vez de valores escritos.By design, this simple solution exposes all configuration settings as string values rather than typed values.

A classe ExternalConfigurationManager fornece um wrapper em torno de um objeto BlobSettingsStore.The ExternalConfigurationManager class provides a wrapper around a BlobSettingsStore object. Uma aplicação pode utilizar esta classe para armazenar e obter informações de configuração.An application can use this class to store and retrieve configuration information. Esta classe utiliza a biblioteca Extensões Reativas da Microsoft para expor quaisquer alterações realizadas à configuração através de uma implementação da interface IObservable.This class uses the Microsoft Reactive Extensions library to expose any changes made to the configuration through an implementation of the IObservable interface. Se uma definição for modificada ao chamar o método SetAppSetting, o evento Changed será gerado e todos os subscritores deste evento serão notificados.If a setting is modified by calling the SetAppSetting method, the Changed event is raised and all subscribers to this event will be notified.

Tenha em atenção que todas as definições também são colocadas em cache num objeto Dictionary dentro da classe ExternalConfigurationManager para acesso rápido.Note that all settings are also cached in a Dictionary object inside the ExternalConfigurationManager class for fast access. O método GetSetting utilizado para obter uma definição de configuração lê os dados da cache.The GetSetting method used to retrieve a configuration setting reads the data from the cache. Se a definição não for encontrada na cache, será obtida através do objeto BlobSettingsStore.If the setting isn't found in the cache, it's retrieved from the BlobSettingsStore object instead.

O método GetSettings invoca o método CheckForConfigurationChanges para detetar se as informações de configuração no armazenamento de blobs foram alteradas.The GetSettings method invokes the CheckForConfigurationChanges method to detect whether the configuration information in blob storage has changed. Para isso, o método examina o número da versão e compara-o com o número da versão atual retido pelo objeto ExternalConfigurationManager.It does this by examining the version number and comparing it with the current version number held by the ExternalConfigurationManager object. Se tiverem ocorrido uma ou mais alterações, o evento Changed será gerado e as definições de configuração colocadas em cache no objeto Dictionary serão atualizadas.If one or more changes have occurred, the Changed event is raised and the configuration settings cached in the Dictionary object are refreshed. Esta é uma aplicação do padrão de Cache-Aside.This is an application of the Cache-Aside pattern.

O seguinte código de exemplo mostra como o evento Changed, o método GetSettings e o método CheckForConfigurationChanges são implementados:The following code sample shows how the Changed event, the GetSettings method, and the CheckForConfigurationChanges method are implemented:

public class ExternalConfigurationManager : IDisposable
{
  // An abstraction of the configuration store.
  private readonly ISettingsStore settings;
  private readonly ISubject<KeyValuePair<string, string>> changed;
  ...
  private readonly ReaderWriterLockSlim settingsCacheLock = new ReaderWriterLockSlim();
  private readonly SemaphoreSlim syncCacheSemaphore = new SemaphoreSlim(1);  
  ...
  private Dictionary<string, string> settingsCache;
  private string currentVersion;
  ...
  public ExternalConfigurationManager(ISettingsStore settings, ...)
  {
    this.settings = settings;
    ...
  }
  ...
  public IObservable<KeyValuePair<string, string>> Changed => this.changed.AsObservable();
  ...

  public string GetAppSetting(string key)
  {
    ...
    // Try to get the value from the settings cache.
    // If there's a cache miss, get the setting from the settings store and refresh the settings cache.

    string value;
    try
    {
        this.settingsCacheLock.EnterReadLock();

        this.settingsCache.TryGetValue(key, out value);
    }
    finally
    {
        this.settingsCacheLock.ExitReadLock();
    }

    return value;
  }
  ...
  private void CheckForConfigurationChanges()
  {
    try
    {
        // It is assumed that updates are infrequent.
        // To avoid race conditions in refreshing the cache, synchronize access to the in-memory cache.
        await this.syncCacheSemaphore.WaitAsync();

        var latestVersion = await this.settings.GetVersionAsync();

        // If the versions are the same, nothing has changed in the configuration.
        if (this.currentVersion == latestVersion) return;

        // Get the latest settings from the settings store and publish changes.
        var latestSettings = await this.settings.FindAllAsync();

        // Refresh the settings cache.
        try
        {
            this.settingsCacheLock.EnterWriteLock();

            if (this.settingsCache != null)
            {
                //Notify settings changed
                latestSettings.Except(this.settingsCache).ToList().ForEach(kv => this.changed.OnNext(kv));
            }
            this.settingsCache = latestSettings;
        }
        finally
        {
            this.settingsCacheLock.ExitWriteLock();
        }

        // Update the current version.
        this.currentVersion = latestVersion;
    }
    catch (Exception ex)
    {
        this.changed.OnError(ex);
    }
    finally
    {
        this.syncCacheSemaphore.Release();
    }
  }
}

A classe ExternalConfigurationManager também fornece uma propriedade chamada Environment.The ExternalConfigurationManager class also provides a property named Environment. Esta propriedade suporta configurações diferentes para uma aplicação em execução em ambientes diferentes, tal como teste e produção.This property supports varying configurations for an application running in different environments, such as staging and production.

Um objeto ExternalConfigurationManager também pode consultar o objeto BlobSettingsStore periodicamente para realizar quaisquer alterações.An ExternalConfigurationManager object can also query the BlobSettingsStore object periodically for any changes. No seguinte código, o método StartMonitor chama CheckForConfigurationChanges num intervalo definido para detetar quaisquer alterações e gerar o evento Changed, conforme descrito anteriormente.In the following code, the StartMonitor method calls CheckForConfigurationChanges at an interval to detect any changes and raise the Changed event, as described earlier.

public class ExternalConfigurationManager : IDisposable
{
  ...
  private readonly ISubject<KeyValuePair<string, string>> changed;
  private Dictionary<string, string> settingsCache;
  private readonly CancellationTokenSource cts = new CancellationTokenSource();
  private Task monitoringTask;
  private readonly TimeSpan interval;

  private readonly SemaphoreSlim timerSemaphore = new SemaphoreSlim(1);
  ...
  public ExternalConfigurationManager(string environment) : this(new BlobSettingsStore(environment), TimeSpan.FromSeconds(15), environment)
  {
  }
  
  public ExternalConfigurationManager(ISettingsStore settings, TimeSpan interval, string environment)
  {
      this.settings = settings;
      this.interval = interval;
      this.CheckForConfigurationChangesAsync().Wait();
      this.changed = new Subject<KeyValuePair<string, string>>();
      this.Environment = environment;
  }
  ...
  /// <summary>
  /// Check to see if the current instance is monitoring for changes
  /// </summary>
  public bool IsMonitoring => this.monitoringTask != null && !this.monitoringTask.IsCompleted;

  /// <summary>
  /// Start the background monitoring for configuration changes in the central store
  /// </summary>
  public void StartMonitor()
  {
      if (this.IsMonitoring)
          return;

      try
      {
          this.timerSemaphore.Wait();

          // Check again to make sure we are not already running.
          if (this.IsMonitoring)
              return;

          // Start running our task loop.
          this.monitoringTask = ConfigChangeMonitor();
      }
      finally
      {
          this.timerSemaphore.Release();
      }
  }

  /// <summary>
  /// Loop that monitors for configuration changes
  /// </summary>
  /// <returns></returns>
  public async Task ConfigChangeMonitor()
  {
      while (!cts.Token.IsCancellationRequested)
      {
          await this.CheckForConfigurationChangesAsync();
          await Task.Delay(this.interval, cts.Token);
      }
  }

  /// <summary>
  /// Stop monitoring for configuration changes
  /// </summary>
  public void StopMonitor()
  {
      try
      {
          this.timerSemaphore.Wait();

          // Signal the task to stop.
          this.cts.Cancel();

          // Wait for the loop to stop.
          this.monitoringTask.Wait();

          this.monitoringTask = null;
      }
      finally
      {
          this.timerSemaphore.Release();
      }
  }

  public void Dispose()
  {
      this.cts.Cancel();
  }
  ...
}

A classe ExternalConfigurationManager é instanciada como uma instância singleton pela classe ExternalConfiguration ilustrada abaixo.The ExternalConfigurationManager class is instantiated as a singleton instance by the ExternalConfiguration class shown below.

public static class ExternalConfiguration
{
    private static readonly Lazy<ExternalConfigurationManager> configuredInstance = new Lazy<ExternalConfigurationManager>(
        () =>
        {
            var environment = CloudConfigurationManager.GetSetting("environment");
            return new ExternalConfigurationManager(environment);
        });

    public static ExternalConfigurationManager Instance => configuredInstance.Value;
}

O seguinte código é obtido a partir da classe WorkerRole no projeto ExternalConfigurationStore.Cloud.The following code is taken from the WorkerRole class in the ExternalConfigurationStore.Cloud project. Este mostra como a aplicação utiliza a classe ExternalConfiguration para ler uma definição.It shows how the application uses the ExternalConfiguration class to read a setting.

public override void Run()
{
  // Start monitoring configuration changes.
  ExternalConfiguration.Instance.StartMonitor();

  // Get a setting.
  var setting = ExternalConfiguration.Instance.GetAppSetting("setting1");
  Trace.TraceInformation("Worker Role: Get setting1, value: " + setting);

  this.completeEvent.WaitOne();
}

O código seguinte, igualmente da classe WorkerRole, mostra como a aplicação subscreve os eventos de configuração.The following code, also from the WorkerRole class, shows how the application subscribes to configuration events.

public override bool OnStart()
{
  ...
  // Subscribe to the event.
  ExternalConfiguration.Instance.Changed.Subscribe(
     m => Trace.TraceInformation("Configuration has changed. Key:{0} Value:{1}",
          m.Key, m.Value),
     ex => Trace.TraceError("Error detected: " + ex.Message));
  ...
}
  • Um exemplo que demonstra este padrão está disponível no GitHub.A sample that demonstrates this pattern is available on GitHub.