Padrão de repositório de configuração externoExternal Configuration Store pattern

Mova as informações de configuração para fora do pacote de implantação de aplicativo para um local centralizado.Move configuration information out of the application deployment package to a centralized location. Ele pode fornecer oportunidades para facilitar o gerenciamento e controle dos dados de configuração e para compartilhar os dados de configuração nos aplicativos e instâncias do aplicativo.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 runtime do aplicativo inclui informações de configuração que são mantidas nos arquivos implantados com o aplicativo.The majority of application runtime environments include configuration information that's held in files deployed with the application. Em alguns casos, é possível editar esses arquivos para alterar o comportamento do aplicativo depois que ele é implantado.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 na configuração requerem que o aplicativo seja reimplantado, geralmente resultando em tempo de inatividade inaceitável e outras sobrecargas administrativas.However, changes to the configuration require the application be redeployed, often resulting in unacceptable downtime and other administrative overhead.

Os arquivos de configuração locais também limitam a configuração a um único aplicativo, porém, às vezes, pode ser útil compartilhar as definições de configuração entre vários aplicativos.Local configuration files also limit the configuration to a single application, but sometimes it would be useful to share configuration settings across multiple applications. Alguns exemplos são as cadeias de conexão de banco de dados, informações de tema de interface do usuário ou as URLs de filas e o armazenamento usado por um conjunto de aplicativos relacionados.Examples include database connection strings, UI theme information, or the URLs of queues and storage used by a related set of applications.

É difícil para gerenciar as alterações de configurações locais em várias instâncias em execução do aplicativo, especialmente em um cenário hospedado na nuvem.It's challenging to manage changes to local configurations across multiple running instances of the application, especially in a cloud-hosted scenario. Isso pode resultar em instâncias que usando definições de configuração diferentes, enquanto a atualização está sendo implantada.It can result in instances using different configuration settings while the update is being deployed.

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

SoluçãoSolution

Armazene as informações de configuração no armazenamento externo e forneça uma interface que pode ser usada 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 repositório externo depende do ambiente de hospedagem e do runtime do aplicativo.The type of external store depends on the hosting and runtime environment of the application. Em um cenário hospedado na nuvem, ele normalmente é um serviço de armazenamento baseado em nuvem, mas pode ser um banco de dados hospedado 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 repositório de backup escolhido para as informações de configuração deve ter uma interface que forneça acesso consistente e de fácil utilização.The backing store you choose for configuration information should have an interface that provides consistent and easy-to-use access. Ele deve expor as informações em um formato com tipo e estrutura corretos.It should expose the information in a correctly typed and structured format. A implementação também pode precisar autorizar o acesso dos usuários para proteger os dados de configuração e ser flexível o suficiente para permitir o armazenamento de várias versões de configuração (como desenvolvimento, preparo ou produção, incluindo várias versões de cada uma delas).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 internos leem os dados quando o aplicativo é iniciado e os armazenar em cache na memória para fornecer acesso rápido e minimizar o impacto sobre o desempenho do aplicativo.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 repositório de backup usado e da latência dele, pode ser útil implementar um mecanismo de armazenamento em cache no repositório 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 saber mais, consulte Diretrizes de caching.For more information, see the Caching Guidance. A figura mostra uma visão geral do Padrão de repositório de configuração externo com um cache local opcional.The figure illustrates an overview of the External Configuration Store pattern with optional local cache.

Uma visão geral do Padrão de repositório de configuração externo com um cache local opcional

Problemas e consideraçõesIssues and considerations

Considere os seguintes pontos ao decidir como implementar esse padrão:Consider the following points when deciding how to implement this pattern:

Escolha um repositório de backup que ofereça desempenho aceitável, alta disponibilidade, robustez e que possa passar por backup como parte do processo de administração e manutenção do aplicativo.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. Em um aplicativo hospedado na nuvem, geralmente é uma boa opção usar um mecanismo de armazenamento para atender a esses requisitos.In a cloud-hosted application, using a cloud storage mechanism is usually a good choice to meet these requirements.

Projete o esquema do repositório de backup para permitir flexibilidade quanto aos tipos de informações que ele pode conter.Design the schema of the backing store to allow flexibility in the types of information it can hold. Verifique se ele fornece todos os requisitos de configuração, como dados com tipo, coleções de configurações, várias versões de configurações e outros recursos exigidos pelos aplicativos que os utilizam.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. Deve ser fácil estender o esquema para dar suporte a configurações adicionais à medida que os requisitos mudam.The schema should be easy to extend to support additional settings as requirements change.

Considere as capacidades físicas do repositório de backup, como ela se relaciona com a forma que as informações de configuração são armazenadas e os efeitos sobre o 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, armazenar um documento XML que contém informações de configuração exigirá que a interface de configuração ou o aplicativo analise o documento para ler as configuraçõ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. Será mais complicado atualizar uma configuração, porém armazená-las em cache pode 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 como a interface de configuração permitirá o controle do escopo e a herança de definições de configuração.Consider how the configuration interface will permit control of the scope and inheritance of configuration settings. Por exemplo, isso pode ser um requisito para definições de configuração de escopo no nível da organização, do aplicativo ou do computador.For example, it might be a requirement to scope configuration settings at the organization, application, and the machine level. Pode ser necessário dar suporte à delegação do controle de acesso a diferentes escopos e impedir ou permitir que aplicativos individuais substituam as configurações.It might need to support delegation of control over access to different scopes, and to prevent or allow individual applications to override settings.

Verifique se a interface de configuração pode expor os dados de configuração nos formatos exigidos como valores com tipo, coleções, pares de chave/valor ou recipientes 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 como a interface do repositório de configuração se comportará quando as configurações contiverem erros ou se não existirem no repositório de backup.Consider how the configuration store interface will behave when settings contain errors, or don't exist in the backing store. Pode ser apropriado retornar as configurações padrão e os log de erros.It might be appropriate to return default settings and log errors. Considere também aspectos como a diferenciação de maiúsculas e minúsculas nas chaves ou nomes das definições de configuração, o armazenamento e manipulação de dados binários e as maneiras como valores nulos ou vazios são manipulados.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 somente aos usuários e aplicativos apropriados.Consider how to protect the configuration data to allow access to only the appropriate users and applications. Esse provavelmente é um recurso da interface do repositório de configuração, mas também é necessário garantir que os dados no repositório de backup não possam ser acessados diretamente sem a devida permissão.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. Verifique se há separação estrita entre as permissões necessárias para ler e gravar os dados de configuração.Ensure strict separation between the permissions required to read and to write configuration data. Considere também se você precisa criptografar algumas ou todas as definições de configuração e como isso será implementado na interface do repositório 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.

Configurações armazenadas centralmente, que alteram o comportamento do aplicativo durante o runtime, são cruciais e devem ser implantados, atualizados e gerenciados usando os mesmos mecanismos da implantação de código do aplicativo.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 a mais de um aplicativo precisam ser realizadas usando um teste completo e a abordagem de implantação de teste para garantir que a alteração seja apropriada para todos os aplicativos que usam essa 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 configuração para atualizar um aplicativo, ele poderia afetar negativamente outros aplicativos que usam a mesma configuração.If an administrator edits a setting to update one application, it could adversely impact other applications that use the same setting.

Se um aplicativo armazenar as informações de configuração em cache, ele precisará ser alertado se as configurações forem alteradas.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 de dados de configuração armazenados em cache para que essas informações sejam atualizadas automaticamente e periodicamente, e que as alterações sejam selecionadas (e as devidas ações sejam tomadas).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 usar esse padrãoWhen to use this pattern

Esse padrão é útil para:This pattern is useful for:

  • As definições de configuração compartilhadas entre vários aplicativos e instâncias do aplicativo ou nas quais uma configuração padrão precisa ser imposta em vários aplicativos e instâncias do aplicativo.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 dá suporte a todas as definições de configuração necessárias, como armazenamento de 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.

  • Como um repositório complementar para algumas das configurações de aplicativos, talvez permitindo que os aplicativos substituam algumas ou todas as configuraçõ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 uma maneira de simplificar a administração de vários aplicativos e, opcionalmente, para monitorar o uso de definições de configuração por meio de registro em log de todos os tipos de acesso para o repositório 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

Em um aplicativo hospedado do Microsoft Azure, uma opção típica para armazenar informações de configuração externamente é usar o Armazenamento do Azure.In a Microsoft Azure hosted application, a typical choice for storing configuration information externally is to use Azure Storage. Ele é flexível, oferece alto desempenho e é replicado três vezes com failover automático para proporcionar alta 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 repositório de chave/valor com a capacidade de usar 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 repositório hierárquico e baseado em contêiner que pode conter qualquer tipo de dados em blobs nomeados individualmente.Azure Blob storage provides a hierarchical, container-based store that can hold any type of data in individually named blobs.

O exemplo a seguir mostra como um repositório 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 abstrai o Armazenamento de Blobs para manter informações de configuração e implementa a interface ISettingsStore mostrada no código a seguir.The BlobSettingsStore class abstracts Blob storage for holding configuration information, and implements the ISettingsStore interface shown in the following code.

Esse 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<ETag> GetVersionAsync();

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

Essa interface define os métodos para recuperar e atualizar as definições de configuração mantidas no repositório de configuração e inclui um número de versão que pode ser usado para detectar 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 usa a propriedade ETag do blob para implementar o controle de versão.The BlobSettingsStore class uses the ETag property of the blob to implement versioning. A propriedade ETag é atualizada automaticamente sempre que há uma gravação no blob.The ETag property is updated automatically each time the blob is written.

Por padrão, essa solução simples expõe todas as definições de configuração como valores de cadeia de caracteres em vez de valores com tipo.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. Um aplicativo pode usar essa classe para armazenar e recuperar informações de configuração.An application can use this class to store and retrieve configuration information. Essa classe usa a biblioteca Microsoft Reactive Extensions para expor as alterações feitas na configuração por meio 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 configuração for modificada chamando o método SetAppSetting, o evento Changed é acionado e todos os assinantes desse 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.

Observe que todas as configurações também são armazenadas em cache em um 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 usado para recuperar uma definição de configuração lê os dados do cache.The GetSetting method used to retrieve a configuration setting reads the data from the cache. Se a configuração não for encontrada no cache, ela será recuperada 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 detectar se as informações de configuração no armazenamento de blobs foi alterado.The GetSettings method invokes the CheckForConfigurationChanges method to detect whether the configuration information in blob storage has changed. Ele faz isso examinando o número de versão e comparando-o com o número de versão atual do objeto ExternalConfigurationManager.It does this by examining the version number and comparing it with the current version number held by the ExternalConfigurationManager object. Se uma ou mais alterações ocorrerem, o evento Changed é acionado e as definições de configuração armazenados em cache no objeto Dictionary sã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. Este é um aplicativo do Padrão cache-aside.This is an application of the Cache-Aside pattern.

O exemplo de código a seguir 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 ETag currentVersion;
  ...

  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;
  }

  ...
  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. Essa propriedade dá suporte a configurações diferentes para um aplicativo em execução em ambientes diferentes, como preparo 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 quaisquer alterações.An ExternalConfigurationManager object can also query the BlobSettingsStore object periodically for any changes. No código a seguir, o método StartMonitor chama CheckForConfigurationChanges em um intervalo para detectar possíveis alterações e aciona 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 mostrada 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 código a seguir é obtido da classe WorkerRole no projeto ExternalConfigurationStore.Cloud.The following code is taken from the WorkerRole class in the ExternalConfigurationStore.Cloud project. Ele mostra como o aplicativo usa a classe ExternalConfiguration para ler uma configuraçã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 a seguir, também da classe WorkerRole, mostra como o aplicativo assina 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 esse padrão está disponível em GitHub.A sample that demonstrates this pattern is available on GitHub.