CoreLib Configuration Design Reference
Keyworks: Configuration ConfigurationSection ConfigurationElement ConfigurionService
Conceitos
Configuração
Neste contexto entende-se por configuração os settings de uma aplicação. Esses settings podem ser armazenados no
ficheiro de configuração (app.config ou web.config) da aplicação ou, sob determinadas condições,
numa estrutura de storage própria.
Configuration Section
Tipicamente as configurações especializadas dos componentes PRIMAVERA estão disponíveis em secções de
configuração próprias. Essas secções são declaradas no elemento configSections da configuração.
App Setting
Um app setting é um tipo especial de configuração que é definido com um par nome/valor numa secção
especial de configuração (appSettings). Este tipo de configurações são usadas em cenários
mais simples.
Connection String
As connection strings são tipos especiais de configuração definidos na secção connectionStrings.
São usadas para definir conexões a bases de dados ou serviços.
Pressupostos
O namespace System.Configuration disponibiliza o tipo ConfigurationManager para manipular
ficheiros de configuração. No entanto, as operações disponíveis nesta API são muito genéricas e não
facilitam a implementação de comportamentos como os descritos neste documento, pelo que há interesse em
disponibilizar na CoreLib um serviço que implemente esses comportamentos de forma transversal e uniforme.
No mundo do desenvolvimento para a cloud há a necessidade de executar a mesma aplicação (com configuração semelhante) em vários ambientes (a máquina do developer, o ambiente de testes, o de staging ou o produtivo). Tipicamente esses ambientes diferentes requerem configurações ligeiramente diferentes (por exemplo, porque uma connection string é diferente, porque determinado componente só está disponível nos ambientes cloud, porque os níveis de serviço de cada ambiente são diferentes, porque é preciso que o ambiente do developer seja eficiente).
Estas “costumizações” dos ficheiros de configuração para cada ambiente foram sendo asseguradas pela transformação desses ficheiros (geração de um ficheiro de configuração por ambiente por transformação de um original). No entanto, esse método é complexo e difícil de manter. Mais recentemente, o Windows Azure disponibilizou a possibilidade de configurar no portal os App Settings e Connection Strings de um dado site, sobrepondo-se ao ficheiro de configuração publicado (substituindo ou adicionando valores nessa secção). Assim é possível publicar o mesmo ficheiro de configuração e depois torná-lo diferente para cada ambiente por configuração do próprio ambiente.
Para além disso há a necessidade de suportar a definição de configurações diferentes para clientes ou subscrições diferentes do mesmo produto no mesmo ambiente (por exemplo, quando se pretende diagnosticar problemas específicos de um determinado cliente).
Por todas estas razões é importante que os componentes possam lidar com essas alterações da configuração nos App Settings de forma transparente e é isso que motiva o conceito de “overridable configurations” e o suporte para armazenamento das configurações na cloud, como descrito adiante.
Regras
Dos pressupostos anteriores e das soluções descritas adiante resultam as seguintes regras:
- A classe
System.Configuration.Managernão deve ser utilizada diretamente em nenhum código PRIMAVERA. Deve usar-se a classePrimavera.Core.Configuration.ConfigurationServiceno seu lugar. - Todos os componentes da CoreLib (e das plataformas PRIMAVERA) devem suportar “overridable configurations”. Também devem armazenar qualquer connection string apenas na respetiva secção.
Desenho
Configuration Service
O serviço Primavera.Core.Configuration.ConfigurationService (em Primavera.CoreLib.dll)
permite obter a configuração da aplicação e é um wrapper para o componente
System.Configuration.ConfigurationManager que lhe acrescenta comportamento standard a
todas aplicações PRIMAVERA.
Entre as operações disponíveis estão as seguintes:
- Ler uma configuration section (overridable ou não).
- Obter o elemento de uma configuration section overridade.
- Ler um app setting.
- Ler uma connection string.
- Ler uma configuration section overridade.
Configuration Sections
Uma secção de configuração permite “agrupar” as propriedades de um determinado componente na mesma "zona" do ficheiro de configuração, através da implementação de uma classe (o handler). Eis alguns exemplos presentes na CoreLib:
TracingConfigurationSectionCachingServiceConfigurationSectionFileStorageServiceConfigurationSection
As secções de configuração dos componentes PRIMAVERA, pelas razões apresentadas antes,
devem sempre herdar da classe base OverridadeConfigurationSection em vez de
ConfigurationSection (para implementarem o comportamento descrito no ponto seguinte).
Na definição dessas classes deve ainda atender-se às seguintes regras:
- O nome dessa classe deve mapear diretamente com o nome do componente que a utilize.
Se o componente se chamar, por exemplo,
XptoService, a classe deve chamar-seXptoServiceConfigurationSection(exemplo:ObjectFactoryeObjectFactoryConfigurationSection). - Para poder ser referenciada no ficheiro de configuração será necessário colocar um
nome correto na definição da configuration section. Esse nome tem que ser sempre o
mesmo e tem que ser aquele que o próprio componente que lerá a configuration section
em runtime estabeleça. Por isso, esse nome deve ser definido como uma constante na
classe da configuration section e o nome utilizado deve mapear diretamente com o nome
da classe da configuration section (ex:
ObjectFactoryConfigurationSectioneobjectFactoryConfiguration).
Exemplo:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="tracingConfiguration" type="Primavera.Core.Diagnostics.TracingConfigurationSection, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</configSections>
<tracingConfiguration>
<elements>
<add name="default" level="Verbose">
<listeners>
<add type="Primavera.CoreLib.Tests.Mocks.MockTracingListener, Primavera.CoreLib.Tests, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</listeners>
</add>
</elements>
</tracingConfiguration>
</configuration>
Overridable Configuration
Para suportar a costumização das configurações por ambiente no Windows Azure, todas as configurações dos componentes PRIMAVERA – as respetivas configuration sections – devem ser “overridable” (no sentido, de que se poderá definir nos AppSettings qual das configurações deve ser efetivamente usada).
Uma secção de configuração é overridable se herdar da classe base
Primavera.Core.Configuration.OverridableConfigurationSection. Nesse caso, essa secção
será definida como uma lista de “overridable configuration elements” e, quando o
componente solicitar a configuração a ser efetivamente usada, será seguido o seguinte
algoritmo pelo ConfigurationService:
- Se a secção não existir será devolvido null.
- Se não existir nenhuma declaração nos app settings será devolvido o primeiro elemento.
- Se existir uma declaração nos app settings, será devolvido o elemento da lista que tiver esse nome. Essa declaração deve ter o mesmo nome da configuration section.
Para obter a configuração o componente deve invocar-se o método GetOverridableElement como
no seguinte exemplo:
configurationElement = ConfigurationService.GetOverridableElement<CachingServiceConfigurationSection, CachingServiceConfigurationElement>(CachingServiceConfigurationSection.SectionName);
Overrides
Também é possível fazer override a valores das propriedades do element "escolhido" pelo algoritmo anterior. Para o conseguir deve definir-se o valor da propriedade também nos app settings, como no exemplo seguinte:
<appSettings>
(...)
<add key="jasminConfigurationSection.SiteName" value="myjasmin2.com" />
</appSettings>
Este override cause que a propriedade "SiteName" no elemento selecionado tenha o valor indicado no app setting em vez do valor que esteja definido na configuration section.
São suportados overrides mais complexos que manipulem propriedades em coleções da configuração.
<serversConfigurationSection>
<elements>
<add name="config1" active="false">
<protocol kind="TCP" />
<hosts>
<add host="host1">
<ports>
<add port="100" />
<add port="101" />
<add port="102" />
</ports>
</add>
<add host="host2" />
<add host="host3" />
</hosts>
</add>
<add name="config2" active="false">
<protocol kind="UDP" />
<hosts>
<add host="xhost1">
<ports>
<add port="100" />
<add port="101" />
<add port="102" />
</ports>
</add>
<add host="xhost2" />
<add host="xhost3" />
<add host="xhost4" />
</hosts>
</add>
</elements>
</serversConfigurationSection>
Exemplos de overrides para a secção de configuração anterior:
<add key="serversConfigurationSection" value="config2" />
<add key="serversConfigurationSection.active" value="true" />
<add key="serversConfigurationSection.protocol.kind" value="SMTP" />
<add key="serversConfigurationSection.hosts{1}.host" value="myserver" />
<add key="serversConfigurationSection.hosts{0}.ports{2}.port" value="500" />
OverridableConfigurationSection
A classe correspondente a secção deste tipo deve ser declarada como no seguinte exemplo:
public class CachingServiceConfigurationSection : OverridableConfigurationSection<CachingServiceConfigurationElement>
O parâmetro genérico TConfigurationElement deve herdar da classe base OverridableConfigurationElement.
OverridableConfigurationElement
Os elementos da secção de configuração devem herdar de OverridableConfigurationElement. São estes
os elementos que deverão possuir a configuração do componente (não a secção, que é genérica).
Exemplo:
public class CachingServiceConfigurationElement : OverridableConfigurationElement
Exemplos
No exemplo seguinte, o elemento de configuração selecionado para a secção
fileStorageConfiguration seria o elemento “default”:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="tracingConfiguration" type="Primavera.Core.Diagnostics.TracingConfigurationSection, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="cachingServiceConfiguration" type="Primavera.Core.Caching.CachingServiceConfigurationSection, Primavera.CoreLib.Caching, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="redisCachingServiceConfiguration" type="Primavera.Core.Caching.Redis.RedisCachingServiceConfigurationSection, Primavera.CoreLib.Caching.Redis, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="fileStorageServiceConfiguration" type="Primavera.Core.Storage.FileStorageServiceConfigurationSection, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</configSections>
<tracingConfiguration>
<elements>
<add name="default" level="Error">
<listeners>
<add type="Primavera.Core.Diagnostics.TracingListeners.RollingFileTracingListener, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</listeners>
</add>
<add name="verbose" level="Verbose">
<listeners>
<add type="Primavera.Core.Diagnostics.TracingListeners.RollingFileTracingListener, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</listeners>
</add>
</elements>
</tracingConfiguration>
<cachingServiceConfiguration>
<elements>
<add
name="default"
defaultAbsoluteExpiration="00:01:00"
defaultSlidingExpiration="00:00:00"
maximumTimeout="7.00:00:00"
cachingManagerFactoryType="Primavera.Core.Caching.InMemory.ManagerFactory, Primavera.CoreLib.Caching, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256"
/>
<add
name="redis"
defaultAbsoluteExpiration="00:01:00"
defaultSlidingExpiration="00:00:00"
maximumTimeout="7.00:00:00"
cachingManagerFactoryType="Primavera.Core.Caching.Redis.ManagerFactory, Primavera.CoreLib.Caching.Redis, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256"
/>
</elements>
</cachingServiceConfiguration>
<redisCachingServiceConfiguration>
<elements>
<add
name="default"
allowAdmin="true"
ssl="true"
connectTimeout="5000"
defaultDatabase="0"
password="ZsXM8I1f+1z2cOdjhoaT2U0/vtVYhwVJ9Jzhw3DmgeA=">
<hosts>
<add host="elevationfw3.redis.cache.windows.net" cachePort="6380"/>
</hosts>
</add>
</elements>
</redisCachingServiceConfiguration>
<fileStorageServiceConfiguration>
<elements>
<add name="default" providerType="Primavera.Core.Storage.Providers.LocalFileStorageProvider, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<add name="azureBlobStorage" providerType="Primavera.Core.Storage.Providers.AzureBlobFileStorageProvider, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</elements>
</fileStorageServiceConfiguration>
</configuration>
No exemplo seguinte o override nos app settings da mesma configuration section resultaria
na seleção do elemento azureBlobStorage.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="tracingConfiguration" type="Primavera.Core.Diagnostics.TracingConfigurationSection, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="cachingServiceConfiguration" type="Primavera.Core.Caching.CachingServiceConfigurationSection, Primavera.CoreLib.Caching, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="redisCachingServiceConfiguration" type="Primavera.Core.Caching.Redis.RedisCachingServiceConfigurationSection, Primavera.CoreLib.Caching.Redis, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<section name="fileStorageServiceConfiguration" type="Primavera.Core.Storage.FileStorageServiceConfigurationSection, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</configSections>
<appSettings>
<add key="fileStorageServiceConfiguration" value="azureBlobStorage" />
</appSettings>
<tracingConfiguration>
<elements>
<add name="default" level="Error">
<listeners>
<add type="Primavera.Core.Diagnostics.TracingListeners.RollingFileTracingListener, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</listeners>
</add>
<add name="verbose" level="Verbose">
<listeners>
<add type="Primavera.Core.Diagnostics.TracingListeners.RollingFileTracingListener, Primavera.CoreLib, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</listeners>
</add>
</elements>
</tracingConfiguration>
<cachingServiceConfiguration>
<elements>
<add
name="default"
defaultAbsoluteExpiration="00:01:00"
defaultSlidingExpiration="00:00:00"
maximumTimeout="7.00:00:00"
cachingManagerFactoryType="Primavera.Core.Caching.InMemory.ManagerFactory, Primavera.CoreLib.Caching, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256"
/>
<add
name="redis"
defaultAbsoluteExpiration="00:01:00"
defaultSlidingExpiration="00:00:00"
maximumTimeout="7.00:00:00"
cachingManagerFactoryType="Primavera.Core.Caching.Redis.ManagerFactory, Primavera.CoreLib.Caching.Redis, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256"
/>
</elements>
</cachingServiceConfiguration>
<redisCachingServiceConfiguration>
<elements>
<add
name="default"
allowAdmin="true"
ssl="true"
connectTimeout="5000"
defaultDatabase="0"
password="ZsXM8I1f+1z2cOdjhoaT2U0/vtVYhwVJ9Jzhw3DmgeA=">
<hosts>
<add host="elevationfw3.redis.cache.windows.net" cachePort="6380"/>
</hosts>
</add>
</elements>
</redisCachingServiceConfiguration>
<fileStorageServiceConfiguration>
<elements>
<add name="default" providerType="Primavera.Core.Storage.Providers.LocalFileStorageProvider, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
<add name="azureBlobStorage" providerType="Primavera.Core.Storage.Providers.AzureBlobFileStorageProvider, Primavera.CoreLib.Storage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</elements>
</fileStorageServiceConfiguration>
</configuration>
Runtime Configuration
A partir da versão 4.00.00 da CoreLib, o configuration service e as overridable sections suportam uma lógica de configuração em runtime que permite armazenar a configuração em storage na cloud, por subscrição, e assim, costumizar a configuração que será utilizada por cada componente para essa subscrição, indepentemente do que estiver definido no ficheiro de configuração da aplicação.
O configuration service utilizará um conjunto de até 3 "configuration handlers", de forma sequencial até que um deles devolva a configuração que deve ser usada para a aplicação e a subscrição em causa:

Cache
O primeiro handler a ser questionado pelo elemento de configuração solicitado será o CacheConfigurationReader.
Este handler mantém em memória uma cache dos elementos de configuração previamente devolvidos pelo serviço.
Por defeito não tem nenhuma configuração e só quando um dos outros handlers resolve determinada configuração é que é alimentado (a cache é carregada) para que futuros pedidos do mesmo elemento sejam respondidos mais rapidamente.
A cache tem um tempo de expiração por defeito em absoluto de 5 minutos. Isto significa que depois de um determinado elemento de configuração ter sido devolvido, qualquer alteração da configuração só terá efeito depois de passados 5 minutos (e a cache ter expirado).
Note-se que este handler pode ser ativido ou desativado também por configuração (ver RuntimeConfigurationSection), assim como o algoritmo
de expiração da cache pode ser configurado.
Table Storage
O segundo handler questionado será o TableStorageConfigurationReader que verificará a existência do elemento de configuração numa
tabela armazenada numa conta Azure Table Storage.
Se existir um registo nessa storage para a subscrição ativa na aplicação, será esse o elemento de configuração devolvido pelo serviço (sendo o mesmo elemento colocado em cache para depois ser retornado pelo handler anterior).
A configuração é armazenada na storage numa tabela com o nome RuntimeConfigurationSections segundo a seguinte lógica:
- A secção de configuração é armazenada na propriedade
Section, serializada em JSON. - Pode ser definida uma secção de configuração que se aplica a uma determinada subscrição, definindo um registo com o nome da secção de configuração
na coluna
PartitionKeye o identificador da subscrição na colunaRowKey. - Pode ser definida uma secção de configuração que se aplica a todas as subscrições, definido um registo com o nome da da secção de configuração
na coluna
PartitionKeye o valor * na colunaRowKey.
Exemplo:

Os app settings são armazenados na tabela RuntimeAppSettings e as connection strings na tabela RuntimeConnectionStrings.
Note-se que também este handler pode ser ativado ou desativado por configuração (ver RuntimeConfigurationSection).
Ficheiro de Configuração
O último handler (fallback) é o ConfigurationManagerConfigurationReader que consultará o ficheiro de configuração da aplicação de acordo
com as regras de override descritas anteriormente.
Note-se que este handler, naturalmente, não pode nunca ser desativado.
Configuração do Comportamento do Configuration Service
O comportamento do configuration service pode ser modificado pela secção de configuração Primavera.Core.Configuration.RuntimeConfigurationSection.
Estão disponíveis os seguintes settings:
cacheEnabled: indica se o handler de cache está ativo ou não (o valor por defeito é true).cacheTimeout: define o tempo absoluto de expiração dos elementos armazenados em cache (o valor por defeito é 5 minutos).storageEnabled: indica se o handle de table storage está ativo ou não (o valor por defeito é true).
A conta de storage utilizada pelo handler TableStorageConfigurationReader não é configurável nesta secção de configuração. A configuração
utilizada é a mesma do ITableStorageService (ver CoreLib Storage).
<configSections>
<section name="runtimeConfiguration" type="Primavera.Core.Configuration.RuntimeConfigurationSection, Primavera.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=33086db60a481256" />
</configSections>
<runtimeConfiguration
cacheEnabled="true"
storageEnabled="true"
/>
Esta secção de configuração não é overridable, por motivos óbvios.
FAQ
- P: Posso usar o namespace
System.Configuration?- R: Sim, porque ele é necessário, por exemplo, para definir ou referenciar configuration sections.
Já a classe
ConfigurationManagerdesse namespace não deve ser utilizada diretamente.
- R: Sim, porque ele é necessário, por exemplo, para definir ou referenciar configuration sections.
Já a classe
- P: Qual é o nome indicado para uma configuration section?
- R: A classe deve indicar claramente a que componente está associada. Por exemplo,
para o componente
FileStorageServiceexiste a classFileStorageServiceConfigurationSection. Já o nome porque ela deve ser referenciada no ficheiro de configuração, deve estar em camel case e mapear diretamente com o nome da classe (exemplo:fileStorageServiceConfiguration).
- R: A classe deve indicar claramente a que componente está associada. Por exemplo,
para o componente
- P: Posso usar o
ConfigurationServicepara ler configuration sections que não são overridable?- R: Sim.
- P: Se um componente usar uma determinada configuration section e ela não existir no ficheiro de
configuração o que acontece?
- R: Depende da implementação do componente. Há componentes em que a configuração é opcional e outros em que é obrigatória.
- P: Na lista de elements de uma configuration section estão 3 entradas. Qual será usada pelo
respetivo componente?
- R: Se não existir nenhum override nos app settings, será utilizado o primeiro elemento da lista. Se existir o override, será utilizado o elemento que tiver esse nome.
- P: Que valor devo colocar no atributo name do app setting para fazer o override de uma
configuration section?
- R: O nome da configuration section no ficheiro de configuração (exemplo:
fileStorageServiceConfiguration).
- R: O nome da configuration section no ficheiro de configuração (exemplo:
- P: Alterei o valor de um elemento de configuração na tabela da storage mas a aplicação continua a
comportar-se como antes. Porquê?
- R: Provavelmente o setting anterior ainda está em cache. É necessário que a cache expire para que o valor armazenado na storage seja lido novamente.
- P: É possível ter uma configuração igual para todas as subscrições de uma conta?
- R: Não. Só é possível definir configurações na storage para uma determinada subscrição ou então para todas.