Entenda as configurações do Spring Boot!

Entenda as configurações do Spring Boot!

Spring Boot é uma ferramenta maravilhosa, mas se você não entender como suas configurações funcionam, aí você tem um problema. A esmagadora maioria das dificuldades que vejo as pessoas enfrentarem com o framework é justamente neste ponto, sendo assim resolvi escrever este post pra dar minha contribuição.

Neste post vou tratar de três assuntos que são normalmente a fonte da maior parte das confusões:

  • Vamos entender os principais conceitos que envolvem as configurações.
  • Em que ordem as configurações são carregadas e como você pode atuar aqui no momento em que for implantar ou executar sua aplicação baseada em Spring Boot.
  • Aquelas que considero boas práticas na gestão de configurações com Spring Boot.

Vai ser um post longo, pois a ideia é que sirva de referência no futuro. :)

Conceitos essenciais

Sabe aonde mora a maior parte das dúvidas relativas a este tema? Ausência de conhecimento conceitual. Vamos dar nomes às coisas aqui pra tornar nossa vida mais fácil. Iremos percorrer três andares deste edifício conceitual para que você possa ver desde os pontos mais elementares até os mais abstratos.

Primeiro andar: Java

Em Java você tem diversas fontes para suas configurações: arquivos de prorpriedades (properties), variáveis de ambiente, variáveis de sistema… Responda rapidamente, olhando o Javadoc da classe java.lang.System, você consegue explicar de forma imediata a diferença entre os métodos getenv() e getProperty()?

O nome dos métodos já inicia uma pequena confusão: por que “getenv” e não “getEnv”? O primeiro conceito então a tratar será a diferença entre “environment variable” (variável de ambiente) e “system property” (propriedade do sistema).

Variáveis de ambiente

Variável de ambiente é aquela que você define no sistema operacional em que seu código é executado. No caso do Linux (e macOS) o fazemos com os comandos set e export. Já no caso do Windows é com o comando set (mais comum) ou a partir do utilitário de configuração gráfica de ambiente.

Vamos então a um exemplo bem simples: imagine que estou no Linux e usando o comando export eu defina a variável de ambiente nome com o valor Kico, tal como no script a seguir:

export nome=Kico

E na sequência eu tenha o seguinte código Java:

System.out.println(System.getenv("nome");

A saída será…

Kico

Resumindo: temos aqui variáveis do sistema operacional que são completamente externas ao nosso código. Quer ser aprofundar no tema envolvendo Java? Este é o link.

Propriedades do sistema

Aqui mora a confusão. Propriedades do sistema (System Properties) são configurações que toda aplicação Java mantém em memória que dizem respeito ao seu ambiente de execução. Algumas destas propriedades são padrão como, por exemplo, “user.home”, que diz qual o diretório home do usuário, “os.name”, que diz qual o sistema operacional no qual a aplicação está sendo executada, etc.

Estas propriedades são portanto implícitas (como as que mencionei acima) ou explícitas. As explícitas são aquelas que passamos para o comando java usando argumentos que começam com “-D”. Imagine que eu tenha um programa Java tal como o apresentado a seguir:

public class Programa {
     public static void main(String args[]) {
            System.out.println(System.getProperty("nome"));
     }
}

Compilado meu programa, e executando-o com o comando abaixo, a saída será “Kico”

java -Dnome=Kico Programa

Então, simplificando, as propriedades do sistema são as implícitas da plataforma de execução e aquelas que passamos por linha de comando ao nosso programa Java. Quer se aprofundar no assunto e conhecer as propriedades padrão? Este é o link.

Segundo andar: Spring

Propriedades (Properties)

Do ponto de vista de quem usa o Spring não há variáveis de ambiente ou propriedades do sistema, há apenas propriedades (Properties). Uma propriedade é uma informação de configuração no formato chave/valor. Toda propriedade portanto é identificada por uma chave e possui um valor associado a esta.

Pense no conceito de property (usarei o termo em inglês daqui pra frente pois é o que você encontrará na esmagadora maioria das documentações e literatura relacionada) como a unidade mais fundamental de configuração no Spring. O objetivo das abstrações sobre as quais falarei a respeito agora tem como objetivo obter estas informações e organizá-las para você.

Environment (Ambiente)

Esta abstração consiste em uma interface do container Spring que será usada para modelar outro aspecto fundamental do framework: os perfis (profiles). Antes de falarmos sobre esta outra abstração é importante refletir um pouco sobre o que vêm a ser um Environment.

O Environment representa na prática o ambiente de execução da sua aplicação. É ele que diz quais variáveis de ambiente estamos usando, quais beans deverão ser instanciados e também quais os perfis que estão ativos ou que serão ativados pela nossa aplicação. Antes de falar sobre os perfis é importante mencionar um elemento bastante usado por este componente: a property source.

Property Source

Sua função consiste em representar uma fonte de dados da qual iremos obter nossas listas de propriedades. Esta fonte de dados pode ser literalmente qualquer coisa: arquivos, web services, bancos de dados, sistemas gestores de configuração como o Spring Cloud Config Server, etc.

Em aplicações Spring Boot quando desejamos externalizar o acesso aos arquivos de configuração usamos a anotação @PropertySource tal como no exemplo a seguir:

@PropertySource("file:///home/kicolobo/configsfelizes.properties")
public class AplicacaoSpringBoot {
  ...
}

E, claro, caso nenhuma das opções providas pelo framework não lhe atendam, basta que você implemente o seu próprio Property Source e registrando-o no Spring, o que foge do assunto deste post.

Profiles (Perfis) – muita atenção aqui!

Uma aplicação Spring Boot ao ser executada pode ter n perfis, mas o que é um perfil? O profile identifica um conjunto de configurações que definem como a sua aplicação deve se portar em um dado ambiente de execução. É muito importante aqui saber a diferença entre profiles e ambientes de execução, pois muitas vezes estes são usados de modo intercambiável:

  • Ambiente de execução – onde sua aplicação está sendo executada. Exemplo: ambiente de desenvolvimento, execução de testes, produção, homologação, etc.
  • Profile (Perfil) – um conjunto lógico de configurações que define como sua aplicação irá ser executada.

Então você pode, por exemplo, ter perfis que identifiquem o tipo de persistência que seu projeto está usando. Exemplo: um perfil chamado Relacional que indica que estamos usando datasources contra uma base de dados relacional e outro chamado NoSQL que indica que estamos usando bases de dados não relacionais.

E você pode ter este mesmo perfil aplicado a diferentes ambientes de execução: imagine o perfil NoSQL aplicado aos ambientes de Produção e Desenvolvimento, enquanto o perfil Relacional apenas no ambiente de homologação.

Voltando ao exemplo dos profiles NoSQL e Relacional, é fundamental saber que o Profile não indica apenas o conjunto de propriedades (properties) que irão ser carregadas, mas também quais beans serão instanciados. Voltando ao exemplo, podemos ter configurações de DAO implementadas tal como no exemplo a seguir:

@Configuration
public class ConfiguracaoDAO {
    @Bean @Profile("Relacional")
     public DAOUsuario   getDAORelacional() {
          return new DAOUsuarioMySQL();
      }

     @Bean @Profile("NoSQL")
      public DAOUsuario getDAONaoRelacional() {
            return new DAOUsuarioMongoDB()
       }
}

Programando contra interfaces a classe cliente nem sonha que DAOUsuario é usado contra o MongoDB ou MySQL, mantemos assim a boa prática de programar contra interfaces e não implementações.

Mas aí você me pergunta: como eu defino o profile adotado?

Selecionando os profiles

Por system property

É fácil, e aqui entra aqueles pontos que você deve decorar. Primeiro a forma mais simples, que é a partir da linha de comando: você usa a system property spring.profiles.active. Vamos a um exemplo:

java -Dspring.profiles.active=prod AplicacaoLinda.jar

Definimos que nossa aplicação irá executar usando o profile prod. E se eu quiser usar mais de um profile ao mesmo tempo? E se eu quiser usar os profiles prod e relacional? Separe com vírgulas:

java -Dspring.profiles.active=prod,relacional AplicacaoSuperPoderosa.jar

Mas há outras maneiras: você pode definir o profile por arquivos de configuração, Maven, em tempo de execução e outros. Este link aqui pode te ensinar estas outras maneiras.

Se eu não selecionar um profile, que profile é carregado? O profile default. Importante que você tenha esta informação em mente. No Spring Boot não existem profiles como dev, prod, test por padrão, mas como vemos em diversos posts, acabamos por achar que eles existem no framework.

Programaticamente

Antes da aplicação ser iniciada, no método main, você pode usar o método setAdditionalProfiles da classe SpringApplication. Importante você saber que após a aplicação ter sido iniciada não é mais possível trocar o perfil (até onde sei). Mais detalhes sobre os perfis do Spring Boot neste link.

Terceiro andar: Spring Boot e suas auto configurações

Calma, é o último andar MESMO agora (por enquanto, pois não vou falar aqui do Spring Cloud). O Spring Boot adiciona mais uma abstração importante referente a configurações: as auto configurações.

Muita gente ignora este aspecto do “Boot”: o framework é composto por diversos módulos que você pode adicionar ao seu projeto, identificados no Maven/Gradle com o nome “spring-boot-starter-*”, e cada um destes módulos contém, além das suas próprias funcionalidades, suas próprias configurações (auto configurações), ativadas no momento em que a aplicação é iniciada.

É fundamental que você saiba disto por que temos aqui aplicado o conceito de convenções sobre configuração. O autor do módulo define qual será o seu comportamento padrão e cabe a você customizar o que é necessário para o seu projeto. Mas aí entra a seguinte questão: como saber quais são estas configurações? Simples: você executa sua aplicação com o parâmetro –debug e todas serão impressas no console. Exemplo:

java --debug -jar AplicacaoLinda.jar

E se eu estiver usando Maven ou Gradle no meu ambiente de desenvolvimento? Dá pra passar o parâmetro também.

Sendo assim se você usa, por exemplo, o módulo JPA do Spring Boot: spring-boot-starter-jpa, automaticamente é configurado um datasource para você. E estas configurações variam de módulo para módulo, mas não fique triste. Mas como estas configurações são carregadas e onde estão?

No momento em que a aplicação é iniciada o Spring varre todos os arquivos JAR presentes no seu classpath que pertençam a estes módulos. E em cada um destes busca por um arquivo chamado spring.factories, presente no diretório META-INF do JAR. Neste arquivo estão listadas as classes de configuração (@Configuration) que são as responsáveis por configurar aquele módulo.

Dica: leia este tópico da documentação oficial do Spring Boot para entender como estes módulos são escritos.

Carregando configurações

Agora que estamos no “último andar” é muito importante que você tenha em mente o objetivo de que configurações devem ser externas ao seu código fonte. Desde o seu lançamento uma das principais features do Spring Boot foi o fato de ter “configurações externalizáveis”. A ideia é simples: há um único pacote da sua aplicação que será implantado em diversos ambientes, cada qual com o seu próprio conjunto de configurações.

Um dos objetivos do Spring Boot é que você tenha um conjunto de configurações padrão para executar a sua aplicação, mas que possam ser alteradas de acordo com o ambiente no qual o projeto é executado. Sendo assim você teria, por exemplo, configurações únicas para acesso a banco de dados no ambiente de produção, teste, homologação, desenvolvimento, etc.

Ordem de carregamento

Na documentação oficial do Spring Boot você encontra este link que descreve as 14 fontes usadas pelo Spring Boot na busca por configurações (properties). A ideia é fornecer ao desenvolvedor (ou equipe de operação) um mecanismo que permita sobrescrever de forma fácil praticamente qualquer configuração usada pela sua aplicação.

Você pode ver a lista das 14 fontes no link que mencionei acima, mas vou copiá-la para cá pois a usaremos para entender o funcionamento:

  1. Configurações padrão do framework.
  2. As configurações que você define com a anotação @PropertySource
  3. Arquivos de configuração (o famigerado application.data)
  4. RandomValuePropertySource – um recurso do Spring Boot que permite inserir valores randômicos em propriedades.
  5. Variáveis de ambiente do sistema operacional
  6. Propriedades do sistema (as System Properties)
  7. Via JNDI buscando em java:comp/env (tá aí um recurso pouco usado do Java e que é bem útil, viu?)
  8. Init params do ServletContext (em aplicações web)
  9. Parâmetros do ServletConfig.
  10. Propriedades definidas na variável de ambiente SPRING_APPLICATION_JSON (merece um post a parte este recurso, não incluí neste post por que quis me ater apenas aos recursos mais usados)
  11. Parâmetros de linha de comando
  12. Atributos properties nos testes.
  13. @TestPropertySource – você inclui esta anotação nos seus testes automatizados com Spring.
  14. Arquivo de configurações globais do Devtools (novamente, merece um post a parte este recurso, mas não o incluí neste post para que seja possível nos ater apenas aos usos mais comuns)

Muita confusão surge a partir desta ordem de carregamento, então vamos a um exemplo. Imagine que temos o seguinte arquivo “application.properties” (falaremos mais sobre este arquivo e suas variantes adiante, contenha sua ansiedade) no seu projeto:

nome=Kico
sobrenome=Lobo

Se sua aplicação for compilada e executada sem quaisquer informação adicional (java -jar Aplicacao.jar) os valores definidos neste arquivo serão mantidos (item 3). Sempre o Spring Boot verifica todos os elementos da lista ao executar a aplicação, sendo assim vamos continuar no exemplo para que a coisa fique mais simples, pois há uma pegadinha envolvendo algo sobre o qual falei no início deste post.

Se houver uma variável de ambiente chamada “nome” com o valor “Nanna”, teremos aqui o valor “Nanna” aplicado na configuração (item 5, posterior ao 3).

Se for passada a propriedade de sistema nome via parâmetro (“-Dnome=Juca”) para a aplicação, o valor será “Juca” (item 6, posterior ao 5 e 3). Sendo assim você pode sobrescrever variáveis de ambiente na execução do seu projeto também.

Até chegarmos ao item 11: “parâmetros de linha de comando”. Aqui o Spring Boot reconhece o valor de variáveis da seguinte forma: “–nome=Jucolino”. Sendo assim, se defini a variável nome no arquivo (item 3), também uma variável de ambiente com mesmo nome (item 5), mesmo assim ainda fui lá e coloquei uma propriedade de sistema (item 6), se eu incluir um “–nome=JucolinoBruto” por linha de comando, prevalece o item 11 (em teoria).

Via de regra: as fontes de configuração que tenham ordem maior são as que serão carregadas pelo seu projeto durante a inicialização.

Arquivos de configuração

Muito cuidado com o item 3 da lista acima: arquivos de configuração. Aqui reside boa parte dos problemas que vejo as pessoas enfrentarem em seu dia a dia. Isto por que estes arquivos se dividem em duas categorias:

  • application.properties definido para o perfil padrão
  • application-[perfil].properties para os arquivos que não são o perfil padrão. Exemplo: arquivo application-dev.properties para o perfil “dev”, application-prod.properties para o perfil “prod”.

O Spring Boot, tal como descrito em sua documentação, carrega estes arquivos na seguinte ordem:

  1. Arquivo application.properties embarcado na aplicação (classpath) para o perfil padrão.
  2. Arquivo application-[perfil].properties para perfis específicos (exemplo: application-dev.properties para o perfil dev) embarcados no projeto.
  3. Arquivo application.properties externo ao seu projeto.
  4. Arquivo application-[perfil].properties externo ao seu projeto.

Sendo assim, imagine que eu tenha o arquivo application.properties embarcado em meu projeto com o seguinte conteúdo:

server.port=8080

Esta propriedade define a porta do serviço que identificará minha aplicação. Se for ativado o perfil “dev” no projeto, e existir internamente um arquivo chamado “application-dev.properties”, e neste o valor da propriedade “server.port” for “9090”, o valor usado pela aplicação será o “9090”.

Mas e se eu ativar mais de um perfil na minha aplicação, digamos, “dev,qa”. Os arquivos nos quais a chave será buscada no classpath será:

  1. application.properties
  2. application-dev.properties
  3. application-qa.properties (observe: os arquivos de perfil são carregados na ordem em que são declarados pela propriedade spring.profiles.active)

Se no mesmo exemplo a propriedade “server.port” estiver definida também no arquivo “application-qa.properties” com valor “1234”, “1234” será o valor da propriedade no final.

O que nos leva à pergunta: mas e nos casos em que o arquivo estiver externo? Aonde está este arquivo? A seção 4.2.3 da documentação oficial nos responde com as convenções aplicadas pelo framework.

As configurações externas podem estar (e serão carregadas seguindo a mesma ordem):

  1. No diretório corrente
  2. Na pasta /config do diretório corrente
  3. Sub-diretórios da pasta /config

Não gostou de nenhuma das opções acima? Ok, defina então uma propriedade do spring chamada “spring.config.location”, cujo valor apontará para o diretório no qual os arquivos de configuração deverão ser buscados e apenas ali eles serão buscados no caso da busca externa.

Você deve ter em mente aqui o conceito de “herança”. Se for usar o arquivo “application.properties” embarcado em sua aplicação apenas aquilo que é estritamente essencial para a execução do seu código deve estar ali presente. Este arquivo essencialmente deve ser mínimo. E aí as configurações relativas ao ambiente (Environment) no qual a aplicação será executada você pode incluir no arquivo externo do ambiente padrão (application.properties) ou por perfil específico (application-[perfil].properties) em um dos diretórios que citei acima.

E como eu uso estas propriedades no meu código?

Fácil, fácil, você usa a anotação @Value, tal como no exemplo a seguir:

@Value("${nome}") // aplicando a propriedade nome
private String nome;

Esta é de longe a forma mais comum. Foge deste post entrar em detalhes de código, mas você pode aprender bastante a respeito neste link.

Boas práticas

Não gosto muito de falar em boas práticas por que cada caso é único e muitas vezes quem usa o termo “boas práticas” age de forma dogmática. Porém em diversas consultorias que presto observo a ocorrência de alguns erros que não só geram déficits técnicos como que também acabam por colocar em risco os stakeholders do projeto. Sendo assim segue uma pequena lista.

Cuidado com informações sensíveis

Entenda por informações sensíveis aquelas que não podem vazar sob hipótese alguma: informações de acesso a banco de dados, credenciais a serviços cloud, senhas, números de documento, API keys, etc.

Estas informações não devem estar jamais no código fonte da aplicação ou no repositório. No caso do Spring Boot, o ideal é que elas estejam presentes portanto em arquivos de configuração externos, variáveis de ambiente, parâmetros de inicialização, enfim: estas informações devem estar apenas no ambiente que precisa delas para que sua aplicação funcione.

É muito comum, por exemplo, encontrar em pacotes uma série de arquivos, tais como:

  • application-prod.properties
  • application-qa.properties

Imagine se este pacote vaza e nestes arquivos estão estas informações sensíveis. Evite ao máximo. Existem inclusive opções que permitem armazenar segredos (informações sensíveis) de forma segura pra você, como o Vault, por exemplo, que pode inclusive ser integrado ao Spring (especialmente Spring Cloud).

Arquivo de configuração mínimo

É um repeteco de algo que falei acima: após ter removido todas as informações sensíveis de sua configuração embarcada, minimize ao máximo o conteúdo do arquivo application.properties. O ideal é que este contenha apenas o essencial para que sua aplicação possa ser executada no ambiente de desenvolvimento.

Por que ambiente de desenvolvimento, Kico? Só pra dar produtividade para os desenvolvedores. As demais configurações você sobrescreve nas diversas fontes que mencionei acima (as outras 11). O ideal é que esteja externalizado também no ambiente de desenvolvimento as informações sensíveis para garantir a segurança dos dados.

Use e abuse das variáveis de ambiente

O ideal na minha opinião é termos apenas um arquivo embarcado application.properties que obtenha todos os seus dados a partir de variáveis de ambiente caso estas existam e use valores padrão caso não existam. Como faz isto?

Basta seguir a sintaxe do exemplo a seguir:

server.port=${SERVER_PORT:8080}

Neste exemplo, se existir uma variável de ambiente chamada SERVER_PORT, seu valor é usado, caso contrário, é usado o valor 8080.

Cuidado com passagem de configurações pela linha de comando

É muito comum em momentos de aperto iniciarmos o processo da nossa aplicação trocando os parâmetros de inicialização (itens 6 e 11 da ordem de inicialização acima mencionada) e não documentarmos isto. Consequentemente, o processo irá ser terminado no futuro e você não sabe mais aqueles valores.

Caso vá realizar algo assim, garanta que exista um script de inicialização do seu projeto e altere nele também estes valores ou, melhor ainda, inclua estas informações nas variáveis de ambiente.

Documenta estas configurações por misericórdia!

O que você lembra hoje vai esquecer amanhã. Especialmente no caso de existir uma equipe de operações, tenha documentado aonde encontram-se as configurações do sistema. São variáveis de ambiente? Ótimo! Quais?

Caso contrário será necessário abrir seu pacote pra descobrir estas variáveis e isto será muito, muito triste.

Concluindo

Bom, este post serviu pra mostrar os principais conceitos envolvendo configurações no Spring Boot. Muitas vezes vejo o pessoal as usando de modo intuitivo, baseado em tutoriais na Internet e sem entender por que está funcionando (ou não) aquele sistema. Então é importante saber estes detalhes.

No caso do Spring Boot, que é essencialmente a aplicação do conceito de convenção sobre configuração no Spring, isto se torna ainda mais importante pois há muitos detalhes que não são expostos de forma imediata à equipe e que muitas vezes geram prejuízos enormes de tempo, dinheiro e razão.

Espero que lhes seja útil. Havendo algum erro no texto, por favor, entre em contato comigo nos comentários para que eu aplique as devidas correções, ok? Usei como base essencialmente a última versão do Spring Boot (2.4.0) como referência.

Mantido por itexto Consultoria