O módulo de ambiante é essencial para lidar com configurações variáveis entre diferentes ambientes como desenvolvimento, teste e produção. Vamos explorar uma implementação funcional deste conceito.
Fontes de Configuração
Diferentes origens de propriedades podem ser abstraídas através do seguinte padrão:
public abstract class OrigemConfiguracao<T> {
protected final String identificador;
protected final T origem;
public abstract Object obterPropriedade(String chave);
public static class OrigemReservada extends OrigemConfiguracao<Object> {
@Override
public Object obterPropriedade(String chave) {
return null;
}
}
}
Propriedades Estruturadas
Para fontes que permitem iteração:
public abstract class OrigemEnumeravel<T>
extends OrigemConfiguracao<T> {
public abstract String[] obterChaves();
}
Implementação baseada em mapa:
public class OrigemMapaConfig
extends OrigemEnumeravel<Map<String, Object>> {
@Override
public Object obterPropriedade(String chave) {
return this.origem.get(chave);
}
@Override
public String[] obterChaves() {
return this.origem.keySet().toArray(new String[0]);
}
}
Gerenciamento de Múltiplas Fontes
public interface FontesConfiguraveis
extends Iterable<OrigemConfiguracao<?>> {
boolean contem(String nome);
OrigemConfiguracao<?> obter(String nome);
}
public class FontesConfigMutaveis implements FontesConfiguraveis {
private final List<OrigemConfiguracao<?>> fontes =
new CopyOnWriteArrayList<>();
@Override
public boolean contem(String nome) {
return fontes.stream()
.anyMatch(fonte -> fonte.identificador.equals(nome));
}
}
Resolução Dinâmica de Propriedades
public interface ResolvedorPropriedades {
String resolverMarcadores(String texto);
}
public interface ResolvedorConfiguravel extends ResolvedorPropriedades {
void definirPrefixoMarcador(String prefixo);
void definirSufixoMarcador(String sufixo);
}
public class ResolvedorFontesPropriedades implements ResolvedorConfiguravel {
private final FontesConfiguraveis fontes;
@Override
public String resolverMarcadores(String texto) {
// Implementação de resolução de ${...}
}
}
Gerenciamento de Perfis
public interface PerfilAmbiente {
boolean corresponde(Predicate<String> perfilAtivo);
}
public class AnalisadorPerfis {
public static PerfilAmbiente criar(String... expressoes) {
// Lógica de análise de expressões (AND/OR/NOT)
}
}
Implementação Central do Ambiente
public interface Ambiente extends ResolvedorPropriedades {
String[] obterPerfisAtivos();
}
public interface AmbienteConfiguravel extends Ambiente, ResolvedorConfiguravel {
void definirPerfisAtivos(String... perfis);
FontesConfigMutaveis obterFontesConfig();
}
public abstract class AmbienteAbstrato implements AmbienteConfiguravel {
protected final Set<String> perfisAtivos = new LinkedHashSet<>();
protected final FontesConfigMutaveis fontes;
protected Set<String> carregarPerfisAtivos() {
String perfis = obterPropriedade("ambiente.perfis.ativos");
if (perfis != null) {
return new HashSet<>(Arrays.asList(perfis.split(",")));
}
return Collections.emptySet();
}
}
public class AmbientePadrao extends AmbienteAbstrato {
@Override
protected void inicializarFontes() {
fontes.adicionar(new OrigemMapaConfig(
"propriedadesSistema", System.getProperties()));
fontes.adicionar(new OrigemMapaConfig(
"variaveisAmbiente", System.getenv()));
}
}
Testando a Implementação
@Test
public void testarAmbiente() {
AmbientePadrao ambiente = new AmbientePadrao();
Map<String, Object> propsTeste = Map.of(
"app.nome", "TesteAmbiente",
"app.versao", "1.5"
);
ambiente.obterFontesConfig().adicionar(
new OrigemMapaConfig("configApp", propsTeste));
assertEquals("TesteAmbiente",
ambiente.obterPropriedade("app.nome"));
}