Contexto
Quando trabalhando em sistemas reais, temos que nos preocupar com a segurança de nosso código, pontos específicos de nosso código podem ser mais suscetíveis a falhas, mudanças de uma camada de baixo nível (infraestrutura) podem acarretar em problemas caso haja grandes alterações ou até mesmo uma mudança de vendor. Nesse breve artigo irei discutir duas atividades bem frequentes na minha rotina no Bees/AmBev
1. Features Toggle
Em diversos momentos, faz sentido que uma feature seja facilmente desligada ou não usando um toggle, fazendo com que essa alteração não precise de um deploy, dando mais agilidade e segurança à sua alteração.
Para isso temos algumas opções, podemos usar variáveis de ambientes ou até mesmo guardar o valor no banco de dados.
Uma maneira fácil de implementar em Spring é simplesmente puxar os dados de seu application.properties
ou application.yaml
No Spring
Opção 1: @Value
Essa opção é particularmente útil para configurações mais simples, que possuem apenas um campo (ex: Enabled) e não são usadas em diversos lugares. Yaml:
features:
algumaFeature:
enabled: true
outraFeature:
enabled: true
Spring:
@Value("${features.algumaFeature.enabled}")
private Boolean valueFromFile;
Opção 2: @ConfigurationProperties
Melhor para cenários mais complexos, ou quando a propriedade é usada em diversos lugares. Yaml:
broker:
queues:
ticket:
name: default.ticket
exchanges:
ticket:
name: direct.ticket
type: direct
bindings:
ticket:
exchange: direct.ticket
queue: default.ticket
routingKey: default.ticke
Spring:
@Configuration
@ConfigurationProperties(prefix = "broker")
@Data
public class BrokerConfigurationProperties {
private Map<String, QueueProperties> queues;
private Map<String, ExchangeProperties> exchanges;
private Map<String, BindingProperties> bindings;
@Data
public static class QueueProperties {
@NotEmpty
private String name;
}
@Data
public static class ExchangeProperties {
@NotEmpty
private String name;
private String type;
}
@Data
public static class BindingProperties {
@NotEmpty
private String exchange;
@NotEmpty
private String queue;
@NotEmpty
private String routingKey;
}
}
Aqui basta você fazer o mapeamento das classes de acordo com suas propriedades. Se houver dificuldade, é uma tarefa em que alguma AI provavelmente vai lidar com facilidade.
2. Diferentes Beans de Infraestrutura! Permita um rollback fácil
Em um código ideal, a sua camada de domínio provavelmente estará desacoplada de sua camada de infraestrutura por meio de interfaces. Se isso não faz sentido para você, recomendo meu post de SOLID :) Continuando, com um código com módulos de alto e baixo nível desacoplados, conseguimos mudar a infraestrutura sem mexer no domínio. Isso nos dá a liberdade de gerenciar os beans que sua classe de domínio irá usar com mais facilidade, pois todas as implementações de infraestrutura irão respeitar a interface.
@AllArgsConstructor
public class TicketService {
private final TicketRepository repository;
private final Notifier messagePublisher;
public List<Ticket> findAll(){
return repository.findAll();
}
public Ticket findById(String Id){
return repository.findById(Id).orElseThrow(
() -> new TicketNotFoundException("Ticket not found")
);
}
public Ticket save(Ticket ticket){
messagePublisher.Notify(TicketEventsEnum.TICKET_CREATED);
return repository.save(ticket);
}
}
Temos aqui um Simples Service, tendo suas dependências injetadas via construtor pelo SPRING. Note que nosso serviço conhece regras de negócio, dentre elas, quando um ticket é criado, ele tem que notificar algo, e tem que guardar em algum lugar (repository pode cuidar de mongoDB, MySQL, et cetera.) Note que se tivermos um repositório com código específico do MySQL e quisermos migrar para um código com Spring Data JPA, teremos que mudar a camada de infraestrutura (resumidamente, vamos escrever mais código de Repositório), mas nossa regra de negócio é a mesma. Imagine agora que essa camada é um ponto crítico da sua aplicação, caso a implementação que você fez com tanta boa vontade dê errado, você terá que fazer um rollback.
Definindo os Beans Manualmente
Ao invés de definir os repositórios com @Component
, como teremos dois repositórios, o Spring não saberá com qual Bean lidar.
Temos diversas maneiras de fazer a desambiguação de Beans, dentre elas, prioridades, primary, qualifier, aqui vou para uma abordagem simples, apenas para exemplificar a feature.
No meu yaml, irei definir uma propriedade:
repository:
type: ${DEFINED_REPOSITORY:mysql}
Caso exista uma variável de ambiente chamada DEFINED_REPOSITORY, ela será o padrão para o tipo do meu repositório, caso contrário, será mysql, como um fallback. (No caso, escolhi deixar o MySQL como fallback pois teoricamente ele é o repositório testado e já em produção). Perfeito, agora vamos definir qual bean utilizar:
@Configuration
public class RepositoryConfiguration{
@Value("${repository.type}")
private String repositoryType;
@Bean
public TicketRepository ticketRepository (){
if("mysql".equalsIgnoreCase(repositoryType)){
return new MySQLTicketRepository();
}
if("spring".equalsIgnoreCase(repositoryType)){
return new JpaTicketRepository();
}
return new MySQLTicketRepository(); // fallback
}
}
Outra maneira de fazer esse tipo de configuração é usar profiles do Spring.
Obrigado!
Obrigado, espero que o breve artigo tenha sido útil. Escrevi em 20 minutos e não o revisei, então se houver algum problema, pode me avisar :)