Digamos que temos essa tarefa.
Existe uma fonte de transações no mercado de ações. Essa fonte nos envia transações por meio da interface Rest.
Precisamos obter essas transações, salvá-las no banco de dados e fazer um armazenamento conveniente na memória.
Este repositório deve executar as seguintes funções:
- retornar uma lista de negócios;
- retornar a posição completa, ou seja, tabela “instrumento” - “quantidade atual de títulos”;
- posição de retorno para um determinado instrumento.
Como abordamos essa tarefa?
De acordo com os preceitos da moda de microsserviços, precisamos dividir a tarefa em componentes de microsserviços:
- recebimento de uma transação pelo Rest;
- salvar a transação no banco de dados;
- armazenamento na memória para apresentar dados de posição.
Vamos fazer o primeiro e o terceiro serviços dentro da estrutura deste tutorial, e deixar o segundo para a segunda parte (escreva nos comentários se for interessante).
Portanto, temos dois microsserviços.
O primeiro recebe dados de fora.
O segundo processa esses dados e responde às solicitações de entrada.
Claro, queremos obter dimensionamento horizontal, atualizações ininterruptas e outros benefícios dos microsserviços.
O que é uma tarefa muito difícil diante de nós?
Na verdade, existem muitos deles, mas agora vamos falar sobre como os dados fluirão entre esses microsserviços. Você também pode fazer Descansar entre eles, pode colocar algum tipo de fila, pode inventar um monte de coisas com seus prós e contras.
Vejamos uma abordagem possível - comunicação assíncrona por meio da estrutura Axon .
Quais são as vantagens desta solução?
Em primeiro lugar, a comunicação assíncrona aumenta a flexibilidade (sim, há um ponto negativo aqui, mas estamos falando apenas dos profissionais até agora).
Em segundo lugar, obtemos Event Sourcing e CQRS prontos para uso .
Terceiro, o Axon fornece uma infraestrutura pronta e só precisamos nos concentrar no desenvolvimento da lógica de negócios.
Vamos começar.
Teremos o projeto no gradle. Terá três módulos:
- comum. módulo com estruturas de dados comuns (não gostamos de copiar e colar);
- tradeCreator. módulo com microsserviço para aceitação de transações em repouso;
- tradeQueries. módulo com microsserviços para exibição de posição.
Vamos usar o Spring Boot como base e conectar o acionador de partida Axon.
Axon funciona bem sem Spring, mas vamos usá-los juntos.
Precisamos parar por aqui e falar algumas palavras sobre o Axon.
É um sistema cliente-servidor. Existe um servidor - este é um aplicativo separado, vamos executá-lo no docker.
E há clientes que se inserem em microsserviços.
Esta é a foto. Primeiro, o servidor Axon (no docker) é iniciado e, em seguida, nossos microsserviços.
Na inicialização, os microsserviços procuram um servidor e começam a interagir com ele. A interação pode ser condicionalmente dividida em dois tipos: técnica e comercial.
A técnica é a troca de mensagens "Estou vivo" (essas mensagens podem ser vistas no modo de log de depuração).
Os negócios são obscurecidos por mensagens como "novo acordo".
Uma característica importante, após iniciar o microsserviço, pode perguntar ao servidor Axon "o que aconteceu" e o servidor envia os eventos acumulados para o microsserviço. Assim, o microsserviço pode ser reiniciado com relativa segurança sem perda de dados.
Com esse esquema de troca, podemos executar facilmente muitas instâncias de microsserviços
e em diferentes hosts.
Sim, uma instância do Axon Server não é confiável, mas até agora.
Trabalhamos nos paradigmas Event Sourcing e CQRS. Isso significa que devemos ter "equipes", "eventos" e "amostras".
Teremos um comando: "criar um negócio", um evento "negócio criado" e três seleções: "mostrar todos os negócios", "mostrar posição", "mostrar posição para um instrumento".
O esquema de trabalho é o seguinte:
- O microsserviço TradeCreator aceita uma transação Rest.
- O microsserviço tradeCreator cria um comando "create trade" e o envia para o servidor Axon.
- O servidor Axon recebe o comando e envia o comando ao destinatário interessado, no nosso caso é o microsserviço tradeCreator.
- O microsserviço tradeCreator recebe um comando, gera um evento "acordo criado" e o envia ao servidor Axon.
- O servidor Axon recebe o evento e o encaminha para os assinantes interessados.
- Agora temos apenas um destinatário interessado - o microsserviço tradeQueries.
- O microsserviço tradeQueries recebe o evento e atualiza os dados internos.
(É importante que no momento da formação do evento, o microserviço tradeQueries pode não estar disponível, mas assim que iniciar, receberá imediatamente o evento).
Sim, o axônio-servidor está no centro da comunicação, todas as mensagens passam por ele.
Vamos passar para a codificação.
Para não atrapalhar o post com código, abaixo darei apenas fragmentos, o link para o exemplo completo estará abaixo.
Vamos começar com o módulo comum.
Nele, as partes comuns são o evento (classe CreatedTradeEvent). Preste atenção no nome, aliás, esse é o nome da equipe que gerou este evento, mas no pretérito. No passado, porque primeiro, aparece o comando, que leva à criação do evento.
Outras estruturas comuns incluem classes para descrever uma posição (posição de classe), uma negociação (negociação de classe) e um lado de uma negociação (lado enum), ou seja, compra ou venda.
Vamos passar para o módulo tradeCreator.
Este módulo possui uma interface Rest (classe TradeController) para aceitar negociações.
O comando "criar um acordo" é formado a partir do acordo recebido e enviado ao servidor axônio.
@PostMapping("/trade")
public ResponseEntity<String> create(@RequestBody Trade trade) {
var createTradeCommand = CreateTradeCommand.builder()
.tradeId(trade.getTradeId())
...
.build();
var result = commandGateway.sendAndWait(createTradeCommand, 3, TimeUnit.SECONDS);
return ResponseEntity.ok(result.get().toString());
}
Para processar o comando, a classe TradeAggregate é usada.
Para que o Axon o encontre, adicionamos a anotação @Aggregate.
O método para processar o comando é assim (com uma abreviatura):
@CommandHandler
public TradeAggregate(CreateTradeCommand command) {
log.info("command: {}", command);
var event = CreatedTradeEvent.builder()
.tradeId(command.tradeId())
....
.build();
AggregateLifecycle.apply(event);
}
Um evento é gerado a partir do comando e enviado ao servidor.
O comando está na classe CreateTradeCommand.
Agora vamos dar uma olhada no último módulo tradeQueries.
As seleções são descritas no pacote de consultas.
Este módulo também possui uma interface
TradeController Rest de classe pública.
Por exemplo, vamos ver o processamento da solicitação: "mostrar todas as transações".
@GetMapping("/trade/all")
public List<Trade> findAllTrades() {
return queryGateway.query(new FindAllTradesQuery(),
ResponseTypes.multipleInstancesOf(Trade.class)).join();
}
Uma solicitação de busca é criada e enviada ao servidor.
A classe TradesEventHandler é usada para processar a solicitação de busca.
Tem um método anotado
@QueryHandler
public List<Position> handleFindCurrentPositionQuery(FindCurrentPositionQuery query)
É ele o responsável por buscar os dados do armazenamento na memória.
Surge a questão de como as informações são atualizadas nesta loja.
Para começar, esta é apenas uma coleção de ConcurrentHashMaps adaptados para seleções específicas.
Para atualizá-los, o método é aplicado:
@EventHandler
public void on(CreatedTradeEvent event) {
log.info("event:{}", event);
var trade = Trade.builder()
...
.build();
trades.put(event.tradeId(), trade);
position.merge(event.shortName(), event.size(),
(oldValue, value) -> event.side() == Side.BUY ? oldValue + value : oldValue - value);
}
Ele recebe o evento "negócio criado" e atualiza os Mapas.
Esses são os principais pontos de desenvolvimento de microsserviços.
E sobre as deficiências do Axon?
Primeiro, essa é a complicação da infraestrutura, um ponto de falha apareceu - o servidor Axon, todas as comunicações passam por ele.
Em segundo lugar, a desvantagem de tais sistemas distribuídos é claramente manifestada - inconsistência temporária de dados. Em nosso caso, pode decorrer um tempo inaceitavelmente longo entre o recebimento de uma nova oferta e a atualização dos dados das amostras.
O que ficou nos bastidores?
Nada é dito sobre Event Sourcing e CQRS, o que é e para que serve.
Sem divulgar esses conceitos, alguns pontos podem não ficar claros.
Talvez alguns fragmentos de código também precisem de esclarecimento.
Falamos sobre isso em um webinar aberto .
Exemplo completo .