Mikroshërbimet me komunikim përmes Axon

Në këtë tutorial të thjeshtë do të krijojmë disa mikroshërbime në Spring Boot dhe do të organizojmë ndërveprimin mes tyre përmes kornizës Axon.

Mikroshërbimet me komunikim përmes Axon


Supozoni se kemi një detyrë të tillë.

Ka një burim transaksionesh në tregun financiar. Ky burim na dërgon transaksione përmes një ndërfaqeje Rest.

Na nevojitet të marrim këto transaksione, t'i ruajmë në një bazë të dhënash dhe të krijojmë një magazinë in-memory të leverdishme.

Kjo magazinë duhet të kryejë funksionet e mëposhtme:

  • të kthejë një listë tregtare;
  • të kthejë pozitat e plota, dmth. tabela "instrument" – "sasia aktuelle e letrave të çmimit";
  • të kthejë pozitat për një instrument të caktuar.

Si do ta qasnim zgjidhjen e kësaj detyre?

Sipas besimeve të modës mikroshërbimore, na nevojitet të ndajmë detyrën në mikroshërbime përbërëse:

  • marrja e transaksioneve përmes Rest-it;
  • ruajtja e transaksioneve në bazën e të dhënave;
  • magazina in-memory për paraqitjen e të dhënave mbi pozitat.

Le të krijojmë në kuadër të këtij tutoriali shërbimin e parë dhe të tretë, ndërsa të dytin ta lëmë për pjesën e dytë (në komentet nëse kjo është interesante).

Pra, kemi dy mikroshërbime.

I pari merr të dhënat nga jashtë.

I dyti i përpunon këto të dhëna dhe përgjigjet në kërkesat që vijnë.

Natyrisht, duam të arrijmë shkallëzim horizontal, përditësim pa ndërprerje dhe përfitime të tjera të mikroshërbimeve.

Cila është, një detyrë shumë e komplikuar, që na pret?

Në fakt, ka shumë, por tani le të flasim se si do të kalojnë të dhënat mes këtyre mikroshërbimeve. Ndër to gjithashtu mund të bëjmë Rest, mund të vendosim ndonjë radhë, mund të inventojmë shumë gjëra me avantazhet dhe disavantazhet e tyre.

Le të shqyrtojmë një nga qasjet e mundshme – ndërveprimin asinkron përmes kornizës Axon.

Cilat janë përfitimet e këtij zgjidhjeje?

Së pari, ndërveprimi asinkron rrit fleksibilitetin (po, ka këtu edhe një disavantazh, por ne për momentin jemi vetëm për përfitimet).

Së dyti, direkt nga kutia ne marrim Event Sourcing dhe CQRS.
Së treti, Axon ofron një infrastrukturë gati, dhe na nevojitet të përqendrohemi vetëm në zhvillimin e logjikës biznesore.

Le të fillojmë.

Projektin do ta kemi në gradle. Do të ketë tri module:

  • common. modul me struktura të përbashkëta të të dhënave (ne nuk e duam kopjimin);
  • tradeCreator. modul me mikroshërbimin për pranimin e transaksioneve përmes Rest;
  • tradeQueries. modul me mikroshërbimin për përfaqësimin e pozitat.

Do ta marrim Spring Boot si bazë dhe do të lidhim starterin e Axon.

Axon punon shkëlqyer edhe pa Spring, por do t'i përdorim ato së bashku.

Këtu duhet të ndalemi dhe të themi disa fjalë rreth Axon.

Kjo është një sistem klient-server. Ka një server – ky është një aplikacion i veçantë, do ta lansojmë atë në docker.

Dhe ka klientë, të cilët integrohen në mikroshërbime.
Kështu që kemi një pamje të tillë. Fillimisht, lansojmë serverin Axon (në docker), pastaj mikroshërbimet tona.

Kur startohen, mikroshërbimet kërkojnë serverin dhe fillojnë të ndërveprojnë me të. Ndërveprimi mund të ndahet në dy lloje: teknik dhe biznesor.

Technical – është shkëmbimi i mesazheve «unë jam gjallë» (këto mesazhe mund të shihen në modalitetin e regjistrimit debug).

Biznesor – është shkëmbimi i mesazheve si «transaksion i ri».

Një veçori e rëndësishme, pas lansimit mikroshërbimi mund të pyesë serverin Axon «çfarë ndodhi» dhe serveri i dërgon mikroshërbimit ngjarjet e akumuluara. Kështu, mikroshërbimi mund të rihapet relativisht në siguri pa humbje të dhënash.
Me këtë skemë shkëmbimi mund të lancojmë shumë instanca të mikroshërbimeve shumë lehtë,
ndërsa në hoste të ndryshme.

Po, një instancë e serverit Axon – nuk është e besueshme, por kështu është për momentin.

Ne punojmë në paradigmën e Event Sourcing dhe CQRS. Kjo do të thotë se ne duhet të kemi «komanda», «ngjarje» dhe «selektime».

Do të kemi një komandë: «krijo transaksion», një ngjarje «transaksioni i krijuar» dhe tri selektime: «trego të gjitha transaksionet», «trego pozita», «trego pozita sipas instrumentit».

Skema e punës rezulton të jetë kështu:

  1. Mikroshërbimi tradeCreator pranon një transaksion përmes Rest.
  2. Mikroshërbimi tradeCreator krijon një komandë «krijo transaksion» dhe e dërgon atë në serverin Axon.
  3. Serveri Axon pranon komandën dhe e dërgon atë te marrësi i interesuar, në rastin tonë është mikroshërbimi tradeCreator.
  4. Mikroshërbimi tradeCreator merr komandën, formon ngjarjen «transaksioni i krijuar» dhe e dërgon atë në serverin Axon.
  5. Serveri Axon pranon ngjarjen dhe e dërgon te abonnentët e interesuar.
  6. Aktualisht kemi vetëm një marrës të interesuar – është mikroshërbimi tradeQueries.
  7. Mikroshërbimi tradeQueries merr ngjarjen dhe përditëson të dhënat e brendshme.

(Është e rëndësishme që në momentin e formimit të ngjarjes, Mikroshërbimi tradeQueries mund të mos jetë i aksesueshëm, por sapo të nisë, do të marrë menjëherë ngjarjen).

Po, serveri axon qëndron në qendër të komunikimeve, të gjitha mesazhet kalojnë përmes tij.

Le të kalojmë në kodim.

Për të mos e mbushur postimin me kod, më poshtë do të paraqes vetëm fragmente, lidhja për shembullin e plotë do të jetë më poshtë.

Të fillojmë me modul të përbashkët common.

Në të, pjesët e përbashkëta janë ngjarja (class CreatedTradeEvent). Vini re emërtimin, në thelb, kjo është emri i komandës që e gjeneroi këtë ngjarje, por në kohën e kaluar. Në kohën e kaluar, sepse së pari shfaqet komanda, e cila çon në krijimin e ngjarjes.

Strukturat e tjera të përbashkëta përfshijnë klasat për përshkrimin e pozicioneve (class Position), tregtisë (class Trade) dhe anën e tregtisë (enum Side), që do të thotë, blerje ose shitje.

Të kalojmë te moduli tradeCreator.

Ky modul ka një interfesë Rest (class TradeController) për të pranuar tregti.
Nga tregtia e marrë krijohet komanda "krijo tregti" dhe dërgohet në serverin 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());
    }

Për përpunimin e komandës përdoret klasa class TradeAggregate.
Për ta gjetur Axon, vendosim anotacionin @Aggregate.
Metoda për përpunimin e komandës duket kështu (me shkurtim):

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

Nga komanda krijohet një ngjarje dhe dërgohet në server.
Komanda gjendet në klasën CreateTradeCommand.

Tani të shikojmë modulin e fundit tradeQueries.

Selekternet përshkruhen në paketën queries.
Në këtë modul ka gjithashtu një interfesë Rest.
public class TradeController.

Si shembull, le të shohim përpunimin e kërkesës: "trego të gjitha tregtitë".

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

Krijohet një kërkesë për selektim dhe dërgohet në server.

Për përpunimin e kërkesës për selektim përdoret klasa TradesEventHandler.
Në të ka një metodë e cila është e shënuar me anotacionin

   @QueryHandler
    public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)

Ai është ai që është përgjegjës për selektimin e të dhënave nga depoja in-memory.

Cila është pyetja se si përditësohet informata në këtë depo.

Të fillojmë me atë që kjo është thjesht një grup ConcurrentHashMap, të dizajnuara për selektime të caktuara.
Për përditësimin e tyre aplikohet metoda:

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

Ai merr të dhënën "në krijimin e tregtisë" dhe azhurnon hartat.

Këto janë piketat kryesore të zhvillimit të mikroshërbimeve.

Çfarë mund të thuhet për mangësitë e Axon?

Së pari, kjo ndërlikon infrastrukturën, duke krijuar një pikë dështimi – serverin Axon, të gjitha komunikimet kalojnë përmes tij.

Së dyti, shfaqet shumë qartë mangësia e sistemeve të tilla të shpërndara – kohëzgjatja e pamjaftueshme e informacionit. Në rastin tonë, mes marrjes së një tregtie të re dhe azhurnimit të të dhënave për zgjedhjet mund të kalojë një kohë e papranueshme.

Çfarë mbeti jashtë skenës?

Nuk u tha fare për Event Sourcing dhe CQRS, çfarë janë dhe për çfarë nevojiten.
Pa sqarimin e këtyre koncepteve, disa pika mund të kenë mbetur të paqartë.

Mund të jetë që fragmente të caktuara të kodit kërkojnë gjithashtu sqarime.

Për këtë do të flasim në webinarin e hapur më 21 Shtator.

Shembulli i plotë.

Burimi: habr.com

Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS 🔥 Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS - ProHoster