V tem preprostem priročniku bomo ustvarili nekaj mikroservices na Spring Boot in organizirali njihovo komunikacijo preko okvirja Axon.

Recimo, da imamo takšno nalogo.
Obstaja vir poslov na borzi. Ta vir nam posreduje posle preko Rest vmesnika.
Te posle moramo prejeti, shraniti v bazo podatkov in ustvariti priročno in-memory shrambo.
Ta shramba mora opravljati naslednje funkcije:
- vrniti seznam trgov;
- vrniti celotno pozicijo, torej tabelo «instrument» — «trenutno število papirjev»;
- vrniti pozicijo za določen instrument.
Kako se bomo lotili rešitve te naloge?
Po zapovedih mikroservices mode moramo nalogo razdeliti na sestavne mikroservices:
- prejemanje posla preko Rest-a;
- shranjevanje posla v bazo podatkov;
- in-memory shranjevanje za predstavitev podatkov o poziciji.
V okviru tega priročnika bomo ustvarili prvi in tretji servis, drugi pa bomo pustili za drugo del (napišite v komentarjih, če vas to zanima).
Torej, imamo dva mikroservices.
Prvi prejema podatke od zunaj.
Drugi te podatke obdeluje in odgovarja na prihajajoče poizvedbe.
Seveda želimo doseči horizontalno skalabilnost, brez prekinitvenih posodobitev in druge prednosti mikroservices.
Kakšna, dokaj zahtevna, naloga je pred nami?
Dejansko je teh zelo veliko, a zdaj se pogovorimo o tem, kako bodo podatki potovali med temi mikroservices. Med njimi je mogoče tudi vzpostaviti Rest, postaviti neko vrsto vrste, in obstaja veliko zamisli s svojimi prednostmi in slabostmi.
Oglejmo si enega izmed možnih pristopov – asinhrono sodelovanje preko okvirja Axon.
Kateri so plusi te rešitve?
Najprej, asinhrono sodelovanje povečuje prilagodljivost (da, tukaj je tudi slabost, a zaenkrat govorimo samo o plusih).
Drugič, takoj dobimo Event Sourcing in CQRS.
Tretjič, Axon nudi že pripravljeno infrastrukturo, in osredotočiti se moramo le na razvoj poslovne logike.
Pričnimo.
Projekt bo na gradle. Vsebuje tri module:
- common. modul z skupnimi struktura podatkov (ne maramo kopipaste);
- tradeCreator. modul z mikroservisom za prejemanje poslov preko Rest;
- tradeQueries. modul z mikroservisom za prikaz pozicije.
Vzamemo Spring Boot kot osnovo in povežemo starter Axona.
Axon dela odlično deluje tudi brez Springa, vendar ju bomo uporabljali skupaj.
Tu se moramo ustaviti in povedati nekaj besed o Axonu.
To je klient-server sistem. Imamo strežnik – to je ločena aplikacija, ki jo bomo zagnali v Dockerju.
In imamo odjemalce, ki so vgrajeni v mikroservise.
Izkazalo se bo takole. Najprej se zažene Axon-strežnik (v Dockerju), nato naši mikroservisi.
Ob zagonu mikroservisi iščejo strežnik in začnejo z njim sodelovati. Sodelovanje lahko delimo na dva tipi: tehnično in poslovno.
Tehnično – to je izmenjava sporočil 'sem živ' (takšna sporočila lahko vidite v načinu beleženja napak).
Poslovno – to so sporočila, kot je 'nov posel'.
Pomembna lastnost je, da lahko po zagonu mikroservis vpraša Axon-strežnik 'kaj se je zgodilo' in strežnik prenese microservisu nabrana dogodke. Tako je mikroservis relativno varno mogoče ponovno zagnati brez izgube podatkov.
Pri takšnem načinu izmenjave lahko zelo enostavno zaženemo veliko instanc mikroservisov,
natančneje na različnih gostiteljih.
Ja, ena instanca Axon-strežnika – to ni zanesljivo, a trenutno je tako.
Delamo v paradigmah Event Sourcing in CQRS. To pomeni, da moramo imeti 'ukaze', 'dogodke' in 'povzetke'.
Imeli bomo en ukaz: 'ustvari posel', en dogodek 'posel ustvarjen' in tri povzetke: 'prikaži vse posle', 'prikaži pozicijo', 'prikaži pozicijo po orodju'.
Tako deluje shema:
- Mikroservis tradeCreator sprejme posel prek Rest.
- Mikroservis tradeCreator ustvari ukaz 'ustvari posel' in ga pošlje Axon-strežniku.
- Axon-strežnik sprejme ukaz in ga posreduje zainteresiranemu prejemniku, v našem primeru mikroservisu tradeCreator.
- Mikroservis tradeCreator prejme ukaz, oblikuje dogodek 'posel ustvarjen' in ga pošlje Axon-strežniku.
- Axon-strežnik sprejme dogodek in ga posreduje zainteresiranim naročnikom.
- Trenutno imamo le enega zainteresiranega prejemnika – to je mikroservis tradeQueries.
- Mikroservis tradeQueries prejme dogodek in posodobi notranje podatke.
(Pomembno je, da v trenutku oblikovanja dogodka mikroservis tradeQueries morda ni na voljo, a takoj, ko se zažene, prejme dogodek).
Ja, axon-strežnik je v središču komunikacij, vse sporočila gredo preko njega.
Poglejmo k kodiranju.
Da ne bi obremenjevali objave s kodo, spodaj bom navedel samo bloke, povezava do celotnega primera pa bo spodaj.
Začeli bomo s skupnim modulom common.
V njem so skupni deli – dogodek (class CreatedTradeEvent). Bodite pozorni na ime, to je v bistvu ime ukaza, ki je povzročil ta dogodek, vendar v pretekliku. V pretekliku, ker najprej pride ukaz, ki privede do ustvarjanja dogodka.
K drugim skupnim strukturam spadajo razredi za opis pozicije (class Position), transakcije (class Trade) in stran transakcije (enum Side), tj. nakup ali prodaja.
Preidimo na modul tradeCreator.
Ta modul ima Rest-vmesnik (class TradeController) za sprejemanje transakcij.
Iz prejetih transakcij se oblikuje ukaz 'ustvari transakcijo' in pošlje na axon-strežnik.
@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());
}
Za obdelavo ukaza se uporablja razred class TradeAggregate.
Da ga Axon najde, dodamo anotacijo @Aggregate.
Metoda za obdelavo ukaza izgleda takole (skrajšano):
@CommandHandler
public TradeAggregate(CreateTradeCommand command) {
log.info("ukaz: {}", command);
var event = CreatedTradeEvent.builder()
.tradeId(command.tradeId())
....
.build();
AggregateLifecycle.apply(event);
}
Iz ukaza se oblikuje dogodek in pošlje na strežnik.
Ukaz se nahaja v razredu CreateTradeCommand.
Poglejmo nazadnje modul tradeQueries.
Poizvedbe se opisujejo v paketu queries.
Tudi v tem modulu je Rest-vmesnik
public class TradeController.
Za primer poglejmo obdelavo poizvedbe: 'prikaži vse transakcije'.
@GetMapping("/trade/all")
public List findAllTrades() {
return queryGateway.query(new FindAllTradesQuery(),
ResponseTypes.multipleInstancesOf(Trade.class)).join();
}
Ustvari se poizvedba za izbiro in pošlje na strežnik.
Za obdelavo poizvedbe se uporablja razred TradesEventHandler.
V njem je metoda, označena z anotacijo
@QueryHandler
public List handleFindCurrentPositionQuery(FindCurrentPositionQuery query)
On je tisti, ki je odgovoren za izbiro podatkov iz in-memory shrambe.
Postavlja se vprašanje, kako se v tej shrambi posodablja informacija.
Začnimo s tem, da je to preprosto niz ConcurrentHashMap, prilagojenih za posebne poizvedbe.
Za njihovo posodabljanje se uporablja 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);
}On obdeluje dogodek „sklenjena trgovina“ in posodablja mape.
To so ključne točke razvoj mikroservisov.
Kaj lahko rečemo o pomanjkljivostih Axon-a?
Prvič, to zapleta infrastrukturo, nastane točka napake – Axon strežnik, vse komunikacije potekajo prek njega.
Drugič, izrazito se pokaže pomanjkljivost takšnih razdeljenih sistemov – začasna neskladnost podatkov. V našem primeru lahko med pridobitvijo nove trgovine in posodobitvijo podatkov za poizvedbe mine nedopustno veliko časa.
Kaj je ostalo v ozadju?
Popolnoma nič ni rečeno o Event Sourcing in CQRS, kaj sta to in zakaj sta potrebna.
Brez razjasnitve teh pojmov nekateri vidiki morda niso bili jasni.
Morda tudi nekateri delčki kode zahtevajo razlago.
O tem bomo govorili na 21. septembra.
.
Izvor: habr.com
