Mikrotjenester med kommunikation via Axon

I denne enkle vejledning vil vi oprette et par mikrotjenester med Spring Boot og organisere interaktion mellem dem via Axon-rammen.

Mikrotjenester med kommunikation via Axon


Lad os antage, at vi har denne opgave.

Der er en kilde til handler på aktiemarkedet. Denne kilde sender os handler via et REST-interface.

Vi skal modtage disse handler, gemme dem i en database og oprette et praktisk in-memory lager.

Dette lager skal udføre følgende funktioner:

  • returnere en liste over handler;
  • returnere den samlede position, dvs. tabellen "instrument" — "aktuel antal papirer";
  • returnere positionen for et givet instrument.

Hvordan vil vi tage fat på denne opgave?

I tråd med mikrotjenesternes mode skal vi opdele opgaven i delkomponenter:

  • modtagelse af handler via REST;
  • gemme handlerne i databasen;
  • in-memory lager til at præsentere data om positionen.

Lad os i denne vejledning lave den første og tredje tjeneste, den anden gemmer vi til del to (skriv i kommentarerne, hvis det er interessant).

Så vi har to mikrotjenester.

Den første modtager data udefra.

Den anden behandler disse data og svarer på indkommende forespørgsler.

Vi ønsker naturligvis at opnå horisontal skalering, uafbrudt opdatering og andre fordele ved mikrotjenester.

Hvilken ret besværlig opgave står vi overfor?

Der er mange, men lad os nu tale om, hvordan data vil blive sendt mellem disse mikrotjenester. Der kan også etableres REST mellem dem, man kan sætte en kø op, eller man kan finde mange andre løsninger med deres fordele og ulemper.

Lad os se på en mulig tilgang – asynkron interaktion via Axon-rammen.

Hvad er fordelene ved denne løsning?

For det første øger asynkron interaktion fleksibiliteten (ja, der er også en ulempe, men lad os nu kun fokusere på fordelene).

For det andet får vi direkte ud af boksen Event Sourcing og CQRS.
For det tredje leverer Axon en klar infrastruktur, og vi skal kun fokusere på udviklingen af forretningslogik.

Lad os komme i gang.

Projektet vil være på Gradle. Det vil have tre moduler:

  • common. modul med fælles datastukturer (vi elsker ikke copy-paste);
  • tradeCreator. modul med mikrotjeneste til modtagelse af handler via REST;
  • tradeQueries. modul med mikrotjeneste til visning af position.

Lad os tage Spring Boot som grundlag og tilslutte Axon-starteren.

Axon fungerer fremragende uden Spring, men vi vil bruge dem sammen.

Her bør vi stoppe op og sige et par ord om Axon.

Det er et klient-server system. Der er en server – det er en separat applikation, som vi vil køre i Docker.

Og der er klienter, der integreres i mikrotjenester.
Det danner et billede. Først starter Axon-serveren (i Docker), og derefter vores mikrotjenester.

Ved opstart søger mikrotjenesterne efter serveren og begynder at interagere med den. Interaktionen kan groft opdeles i to typer: teknisk og forretningsmæssig.

Den tekniske interaktion er udvekslingen af meddelelser som "jeg er live" (sådanne meddelelser kan ses i debug-logning).

Den forretningsmæssige interaktion er udvekslingen af meddelelser som "ny handel".

En vigtig funktion er, at efter opstart kan mikrotjenesten spørge Axon-serveren "hvad skete der", og serveren sender de akkumulerede begivenheder til mikrotjenesten. På den måde kan mikrotjenesten relativt sikkert genstartes uden datatab.
Med denne udvekslingsmetode kan vi meget nemt køre mange instanser af mikrotjenester,
selv på forskellige værter.

Ja, en enkelt instans af Axon-serveren er ikke pålidelig, men indtil videre må vi klare os med det.

Vi arbejder i paradigmerne Event Sourcing og CQRS. Det betyder, at vi skal have "kommandoer", "begivenheder" og "forespørgsler".

Vi vil have en kommando: "opret handel", én begivenhed "handel oprettet", og tre forespørgsler: "vis alle handler", "vis position", "vis position efter instrument".

Arbejdsplanen ser således ud:

  1. Mikrotjenesten tradeCreator modtager handlen via Rest.
  2. Mikrotjenesten tradeCreator opretter kommandoen "opret handel" og sender den til Axon-serveren.
  3. Axon-serveren modtager kommandoen og videresender den til den interesserede modtager, i vores tilfælde mikrotjenesten tradeCreator.
  4. Mikrotjenesten tradeCreator modtager kommandoen, danner begivenheden "handel oprettet" og sender den til Axon-serveren.
  5. Axon-serveren modtager begivenheden og videresender den til de interesserede abonnenter.
  6. I øjeblikket har vi kun én interesseret modtager – mikrotjenesten tradeQueries.
  7. Mikrotjenesten tradeQueries modtager begivenheden og opdaterer sine interne data.

(Det er vigtigt, at mikrotjenesten tradeQueries muligvis ikke er tilgængelig på tidspunktet for dannelsen af begivenheden, men så snart den starter, vil den straks modtage begivenheden).

Ja, Axon-serveren står i centrum for kommunikationerne, alle meddelelser går gennem den.

Lad os gå i gang med kodningen.

For ikke at overbelaste indlægget med kode, vil jeg her kun inkludere fragmenter, linket til det fulde eksempel vil være nedenfor.

Vi begynder med det fælles modul common.

I dette modul er de fælles elementer en begivenhed (class CreatedTradeEvent). Bemærk navngivningen; det er i princippet navnet på den kommando, der skabte denne begivenhed, men i datid. I datid, fordi først kommer kommandoen, som fører til skabelsen af begivenheden.

Andre fælles strukturer omfatter klasser til at beskrive positionen (class Position), handlen (class Trade) og siden i handlen (enum Side), dvs. køb eller salg.

Lad os gå videre til modulet tradeCreator.

Dette modul har et Rest-interface (class TradeController) til modtagelse af handler.
Fra den modtagne handel dannes kommandoen "opret handel" og sendes til axon-serveren.

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

Til behandling af kommandoen anvendes klassen class TradeAggregate.
For at Axon kan finde den, sætter vi annotationen @Aggregate.
Metoden til at behandlekommandoen ser således ud (med forkortelse):

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

Fra kommandoen dannes en begivenhed og sendes til serveren.
Kommandoen findes i klassen CreateTradeCommand.

Lad os nu se på det sidste modul tradeQueries.

Uddrag beskrives i pakken queries.
I dette modul er der også et Rest-interface.
public class TradeController.

For eksempel, lad os se på behandlingen af anmodningen: "vis alle handler".

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

Der laves en anmodning om uddrag, som sendes til serveren.

Til behandling af anmodningen om uddrag anvendes klassen TradesEventHandler.
I den findes en metode, markeret med annotationen.

   @QueryHandler
    public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)

Det er netop denne, der er ansvarlig for at trække data fra in-memory lageret.

Der opstår spørgsmålet, hvordan informationen opdateres i dette lager.

Vi begynder med, at det blot er et sæt ConcurrentHashMap, tilpasset specifikke uddrag.
Til deres opdatering anvendes metoden:

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

Han modtager begivenheden "handel oprettet" og opdaterer kortene.

Dette er hovedpunkterne i udviklingen af mikrotjenester.

Hvad kan siges om ulemperne ved Axon?

For det første er der en kompleksitet i infrastrukturen; vi har fået et enkelt fejlfunktionspunkt - Axon-serveren, al kommunikation går gennem den.

For det andet fremtræder et typisk problem i sådanne distribuerede systemer - tidsmæssig inkonsistens af data. I vores tilfælde kan der gå utilstedelig lang tid mellem modtagelse af en ny handel og opdatering af data til forespørgsler.

Hvad blev efterladt uset?

Der blev slet ikke nævnt noget om Event Sourcing og CQRS; hvad det er, og hvad det bruges til.
Uden at uddybe disse begreber kan nogle punkter have været uklare.

Måske kræver enkelte kodefragmenter også forklaring.

Det taler vi om på det åbne webinar den 21. september.

Komplet eksempel.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster