Microservices with communication through Axon

In deze eenvoudige tutorial maken we een paar microservices met Spring Boot en organiseren we de interactie tussen hen via het Axon-framework.

Microservices with communication through Axon


Laten we aannemen dat we de volgende taak hebben.

Er is een bron van transacties op de aandelenmarkt. Deze bron geeft ons transacties door via een Rest-interface.

We moeten deze transacties ophalen, opslaan in een database en een handig in-memory opslag maken.

Deze opslag moet de volgende functies vervullen:

  • een lijst van trades teruggeven;
  • de volledige positie teruggeven, d.w.z. een tabel met 'instrument' — 'huidige hoeveelheid papieren';
  • de positie teruggeven voor een gegeven instrument.

Hoe benaderen we deze taak?

Volgens de principes van microservice-architectuur moeten we de taak opsplitsen in de samenstellende microservices:

  • ontvangst van transacties via Rest;
  • opslag van transacties in de database;
  • in-memory opslag voor het presenteren van gegevens over de positie.

Laten we in het kader van deze tutorial de eerste en de derde service maken, de tweede bewaren voor het tweede deel (laat het in de opmerkingen weten als dit interessant is).

Dus, we hebben twee microservices.

De eerste ontvangt gegevens van buitenaf.

De tweede verwerkt deze gegevens en reageert op binnenkomende verzoeken.

We willen natuurlijk horizontale schaalbaarheid, ononderbroken updates en andere voordelen van microservices.

Welke, niet al te gemakkelijke, taak staat er voor ons?

Eigenlijk zijn er veel, maar laten we nu praten over hoe de gegevens tussen deze microservices zullen stromen. Tussen hen kan ook een Rest worden gemaakt, er kan een wachtrij worden ingesteld, er zijn veel mogelijkheden met hun voor- en nadelen.

Laten we een van de mogelijke benaderingen overwegen - asynchrone interactie via het Axon-framework.

Wat zijn de voordelen van deze oplossing?

Ten eerste vergroot asynchrone interactie de flexibiliteit (ja, er is ook een nadeel, maar we hebben het voorlopig alleen over de voordelen).

Ten tweede krijgen we direct uit de doos Event Sourcing en CQRS.
Ten derde biedt Axon kant-en-klare infrastructuur, zodat we ons alleen op de ontwikkeling van de businesslogica hoeven te concentreren.

Laten we beginnen.

Ons project zal op Gradle zijn. Het zal drie modules bevatten:

  • common. een module met gemeenschappelijke datastructuren (we houden niet van kopiĆ«ren en plakken);
  • tradeCreator. een module met een microservice voor het ontvangen van transacties via Rest;
  • tradeQueries. een module met een microservice voor het weergeven van de positie.

Laten we Spring Boot als basis nemen en de Axon-starter aansluiten.

Axon werkt uitstekend zonder Spring, maar we gaan ze samen gebruiken.

Hier moeten we even stoppen en een paar woorden over Axon zeggen.

Het is een client-server systeem. Er is een server – dat is een aparte applicatie, die we in Docker gaan draaien.

En er zijn clients die in microservices worden geĆÆntegreerd.
Zo ziet het eruit. Eerst wordt de Axon-server (in Docker) gestart, daarna onze microservices.

Bij het opstarten zoeken de microservices de server en beginnen ze met interactie. De interactie kan grofweg in twee soorten worden verdeeld: technisch en zakelijk.

Technisch is het uitwisselen van berichten zoals 'ik ben alive' (zo'n bericht kan in debug-logmodus worden gezien).

Zakelijk is de uitwisseling van berichten zoals 'nieuwe deal'.

Een belangrijk kenmerk is dat, na de start, microservices de Axon-server kunnen vragen 'wat is er gebeurd', en de server geeft de microservice de verzamelde gebeurtenissen door. Daardoor kan een microservice relatief veilig opnieuw worden opgestart zonder gegevensverlies.
Met deze uitwisselingsstructuur kunnen we heel eenvoudig veel exemplaren van microservices opstarten,
zelfs op verschillende hosts.

Ja, ƩƩn exemplaar van de Axon-server is niet betrouwbaar, maar voorlopig is het zo.

We werken binnen de paradigma's Event Sourcing en CQRS. Dit betekent dat we 'commando's', 'gebeurtenissen' en 'lezers' moeten hebben.

We zullen ƩƩn commando hebben: 'maak deal aan', ƩƩn gebeurtenis 'deal is aangemaakt', en drie lezers: 'toon alle deals', 'toon positie', 'toon positie per instrument'.

De werkwijze ziet er als volgt uit:

  1. De microservice tradeCreator accepteert een deal via Rest.
  2. De microservice tradeCreator maakt een commando 'maak deal aan' en stuurt dit naar de Axon-server.
  3. De Axon-server ontvangt het commando en stuurt het door naar de belanghebbende ontvanger, in ons geval is dat de microservice tradeCreator.
  4. De microservice tradeCreator ontvangt het commando, vormt de gebeurtenis 'deal is aangemaakt' en stuurt deze naar de Axon-server.
  5. De Axon-server ontvangt de gebeurtenis en stuurt deze door naar de belanghebbende abonnees.
  6. Momenteel hebben we slechts ƩƩn belanghebbende ontvanger – dat is de microservice tradeQueries.
  7. De microservice tradeQueries ontvangt de gebeurtenis en werkt de interne gegevens bij.

(Belangrijk is dat op het moment van de vorming van de gebeurtenis de microservice tradeQueries mogelijk niet beschikbaar is, maar zodra deze opstart, ontvangt het direct de gebeurtenis).

Ja, de axon-server staat in het centrum van de communicatie, alle berichten gaan via hem.

Laten we beginnen met coderen.

Om de post niet met code te overbelasten, geef ik hieronder alleen fragmenten, de link naar het volledige voorbeeld volgt hieronder.

Laten we beginnen met de algemene module common.

De gemeenschappelijke delen hierin zijn een gebeurtenis (class CreatedTradeEvent). Let op de naamgeving, dit is in wezen de naam van het commando dat deze gebeurtenis heeft veroorzaakt, maar in de verleden tijd. In de verleden tijd, omdat eerst het commando verschijnt dat leidt tot de creatie van de gebeurtenis.

Andere gemeenschappelijke structuren zijn klassen voor het beschrijven van een positie (class Position), een transactie (class Trade) en de kant van de transactie (enum Side), dat wil zeggen, kopen of verkopen.

Laten we overgaan naar de module tradeCreator.

Deze module heeft een Rest-interface (class TradeController) voor het ontvangen van transacties.
Van de ontvangen transactie wordt een ā€˜creĆ«er transactie’-commando gevormd en naar de axon-server gestuurd.

    @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());
    }

Voor het verwerken van het commando wordt de class TradeAggregate gebruikt.
Om ervoor te zorgen dat Axon het vindt, plaatsen we de annotatie @Aggregate.
De methode voor het verwerken van het commando ziet er als volgt uit (afgekort):

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

Van het commando wordt een gebeurtenis gevormd en naar de server gestuurd.
Het commando bevindt zich in de class CreateTradeCommand.

Laten we nu kijken naar de laatste module tradeQueries.

De queries worden beschreven in het pakket queries.
In deze module is er ook een Rest-interface.
public class TradeController.

Voor het voorbeeld bekijken we de verwerking van het verzoek: ā€˜toon alle transacties’.

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

Er wordt een verzoek voor een selectie aangemaakt en naar de server gestuurd.

Voor het verwerken van het selectieverzoek wordt de class TradesEventHandler gebruikt.
Hierin is er een methode, gemarkeerd met de annotatie.

   @QueryHandler
    public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)

Hij is verantwoordelijk voor het ophalen van gegevens uit de in-memory opslag.

De vraag rijst hoe in deze opslag de informatie wordt bijgewerkt.

Laten we beginnen met het feit dat dit gewoon een verzameling ConcurrentHashMap's is, afgestemd op specifieke selecties.
Voor hun bijwerking wordt de methode toegepast:

    @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);
    }

Het verwerkt het 'handelscreatie'-evenement en werkt de Maps bij.

Dit zijn de belangrijkste punten van de ontwikkeling van microservices.

Wat kunnen we zeggen over de nadelen van Axon?

Ten eerste, het compliceert de infrastructuur en introduceert een faalpunt - de Axon-server, alle communicatie verloopt via hem.

Ten tweede, het nadeel van dergelijke gedistribueerde systemen wordt sterk zichtbaar - tijdelijke inconsistentie van gegevens. In ons geval kan er onacceptabel veel tijd verstrijken tussen het ontvangen van een nieuwe handel en het bijwerken van de gegevens voor de selecties.

Wat is er achter de schermen gebleven?

Er is helemaal niets gezegd over Event Sourcing en CQRS, wat het is en waarvoor het nodig is.
Zonder uitleg van deze concepten zouden sommige punten mogelijk onduidelijk zijn.

Wellicht hebben ook bepaalde fragmenten van de code uitleg nodig.

Hierover gaan we het hebben tijdens een open webinar op 21 september.

Volledige voorbeeld.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster