Microserviços com comunicação via Axon

Neste tutorial simples, criaremos alguns microsserviços em Spring Boot e organizaremos a interação entre eles através do framework Axon.

Microserviços com comunicação via Axon


Suponha que temos a seguinte tarefa.

Há uma fonte de transações no mercado de ações. Essa fonte nos envia as transações através de uma API Rest.

Precisamos receber essas transações, armazená-las em uma base de dados e criar um repositório em memória conveniente.

Esse repositório deve cumprir as seguintes funções:

  • retornar uma lista de trades;
  • retornar a posição completa, ou seja, uma tabela "instrumento" — "quantidade atual de papéis";
  • retornar a posição para um instrumento específico.

Como chegaremos à solução dessa tarefa?

Seguindo as diretrizes da moda dos microsserviços, precisamos dividir a tarefa em microsserviços componentes:

  • obter a transação via Rest;
  • armazenar a transação na base de dados;
  • repositório em memória para representação dos dados da posição.

Vamos, neste tutorial, criar o primeiro e o terceiro serviço, enquanto deixaremos o segundo para a segunda parte (deixe um comentário se você achar isso interessante).

Portanto, temos dois microsserviços.

O primeiro recebe dados externos.

O segundo processa esses dados e responde às solicitações de entrada.

Claro que queremos escalabilidade horizontal, atualizações sem interrupções e outras vantagens dos microsserviços.

Qual é, sem dúvida, a tarefa desafiadora que temos pela frente?

Na verdade, são muitas, mas agora vamos falar sobre como os dados serão trocados entre esses microsserviços. Podemos usar Rest, podemos implementar uma fila, ou muitas outras opções com seus prós e contras.

Vamos considerar uma das abordagens possíveis — a interação assíncrona através do framework Axon..

Quais são as vantagens dessa solução?

Primeiro, a interação assíncrona aumenta a flexibilidade (sim, há um lado negativo, mas por enquanto estamos focando apenas nos pontos positivos).

Em segundo lugar, recebemos de forma nativa Event Sourcing. e CQRS.
Em terceiro lugar, o Axon fornece uma infraestrutura pronta, permitindo que nos concentremos apenas no desenvolvimento da lógica de negócios.

Vamos começar.

O projeto será feito com Gradle. Ele terá três módulos:

  • common. módulo com estruturas de dados compartilhadas (não gostamos de copiar e colar);
  • tradeCreator. módulo com o microsserviço para receber transações via Rest;
  • tradeQueries. módulo com o microsserviço para exibição da posição.

Usaremos Spring Boot como base e conectaremos o starter do Axon.

Axon funciona muito bem sem o Spring, mas vamos usá-los juntos.

Aqui precisamos parar e dizer algumas palavras sobre o Axon.

É um sistema cliente-servidor. Há um servidor — é um aplicativo separado, que vamos executar no Docker.

E há clientes que se integram aos microsserviços.
O cenário é o seguinte: primeiro, o Axon-server é iniciado (no Docker), depois nossos microsserviços.

Ao iniciar, os microsserviços buscam o servidor e começam a interagir com ele. A interação pode ser dividida em dois tipos: técnica e de negócios.

Técnica — é a troca de mensagens 'estou vivo' (essas mensagens podem ser vistas no modo de registro debug).

De negócios — é a troca de mensagens como 'nova transação'.

Uma característica importante, após a inicialização, o microsserviço pode perguntar ao Axon-server 'o que aconteceu' e o servidor transmite os eventos acumulados para o microsserviço. Assim, o microsserviço pode ser reiniciado de forma relativamente segura sem perda de dados.
Com esse esquema de troca, podemos iniciar muito facilmente várias instâncias de microsserviços,
inclusive em diferentes hosts.

Sim, uma instância do Axon-server não é confiável, mas é o que temos por enquanto.

Trabalhamos nas paradigmas de Event Sourcing e CQRS. Isso significa que devemos ter 'comandos', 'eventos' e 'consultas'.

Tivemos um comando: 'criar transação', um evento 'transação criada' e três consultas: 'mostrar todas as transações', 'mostrar posição', 'mostrar posição por instrumento'.

O funcionamento resulta em:

  1. O microsserviço tradeCreator recebe uma transação via Rest.
  2. O microsserviço tradeCreator cria um comando 'criar transação' e o envia para o Axon-server.
  3. O Axon-server recebe o comando e o retransmite para o destinatário interessado, que neste caso é o microsserviço tradeCreator.
  4. O microsserviço tradeCreator recebe o comando, forma o evento 'transação criada' e o envia para o Axon-server.
  5. O Axon-server recebe o evento e o retransmite para os destinatários interessados.
  6. Atualmente, temos apenas um destinatário interessado — o microsserviço tradeQueries.
  7. O microsserviço tradeQueries recebe o evento e atualiza os dados internos.

(É importante que no momento da formação do evento o microsserviço tradeQueries pode não estar disponível, mas assim que for iniciado, receberá imediatamente o evento).

Sim, o Axon-server está no centro das comunicações, todas as mensagens passam por ele.

Vamos começar a codificação.

Para não poluir o post com código, abaixo trarei apenas fragmentos, o link para o exemplo completo estará abaixo.

Vamos começar com o módulo comum.

Nele, partes comuns são o evento (class CreatedTradeEvent). Note o nome, que na verdade é o nome do comando que gerou esse evento, mas no passado. No passado, porque primeiro aparece o comando que resulta na criação do evento.

Outras estruturas comuns incluem classes para descrever a posição (class Position), a negociação (class Trade) e o lado da negociação (enum Side), ou seja, compra ou venda.

Vamos para o módulo tradeCreator.

Esse módulo possui uma interface Rest (class TradeController) para aceitar negociações.
A negociação recebida é transformada em um comando "criar negociação" e enviada para o servidor axon.

    @PostMapping("/trade")
    public ResponseEntity 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, é utilizada a classe class TradeAggregate.
Para que o Axon a encontre, usamos a anotação @Aggregate.
O método para processar o comando se parece com isto (com abreviação):

    @CommandHandler
    public TradeAggregate(CreateTradeCommand command) {
        log.info("command: {}", command);
        var event = CreatedTradeEvent.builder()
                .tradeId(command.tradeId())
		....
                .build();
        AggregateLifecycle.apply(event);
    }

Do comando é gerado um evento e enviado para o servidor.
O comando está na classe CreateTradeCommand.

Agora vamos olhar para o último módulo tradeQueries.

As consultas são descritas no pacote queries.
Neste módulo também há uma interface Rest.
public class TradeController.

Como exemplo, vamos ver o processamento do pedido: "mostrar todas as negociações".

    @GetMapping("/trade/all")
    public List findAllTrades() {
        return queryGateway.query(new FindAllTradesQuery(),
                ResponseTypes.multipleInstancesOf(Trade.class)).join();
    }

É criada uma consulta e enviada para o servidor.

Para processar a consulta é utilizada a classe TradesEventHandler.
Nela, há um método anotado com uma anotação.

   @QueryHandler
    public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)

É ele quem é responsável pela recuperação de dados do armazenamento em memória.

Surge a pergunta: como a informação é atualizada nesse armazenamento.

Começamos pelo fato de que é apenas um conjunto de ConcurrentHashMap, projetado para consultas específicas.
Para sua atualização, é utilizado o método:

    @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 'trade criada' e atualiza os Maps.

Esses são os principais pontos no desenvolvimento de microsserviços.

O que pode ser dito sobre as desvantagens do Axon?

Primeiramente, isso complica a infraestrutura, criando um ponto de falha – o servidor Axon, todas as comunicações passam por ele.

Em segundo lugar, a desvantagem de sistemas distribuídos se torna bastante evidente – a inconsistência temporal dos dados. No nosso caso, entre a recebimento de uma nova troca e a atualização dos dados para consultas pode passar tempo demais.

O que ficou de fora?

Nada foi mencionado sobre Event Sourcing e CQRS, o que são e para que servem.
Sem esses conceitos sendo explicados, alguns pontos podem não ter ficado claros.

Talvez alguns trechos de código também precisem de explicação.

Sobre isso, vamos falar no webinar aberto 21 de setembro.

Exemplo completo.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster