Vodič za Axon Framework

1. Pregled

U ovom ćemo članku pogledati Axon i kako nam pomaže u implementaciji aplikacija s CQRS (Razdvajanje odgovornosti za naredbeni upit) i Izvor događaja na umu.

Tijekom ovog vodiča koristit će se i Axon Framework i Axon Server. Prva će sadržavati našu implementaciju, a druga će biti naše namjensko rješenje za pohranu događaja i usmjeravanje poruka.

Uzorak aplikacije koji ćemo izraditi usredotočen je na Narudžba domena. Za ovo, iskoristit ćemo CQRS i građevinske blokove za izvor izvora koji nam pruža Axon.

Imajte na umu da puno zajedničkih koncepata dolazi upravo iz toga DDD, što je izvan dosega ovog trenutnog članka.

2. Ovisnosti Mavena

Stvorit ćemo aplikaciju Axon / Spring Boot. Stoga moramo dodati najnovije axon-spring-boot-starter ovisnost o našoj pom.xml, kao i akson-test ovisnost za ispitivanje:

 org.axonframework axon-spring-boot-starter 4.1.2 org.axonframework axon-test 4.1.2 test 

3. Axon poslužitelj

Upotrijebit ćemo Axon Server kao našu trgovinu događaja i naše posebno rješenje usmjeravanja naredbi, događaja i upita.

Kao trgovina događaja pruža nam idealne karakteristike potrebne za pohranu događaja. Ovaj članak daje pozadinu zašto je to poželjno.

Kao rješenje za usmjeravanje poruka daje nam mogućnost povezivanja nekoliko instanci, bez fokusiranja na konfiguriranje stvari poput RabbitMQ ili Kafka teme za dijeljenje i slanje poruka.

Axon Server možete preuzeti ovdje. Kako se radi o jednostavnoj JAR datoteci, za njezino pokretanje dovoljna je sljedeća operacija:

java -jar axonserver.jar

Ovo će pokrenuti jednu instancu Axon poslužitelja kojoj je moguće pristupiti putem lokalnihost: 8024. Krajnja točka pruža pregled povezanih aplikacija i poruka koje mogu obraditi, kao i mehanizam upita prema Store Storeu koji se nalazi na Axon poslužitelju.

Zadana konfiguracija Axon poslužitelja zajedno s axon-spring-boot-starter ovisnost će osigurati da se naša usluga narudžbe automatski poveže s njom.

4. API usluge naručivanja - naredbe

Postavit ćemo našu uslugu narudžbi imajući na umu CQRS. Stoga ćemo naglasiti poruke koje prolaze kroz našu aplikaciju.

Prvo ćemo definirati naredbe, što znači izraze namjere. Usluga narudžbe sposobna je za obradu tri različite vrste radnji:

  1. Davanje nove narudžbe
  2. Potvrđivanje narudžbe
  3. Dostava narudžbe

Naravno, postojat će tri naredbene poruke s kojima se naša domena može nositi - PlaceOrderCommand, ConfirmOrderCommand, i ShipOrderCommand:

javna klasa PlaceOrderCommand {@TargetAggregateIdentifier privatni konačni String orderId; privatni konačni String proizvod; // konstruktor, getteri, equals / hashCode i toString} javna klasa ConfirmOrderCommand {@TargetAggregateIdentifier privatni konačni String orderId; // konstruktor, getteri, equals / hashCode i toString} javna klasa ShipOrderCommand {@TargetAggregateIdentifier privatni konačni String orderId; // konstruktor, getteri, jednako / hashCode i toString}

The TargetAggregateIdentifier anotacija govori Axonu da je označeno polje ID datog agregata na koji treba ciljati naredbu. Kratko ćemo se dotaknuti agregata kasnije u ovom članku.

Također, imajte na umu da smo polja u naredbama označili kao konačni. Ovo je namjerno, kao to je najbolja praksa za bilo koji implementacija poruke biti nepromjenjiva.

5. API usluge narudžbi - događaji

Naš će skup upravljati naredbama, jer je zadužen za odlučivanje može li se narudžba poslati, potvrditi ili otpremiti.

Obavijestit će ostatak prijave o svojoj odluci objavljivanjem događaja. Imat ćemo tri vrste događaja - OrderPlacedEvent, OrderConfirmedEvent, i OrderShippedEvent:

javna klasa OrderPlacedEvent {private final String orderId; privatni konačni String proizvod; // zadani konstruktor, getteri, equals / hashCode i toString} javna klasa OrderConfirmedEvent {private final String orderId; // zadani konstruktor, getteri, equals / hashCode i toString} javna klasa OrderShippedEvent {private final String orderId; // zadani konstruktor, getteri, equals / hashCode i toString}

6. Model naredbe - agregat narudžbe

Sad kad smo modelirali naš osnovni API s obzirom na naredbe i događaje, možemo započeti s izradom naredbenog modela.

Kako se naša domena usredotočuje na bavljenje narudžbama, stvorit ćemo OrderAggregate kao središte našeg Zapovjednog modela.

6.1. Skupna klasa

Stoga, kreirajmo našu osnovnu agregatnu klasu:

@Aggregate javna klasa OrderAggregate {@AggregateIdentifier private String orderId; privatni logički nalogPotvrđeno; @CommandHandler javni OrderAggregate (naredba PlaceOrderCommand) {AggregateLifecycle.apply (new OrderPlacedEvent (command.getOrderId (), command.getProduct ())); } @EventSourcingHandler javna praznina na (događaj OrderPlacedEvent) {this.orderId = event.getOrderId (); orderConfirmed = false; } zaštićeni OrderAggregate () {}}

The Zbirno anotacija je napomena specifična za Axon Spring koja označava ovu klasu kao agregat. Obavijestit će okvir da se za to trebaju izraditi potrebni građevni blokovi CQRS i izvori događaja OrderAggregate.

Kako će agregat obrađivati ​​naredbe ciljane za određenu skupnu instancu, moramo navesti identifikator s AggregateIdentifier bilješka.

Naš agregat započet će svoj životni ciklus rukovanjem PlaceOrderCommand u OrderAggregate 'Konstruktor za upravljanje naredbama'. Da bismo okviru rekli da je zadana funkcija sposobna rukovati naredbama, dodati ćemo CommandHandler bilješka.

Pri rukovanju PlaceOrderCommand, obavijestit će ostatak aplikacije da je narudžba objavljena objavljivanjem OrderPlacedEvent. Za objavljivanje događaja iz agregata upotrijebit ćemo Primijeniti AggregateLifecycle (objekt ...).

Od ove točke, zapravo možemo početi uključivati ​​Izvor događaja kao pokretačku silu za ponovno stvaranje agregatne instance iz svog toka događaja.

Započinjemo s "skupnim događajem stvaranja", OrderPlacedEvent, koji se obrađuje u EventSourcingHandler anotirana funkcija za postavljanje orderId i narudžbaPotvrđena agregatno stanje Reda.

Također imajte na umu da Axon zahtijeva zadani konstruktor da bi mogao generirati agregat na temelju svojih događaja.

6.2. Zbirni obrađivači naredbi

Sada kada imamo svoj osnovni agregat, možemo početi implementirati preostale rukovatelje naredbama:

@CommandHandler javna ručka za poništavanje (naredba ConfirmOrderCommand) {primijeniti (novi OrderConfirmedEvent (orderId)); } @CommandHandler javna ručka za poništavanje (naredba ShipOrderCommand) {if (! OrderConfirmed) {throw new UnconfirmedOrderException (); } primijeniti (novi OrderShippedEvent (orderId)); } @EventSourcingHandler javna praznina na (događaj OrderConfirmedEvent) {orderConfirmed = true; }

Potpis naših voditelja naredbi i izvora događaja jednostavno navodi handle ({naredba}) i na ({the-event}) za održavanje sažetog formata.

Uz to, definirali smo da se Narudžba može poslati samo ako je potvrđena. Stoga ćemo baciti UnconfirmedOrderException ako to nije slučaj.

To ilustrira potrebu za OrderConfirmedEvent izvor obrade za ažuriranje narudžbaPotvrđena država da pravi za agregat Narudžbe.

7. Ispitivanje naredbenog modela

Prvo, moramo postaviti svoj test stvaranjem a FixtureConfiguration za OrderAggregate:

privatno učvršćenje FixtureConfiguration; @Before public void setUp () {fixture = new AggregateTestFixture (OrderAggregate.class); }

Prvi testni slučaj trebao bi obuhvatiti najjednostavniju situaciju. Kada agregat obrađuje PlaceOrderCommand, trebao bi proizvesti OrderPlacedEvent:

Niz orderId = UUID.randomUUID (). ToString (); String proizvod = "Deluxe stolica"; fixture.givenNoPriorActivity () .when (new PlaceOrderCommand (orderId, product)) .expectEvents (new OrderPlacedEvent (orderId, product));

Dalje, možemo testirati logiku odlučivanja da možemo poslati narudžbu samo ako je potvrđena. Zbog toga imamo dva scenarija - jedan u kojem očekujemo iznimku i jedan u kojem očekujemo OrderShippedEvent.

Pogledajmo prvi scenarij, gdje očekujemo iznimku:

Niz orderId = UUID.randomUUID (). ToString (); String proizvod = "Deluxe stolica"; fixture.given (new OrderPlacedEvent (orderId, product)) .when (new ShipOrderCommand (orderId)) .expectException (IllegalStateException.class); 

A sada drugi scenarij, gdje očekujemo OrderShippedEvent:

Niz orderId = UUID.randomUUID (). ToString (); String proizvod = "Deluxe stolica"; fixture.given (new OrderPlacedEvent (orderId, product), new OrderConfirmedEvent (orderId)) .when (new ShipOrderCommand (orderId)) .expectEvents (new OrderShippedEvent (orderId));

8. Model upita - Obrađivači događaja

Do sada smo uspostavili svoj osnovni API s naredbama i događajima, a na mjestu imamo i model naredbe naše usluge narudžbe CQRS, agregat narudžbe.

Sljedeći, možemo početi razmišljati o jednom od modela upita koji bi naša aplikacija trebala servisirati.

Jedan od tih modela je NaručeniProizvodi:

javna klasa OrderedProduct {private final String orderId; privatni konačni String proizvod; privatni OrderStatus orderStatus; javni OrderedProduct (String orderId, String product) {this.orderId = orderId; this.product = proizvod; orderStatus = StatusStana.PLACED; } javna praznina setOrderConfirmed () {this.orderStatus = OrderStatus.CONFIRMED; } javna praznina setOrderShipped () {this.orderStatus = OrderStatus.SHIPPED; } // getters, equals / hashCode i toString funkcije} javni nabroj OrderStatus {PLACED, CONFIRMED, SHIPPED}

Ažurirat ćemo ovaj model na temelju događaja koji se šire kroz naš sustav. Proljeće Servis bean za ažuriranje našeg modela učinit će trik:

@Service javna klasa OrderedProductsEventHandler {privatna konačna karta orderProducts = nova HashMap (); @EventHandler javna praznina na (događaj OrderPlacedEvent) {String orderId = event.getOrderId (); orderProducts.put (orderId, novi OrderedProduct (orderId, event.getProduct ())); } // Obrađivači događaja za OrderConfirmedEvent i OrderShippedEvent ...}

Kao što smo koristili axon-spring-boot-starter ovisnost o pokretanju naše Axon aplikacije, okvir će automatski skenirati sve grahove za postojeće funkcije rukovanja porukama.

Kao OrderedProductsEventHandler ima Rukovatelj događajima anotirane funkcije za spremanje datoteke NaručeniProizvod i ažurirati ga, ovaj će grah biti registriran u okviru kao klasa koja bi trebala primati događaje bez potrebe za bilo kakvom konfiguracijom s naše strane.

9. Model upita - rukovatelji upitima

Dalje, da bismo, na primjer, postavili upit za ovaj model kako bismo dohvatili sve naručene proizvode, prvo bismo trebali uvesti poruku upita u naš osnovni API:

javna klasa FindAllOrderedProductsQuery {}

Drugo, morat ćemo ažurirati OrderedProductsEventHandler kako bi se mogao nositi s FindAllOrderedProductsQuery:

@QueryHandler javni popis za rukovanje (upit FindAllOrderedProductsQuery) {return new ArrayList (orderProducts.values ​​()); }

The QueryHandler anotirana funkcija obrađivat će FindAllOrderedProductsQuery i postavljeno je da vraća a Popis bez obzira, slično bilo kojem upitu "pronađi sve".

10. Spajanje svega

Dotjerali smo naš osnovni API s naredbama, događajima i upitima te postavili svoj model Command and Query postavljanjem OrderAggregate i NaručeniProizvodi model.

Sljedeće je povezivanje slobodnih krajeva naše infrastrukture. Kao što koristimo axon-spring-boot-starter, ovo automatski postavlja puno potrebne konfiguracije.

Prvi, budući da želimo iskoristiti izvor događaja za naš agregat, trebat će nam EventStore. Axon Server koji smo pokrenuli u trećem koraku ispunit će ovu rupu.

Drugo, trebamo mehanizam za pohranu naših NaručeniProizvod model upita. Za ovaj primjer možemo dodati h2 kao baza podataka u memoriji i spring-boot-starter-data-jpa za jednostavnost upotrebe:

 org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 vrijeme izvođenja 

10.1. Postavljanje REST krajnje točke

Dalje, moramo imati pristup našoj aplikaciji, za koju ćemo iskoristiti REST krajnju točku dodavanjem proljeće-boot-starter-web ovisnost:

 org.springframework.boot spring-boot-starter-web 

Iz naše krajnje točke REST možemo započeti s otpremanjem naredbi i upita:

@RestController javna klasa OrderRestEndpoint {private final CommandGateway commandGateway; privatni konačni QueryGateway queryGateway; // Konstruktor automatskog povezivanja i POST / GET krajnje točke}

The CommandGateway koristi se kao mehanizam za slanje naših naredbenih poruka, a QueryGatewayzauzvrat za slanje poruka upita. Pristupnici pružaju jednostavniji, jednostavniji API u odnosu na CommandBus i QueryBus s kojima se povezuju.

Odsad nadalje, naše OrderRestEndpoint treba imati POST krajnju točku za postavljanje, potvrđivanje i slanje narudžbe:

@PostMapping ("/ order-ship") javna void shipOrder () {String orderId = UUID.randomUUID (). ToString (); commandGateway.send (novi PlaceOrderCommand (orderId, "Deluxe stolica")); commandGateway.send (novi ConfirmOrderCommand (orderId)); commandGateway.send (novi ShipOrderCommand (orderId)); }

Ovo zaokružuje naredbenu stranu naše aplikacije CQRS.

Sada je preostala samo GET krajnja točka za upit svih NaručeniProizvodi:

@GetMapping ("/ all-order") javni popis findAllOrderedProducts () {return queryGateway.query (new FindAllOrderedProductsQuery (), ResponseTypes.multipleInsistanceOf (OrderedProduct.class)). Join (); }

U GET krajnjoj točki koristimo QueryGateway za slanje upita od točke do točke. Pritom stvaramo zadani FindAllOrderedProductsQuery, ali također moramo navesti i očekivani tip povrata.

Kao što očekujemo višestruko NaručeniProizvod slučajeve koje treba vratiti, koristimo statički ResponseTypes # multipleInsistanceOf (Class) funkcija. Ovim smo osigurali osnovni ulaz u upitnu stranu naše usluge narudžbe.

Završili smo postavljanje, tako da sada možemo poslati neke naredbe i upite putem našeg REST kontrolera nakon što pokrenemo Primjena narudžbe.

OBJAVLJIVANJE do krajnje točke / narudžba broda instancirat će OrderAggregate koji će objaviti događaje, a koji će zauzvrat spasiti / ažurirati naš NaručeniProizvodi. DOBITI iz / sve narudžbe krajnja točka će objaviti poruku upita kojom će se baviti OrderedProductsEventHandler, koji će vratiti sve postojeće NaručeniProizvodi.

11. Zaključak

U ovom smo članku predstavili Axon Framework kao moćnu osnovu za izgradnju aplikacije koja iskorištava blagodati CQRS-a i izvora događaja.

Implementirali smo jednostavnu uslugu Naručivanje pomoću okvira kako bismo pokazali kako takva aplikacija treba biti strukturirana u praksi.

Na kraju, Axon Server predstavljao se kao naša trgovina događaja i mehanizam za usmjeravanje poruka.

Provedbu svih ovih primjera i isječaka koda možete pronaći na GitHubu.

Za sva dodatna pitanja koja imate, provjerite i korisničku grupu Axon Framework.


$config[zx-auto] not found$config[zx-overlay] not found