O Spring provê uma forma bem maneira de lidar com os configurações em nossos projetos, através do arquivo application.properties ou application.yml.
Com eles temos o conceito de profiles, que resolve o problema de termos configurações diferentes para ambientes diferentes.
Como, por exemplo se, precisarmos nos conectar à um banco de dados diferente dependendo do ambiente que estivermos utilizando (produção, desenvolvimento, homologação).
Podemos atender essa necessidade configurando uma conexão especifica para cada profile:
Utilizando application.properties
Quando trabalhamos com arquivos .properties temos que ter um arquivo para cada profile com o seguinte padrão para o nome:
application-“nome do profile”.properties.
Como nosso caso temos três ambientes (produção, desenvolvimento e homologação). Teremos três arquivos:
application-production.properties
spring.datasource.username=application-user
spring.datasource.password=x123XASD&1
spring.datasource.url=jdbc:mysql://servidor-em-algum-lugar-do-mundo/bancoDeDadosOficial
application-development.properties
spring.datasource.username=root
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost/bancoDeDados
application-acceptence.properties
spring.datasource.username=another-user
spring.datasource.password=123accepteence
spring.datasource.url=jdbc:mysql://localhost/bancoDeDados
Utilizando application.yml
Quando estamos trabalhando com arquivos .yml podemos ter um unico arquivo e dentro dele separar os profiles utilizando a sintaxe do próprio formato YAML.
#isso indica um novo arquivo
---
spring:
profiles: production
datasource:
username: application-user
password: x123XASD&1
url: jdbc:mysql://servidor-em-algum-lugar-do-mundo/bancoDeDadosOficial
---
spring:
profiles: development
datasource:
username: root
password:
url: jdbc:mysql://localhost/bancoDeDados
---
spring:
profiles: acceptence
datasource:
username: another-user
password: 123accepteence
url: jdbc:mysql://localhost/bancoDeDados
Em ambos os casos podemos utilizar o parâmetro -Dspring.profiles.active=“nome do profile” para selecionar qual profile queremos utilizar.
Um ponto a ser levado em consideração é que todas as informações estão expostas para quem tiver acesso ao repositório do projeto.
Então para minimizar esse problema podemos externalizar essas configurações em variáveis de ambientes. Com isso poderíamos ter um único arquivo de configuração.
Exemplo:
application.properties
spring.datasource.username=${DATABASE_USER}
spring.datasource.password=${DATA_BASE_PASSWORD}
spring.datasource.url=${DATA_BASE_URL}
Ou
application.yml
#isso indica um novo arquivo
---
spring:
datasource:
username: ${DATABASE_USER}
password: ${DATA_BASE_PASSWORD}
url: ${DATA_BASE_URL}
Para deixar mais flexível ainda podemos definir um valor default caso não exista a variável de ambiente. Para isso usamos a sintaxe ${VARIAVEL_DE_AMBIENTE:VALOR_DEFAULT}.
Dessa forma podemos deixar o valor default para o ambiente de desenvolvimento, e definir manualmente os valores para os ambientes de produção e homologação.
Exemplo:
application.properties
spring.datasource.username=${DATABASE_USER:root}
spring.datasource.password=${DATA_BASE_PASSWORD:}
spring.datasource.url=${DATA_BASE_URL:jdbc:mysql://localhost/bancoDeDados}
Ou
application.yml
#isso indica um novo arquivo
---
spring:
datasource:
username: ${DATABASE_USER:root}
password: ${DATA_BASE_PASSWORD:}
url: ${DATA_BASE_URL:jdbc:mysql://localhost/bancoDeDados}
Mas essa implementação nos leva a outro ponto: Alterar a maquina física para definir a variável de ambiente.
Quando pensamos em uma única aplicação, não parece doer tanto ter que alterar as variáveis de ambiente para que a aplicação funcione nos ambientes de produção e homologação.
Mas e quando estamos em uma arquitetura de microserviços, quantas variáveis de ambiente teremos que lembrar de preencher?
Nesse cenário é bem comum termos dezenas/centenas de aplicações rodando simultaneamente. E a quantidade de configurações que irá demandar para que nossa aplicação funcione, pode se tornar um problema quando temos que gerenciar tudo isso junto.
Para facilitar a gestão dessas configurações, podemos centralizar essas configurações e fazer com que nossa aplicação busque essas configurações quando for necessário ou ao inicia-la.
Essa técnica é chamada de Remote Configuration.
O Spring já tem um projeto específico para essa finalidade chamado Spring Cloud Config.
O Spring Cloud Config é um projeto dentro do guarda-chuva de projetos Spring Cloud. A ideia desse guarda-chuva de projetos é facilitar a utilização de Computação na nuvem e Arquitetura de Micro Serviços.
Entendendo como funciona o configuração remota
A ideia base desse projeto é centralizar todos os arquivos de configurações em um repositório GIT (para que seja possível versionar e gerenciar credenciais).
E teremos arquitetura cliente e servidor, onde o servidor irá se conectar ao repositório e irá servir as configurações.
O cliente, por sua vez, ao iniciar a aplicação irá solicitar ao servidor uma configuração. E com isso o cliente se auto-configura com base nos dados que o servidor configuração entregou.
Dessa forma, o cliente só precisa conhecer o servidor de configuração e nada mais.
Configurando o Servidor
Nosso servidor será uma aplicação com Spring Boot, só adicionaremos a dependência para o Spring Cloud Config.
Nosso pom.xml ficará assim:
<?xml version="1.0" encoding="UTF8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0 (http://maven.apach
<modelVersion>4.0.0</modelVersion>
<groupId>com.domineospring.cloud</groupId>
<artifactId>configserver</artifactId>
<version>1.0SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF8</project.build.sourceEnco
</properties>
<dependencyManagement>
<dependencies>
<!--Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>springbootdependencies</artifactId>
<version>1.5.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>springclouddependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>springcloudconfigserver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>springbootstarterweb</artifactId>
</dependency>
</dependencies>
</project>
Nossa classe que irá iniciar o Spring Boot precisa ter a anotação @EnableConfigServer
@EnableConfigServer
@SpringBootApplication
public class Boot {
public static void main(String[] args) {
SpringApplication.run(Boot.class, args);
}
}
Em nosso arquivo de configuração application.properties devemos definir onde está o repositório GIT. (No meu caso defini também a porta que ele irá subir, pois estou rodando tudo na mesma máquina.)
server.port=8001
spring.cloud.config.server.git.uri=https://github.com/feh-wilinando/spring-cloud-config-files
Pronto, terminamos de configurar o nosso Config Server.
Vamos dar uma olhada nesse repositório.
Repositório GIT para arquivos de configuração
Nesse repositório podemos ter vários arquivos de configurações (.properties ou .yml) com profiles e tudo que já conhecemos quando utilizamos Spring Boot.
O nome do arquivo deve ser o nome da aplicação que iremos configurar no cliente.
Caso seja um arquivo com a extensão .properties devemos seguir a mesma convenção de nome para o profile (nome da aplicação–profile.properties).
Caso seja um arquivo com a extensão .yml podemos definir o(s) profile(s) dentro do próprio arquivo.(vide acima)
No repositório que configuramos temos dois arquivos:
cliente-development.properties
spring.datasource.username=root
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost/bancoDeDados
cliente-prod.properties
spring.datasource.username=application-user
spring.datasource.password=x123XASD&1
spring.datasource.url=jdbc:mysql://localhost/bancoDeDadosOficial
Agora que entendemos o que deve ter no repositório, vamos configurar o nosso cliente.
Configurando o Cliente
As configurações do lado do cliente são bem semelhantes à do servidor. Teremos uma aplicação Spring Boot e adicionaremos a dependência do Spring Cloud Config
Nosso arquivo pom.xml ficará assim:
<?xml version="1.0" encoding="UTF8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0 (http://maven.apach
<modelVersion>4.0.0</modelVersion>
<groupId>com.domineospring.cloud</groupId>
<artifactId>configclient</artifactId>
<version>1.0SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF8</project.build.sourceEnco
</properties>
<dependencyManagement>
<dependencies>
<!--Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>springbootdependencies</artifactId>
<version>1.5.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>springclouddependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>springbootstarterweb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>springcloudconfigclient</artifactId>
</dependency>
</dependencies>
</project>
Nossa classe que iniciará a aplicação não precisa ter nada de especial:
package com.domineospring.cloud.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Boot {
public static void main(String[] args) {
SpringApplication.run(Boot.class, args);
}
}
Teremos um único controller que irá receber injetado os valores referente à configuração do datasource (que foi definida nos arquivos de configurações) e irá retornar-los no próprio body da response.
Para injetar os valores referente ao datasource vamos usar a anotação @Value.
package com.domineospring.cloud.client.controllers;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HomeController {
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.url}")
private String url;
@GetMapping("/datasource")
public String configurations(){
Map<String, String> response = new HashMap<>();
response.put("username", username);
response.put("password", password);
response.put("url", url);
return response.toString();
}
}
No arquivo application.properties vamos definir o nome da aplicação:
spring.application.name=client
Agora precisamos conectar a nossa aplicação cliente ao servidor de configuração.
Para isso, no nosso projeto teremos um arquivo bootstrap.properties ou bootstrap.yml. Esse arquivo é lido durante o processo de inicialização da aplicação, nele iremos indicar o endereço do servidor de configuração.
bootstrap.properties
spring.cloud.config.uri=http://localhost:8001
Podemos definir o profile da nossa aplicação para filtrar qual arquivo de configuração deve ser utilizado.
Uma prática comum é deixar essa configuração de profile ativo no arquivo bootstrap.properties.
Pois nele fica a configuração do endereço do Config Server. E como o servidor de configuração utiliza o profile para selecionar o arquivo corretamente, nada mais justo que essas configurações fiquem próximas.
Vamos definir o profile development também no nosso arquivo bootstrap.properteis.
bootstrap.properties
spring.cloud.config.uri=http://localhost:8001
spring.profiles.active=development
Pronto, nossas aplicações servidora e cliente estão pronta para serem usadas.
Basta iniciar a Config Server e depois inicializar nossa aplicação cliente.
Podemos utilizar o utilitário curl para fazer a requisição para a aplicação cliente e ver como resultado para cada ambiente.
curl http://localhost:8080/datasource
Com definimos o profile development veremos o seguinte resultado: {password=, url=jdbc:mysql://localhost/bancoDeDados, username=root} que são justamente os valores que definimos arquivo client-development.properties
Se alteramos para o profile production e utilizarmos novamente o curl para efetuar o request, veremos o seguinte resultado.
{password=x123XASD&1, url=jdbc:mysql://localhost/bancoDeDadosOficial, username=application-user}
E se não definirmos nenhum profile?
Nesse caso o Config Server vai procurar um arquivo que não tenha uma definição de profile. No nosso caso ele irá procurar um arquivo client.properties (se estivéssemos usando arquivo no formato yml ele iria procurar dentro do arquivo uma configuração que não estivesse atrelada a nenhum profile).
Como não temos esse arquivo isso causará um erro. As alternativas aqui são criar o arquivo sem profile no repositório com GIT ou deixar a configuração sem profile no próprio arquivo application.properties ou application.yml.
Agora nossas configurações ficam todas centralizadas no nosso repositório GIT.
Para saber mais:
Autenticação
Não precisamos de nenhum tipo de autenticação pois o repositório que estou utilizando no exemplo é público.
Porém se fosse um repositório privado precisaríamos informar ou usuário/senha ou chave ssh
Para usuário e senha precisamos definir também as seguintes configurações:
spring.cloud.config.server.git.username=seu-usuario-aqui
spring.cloud.config.server.git.password=sua-senha-aqui
No caso de usar chaves ssh temos duas opções a primeira (que é a padrão) é utilizar as configurações de ssh da maquina (que irá utilizar as configurações contidas em ~/.ssh e /etc/ssh).
Dessa forma não precisamos fazer nada via código ou configuração no projeto.
A segunda opção é manter as configuração no arquivo application.properties.
Para isso temos definir as seguintes configurações:
spring.cloud.config.server.git.ignoreLocalSshSettings=true #para não considerar configurações de ssh locais
spring.cloud.config.server.git.hostKey=seu-host-aqui
spring.cloud.config.server.git.hostKeyAlgorithm=ssh-rsa #algoritmo utilizado para gerar as chaves (ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384 ,ecdsa-sha2-nistp521)
spring.cloud.config.server.git.privateKey= |
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX
IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF
ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud
1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i
oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W
DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd
fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b
BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG
EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj
5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8
+AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe
pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG
ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ
xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW
dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi
PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX
VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z
FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R
gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4
VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV
cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee
KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N
CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs
q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J
69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT
-----END RSA PRIVATE KEY-----
Utilizando Labels
Além do nome da aplicação e profile, podemos utilizar labels para indicar ao servidor de configuração qual arquivo usar.
Com a label podemos definir qual a branch se encontra o nosso arquivo de configuração.
Em nosso repositório temos além da branch master a branch aws, com os mesmos arquivos:
cliente-development.properties na branch AWS
spring.datasource.username=aws-development
spring.datasource.password=aws-simple-password
spring.datasource.url=jdbc:mysql://aws-host/bancoDeDados
cliente-production.properties na branch AWS
spring.datasource.username=application-user
spring.datasource.password=x123XASD&1
spring.datasource.url=jdbc:mysql://localhost/bancoDeDadosOficial
Podemos definir a label também no arquivo bootstrap.properteis:
bootstrap.properties
spring.cloud.config.uri=http://localhost:8001
spring.profiles.active=production
spring.cloud.config.label=aws
Com isso, ao efetuar novamente a requisição via curl:
curl http://localhost:8080/datasource
Veremos no como resultado o texto {password=aws-x123XASD&1, url=jdbc:mysql://aws-host/bancoDeDadosOficial, username=aws-user}, pois está definido o profile production e a label aws.
Se definirmos o profile development e repetirmos o processo veremos como resultado o texto {password=aws-simple-password, url=jdbc:mysql://aws-host/bancoDeDados, username=aws-development}.
Ganhamos muita flexibilidade para gerenciar qual arquivo deve ser selecionado.
E aí o que achou do Spring Cloud Config?