Sprievodca rámcom Axon

1. Prehľad

V tomto článku sa pozrieme na Axon a ako nám pomáha pri implementácii aplikácií CQRS (Segregácia zodpovednosti za príkazové dotazy) a Sourcing udalostí na mysli.

Počas tejto príručky sa bude používať Axon Framework aj Axon Server. Prvý bude obsahovať našu implementáciu a druhý bude naše špecializované riešenie Event Store a Message Routing.

Vzorová aplikácia, ktorú budeme budovať, sa zameriava na objednať doména. Pre to, využijeme stavebné bloky CQRS a Event Sourcing, ktoré nám poskytuje Axon.

Upozorňujeme, že veľa zdieľaných konceptov vychádza priamo z DDD, čo je nad rámec tohto súčasného článku.

2. Maven závislosti

Vytvoríme aplikáciu Axon / Spring Boot. Preto musíme pridať najnovšie štartér axon-pružina-boot-štartér závislosť na našom pom.xml, ako aj axon-test závislosť na testovaní:

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

3. Axon Server

Server Axon použijeme ako náš obchod s udalosťami a naše riešenie riešenia smerovania príkazov, udalostí a dotazov.

Ako obchod s udalosťami nám poskytuje ideálne vlastnosti požadované pri ukladaní udalostí. Tento článok poskytuje základné informácie o tom, prečo je to žiaduce.

Ako riešenie smerovania správ nám dáva možnosť spojiť niekoľko inštancií bez toho, aby sme sa zamerali na konfiguráciu vecí ako RabbitMQ alebo Kafka na zdieľanie a odosielanie správ.

Axon Server si môžete stiahnuť tu. Pretože ide o jednoduchý súbor JAR, na jeho spustenie stačí nasledujúca operácia:

java -jar axonserver.jar

Spustí sa jedna inštancia servera Axon, ktorá je prístupná cez localhost: 8024. Koncový bod poskytuje prehľad o pripojených aplikáciách a správach, ktoré dokážu spracovať, ako aj mechanizmus dotazovania smerom k úložisku udalostí obsiahnutému v serveri Axon Server.

Predvolená konfigurácia servera Axon spolu s štartér axon-pružina-boot-štartér závislosť zabezpečí, že sa k nej naša objednávková služba pripojí automaticky.

4. Order Service API - Príkazy

Službu Objednávka nastavíme s ohľadom na CQRS. Preto zdôrazníme správy, ktoré prechádzajú našou aplikáciou.

Najskôr definujeme Príkazy, čo znamená vyjadrenie zámeru. Služba Objednávka je schopná spracovať tri rôzne typy akcií:

  1. Zadanie novej objednávky
  2. Potvrdenie objednávky
  3. Zaslanie objednávky

Prirodzene, budú existovať tri príkazové správy, s ktorými si naša doména dokáže poradiť - PlaceOrderCommand, ConfirmOrderCommanda ShipOrderCommand:

verejná trieda PlaceOrderCommand {@TargetAggregateIdentifier private final String orderId; súkromný finálny produkt String; // konštruktor, getre, equals / hashCode a toString} verejná trieda ConfirmOrderCommand {@TargetAggregateIdentifier private final String orderId; // konštruktor, getre, rovná sa / hashCode a toString} verejná trieda ShipOrderCommand {@TargetAggregateIdentifier private final String orderId; // konštruktor, getre, rovná sa / hashCode a toString}

The TargetAggregateIdentifier anotácia hovorí Axonu, že anotované pole je ID daného agregátu, na ktorý by mal byť príkaz zameraný. Ďalej sa v tomto článku stručne dotkneme agregátov.

Upozorňujeme tiež, že sme označili polia v príkazoch ako konečné. Toto je zámerné, ako je to najlepší postup pre akýkoľvek implementácia správy bola nemenná.

5. Order Service API - Udalosti

Náš agregát bude vybavovať príkazy, pretože má na starosti rozhodovanie o tom, či je možné zadať, potvrdiť alebo odoslať objednávku.

Zvyšok žiadosti oznámi svojmu rozhodnutiu zverejnením udalosti. Budeme mať tri typy udalostí - OrderPlacedEvent, OrderConfirmedEventa OrderShippedEvent:

verejná trieda OrderPlacedEvent {private final String orderId; súkromný finálny produkt String; // predvolený konštruktor, getre, rovná sa / hashCode a toString} verejná trieda OrderConfirmedEvent {private final String orderId; // predvolený konštruktor, getre, equals / hashCode a toString} verejná trieda OrderShippedEvent {private final String orderId; // predvolený konštruktor, getre, rovná sa / hashCode a toString}

6. Model velenia - agregát objednávok

Teraz, keď sme modelovali naše základné API s ohľadom na príkazy a udalosti, môžeme začať vytvárať príkazový model.

Pretože sa naša doména zameriava na vybavovanie objednávok, vytvoríme OrderAggregate ako stred nášho modelu velenia.

6.1. Agregovaná trieda

Vytvorme teda našu základnú agregovanú triedu:

@Aggregate verejná trieda OrderAggregate {@AggregateIdentifier private String orderId; súkromná logická objednávkaPotvrdené; @CommandHandler public OrderAggregate (príkaz PlaceOrderCommand) {AggregateLifecycle.apply (nový OrderPlacedEvent (command.getOrderId (), command.getProduct ())); } @EventSourcingHandler public void on (OrderPlacedEvent event) {this.orderId = event.getOrderId (); orderConfirmed = false; } chránené OrderAggregate () {}}

The Agregát anotácia je anotácia špecifická pre Axon Spring, ktorá označuje túto triedu ako agregát. Bude informovať rámec, že ​​na to je potrebné vytvoriť inštanciu požadovaných špecifických stavebných blokov špecifických pre CQRS a Event Sourcing OrderAggregate.

Pretože agregát bude spracúvať príkazy, ktoré sú zamerané na konkrétnu inštanciu agregátu, musíme špecifikovať identifikátor pomocou AggregateIdentifier anotácia.

Náš agregát začne svoj životný cyklus po manipulácii s PlaceOrderCommand v OrderAggregate „Konštruktor spracovania príkazov“. Aby sme rámcu povedali, že daná funkcia je schopná spracovávať príkazy, pridáme znak CommandHandler anotácia.

Pri manipulácii s PlaceOrderCommand, oznámi ostatným aplikáciám, že bola zadaná objednávka, zverejnením OrderPlacedEvent. Na zverejnenie udalosti v rámci agregátu použijeme Použiť AggregateLifecycle # (objekt…).

Od tohto bodu môžeme skutočne začať začleňovať Event Sourcing ako hnaciu silu na opätovné vytvorenie agregovanej inštancie z jej prúdu udalostí.

Začíname to „udalosťou vytvorenia agregátu“, OrderPlacedEvent, ktorý je spracovaný v EventSourcingHandler komentovaná funkcia na nastavenie Číslo objednávky a objednávkaPotvrdené stav agregátu objednávky.

Upozorňujeme tiež, že aby bolo možné získať agregát na základe jeho udalostí, Axon vyžaduje predvolený konštruktor.

6.2. Súhrnní obsluhovatelia príkazov

Teraz, keď máme náš základný agregát, môžeme začať implementovať zostávajúce obslužné rutiny príkazov:

@CommandHandler public void handle (príkaz ConfirmOrderCommand) {apply (nový OrderConfirmedEvent (orderId)); } @CommandHandler public void handle (príkaz ShipOrderCommand) {if (! OrderConfirmed) {hodiť novú UnconfirmedOrderException (); } apply (new OrderShippedEvent (orderId)); } @EventSourcingHandler public void on (udalosť OrderConfirmedEvent) {orderConfirmed = true; }

Podpis našich spracovateľov príkazov a zdrojov udalostí jednoducho uvádza handle ({the-command}) a na ({the-event}) zachovať výstižný formát.

Ďalej sme definovali, že objednávku je možné odoslať, iba ak bude potvrdená. Takto hodíme UnconfirmedOrderException ak to tak nie je.

Toto je príkladom potreby OrderConfirmedEvent obslužný program zdrojov na aktualizáciu objednávkaPotvrdené štát do pravda pre agregát objednávky.

7. Testovanie modelu velenia

Najprv musíme nastaviť náš test vytvorením a Konfigurácia Fixture pre OrderAggregate:

súkromné ​​zariadenie | Konfiguračné zariadenie; @Before public void setUp () {fixture = new AggregateTestFixture (OrderAggregate.class); }

Prvý testovací prípad by sa mal týkať najjednoduchšej situácie. Keď agregát spracováva PlaceOrderCommand, mala by produkovať OrderPlacedEvent:

Reťazec orderId = UUID.randomUUID (). ToString (); Produkt zo strún = "Deluxe Chair"; fixture.givenNoPriorActivity () .when (new PlaceOrderCommand (orderId, product)) .expectEvents (new OrderPlacedEvent (orderId, product));

Ďalej môžeme otestovať rozhodovaciu logiku možnosti odoslať objednávku iba v prípade, že bude potvrdená. Z tohto dôvodu máme dva scenáre - jeden, kde očakávame výnimku, a druhý, kde očakávame výnimku OrderShippedEvent.

Pozrime sa na prvý scenár, kde očakávame výnimku:

Reťazec orderId = UUID.randomUUID (). ToString (); Produkt zo strún = "Deluxe Chair"; fixture.given (new OrderPlacedEvent (orderId, product)) .when (new ShipOrderCommand (orderId)) .expectException (IllegalStateException.class); 

A teraz druhý scenár, kde očakávame OrderShippedEvent:

Reťazec orderId = UUID.randomUUID (). ToString (); Produkt zo strún = "Deluxe Chair"; fixture.given (new OrderPlacedEvent (orderId, product), new OrderConfirmedEvent (orderId)) .when (new ShipOrderCommand (orderId)) .expectEvents (new OrderShippedEvent (orderId));

8. Dotazový model - obslužné rutiny udalostí

Zatiaľ sme založili naše základné API s príkazmi a udalosťami a máme zavedený model príkazov našej služby objednávok CQRS, agregát objednávok.

Ďalšie, môžeme začať myslieť na jeden z dotazovacích modelov, ktoré by mala naša aplikácia poskytovať.

Jedným z týchto modelov je Objednané produkty:

verejná trieda OrderedProduct {private final String orderId; súkromný finálny produkt String; private OrderStatus orderStatus; public OrderedProduct (String orderId, String product) {this.orderId = orderId; this.product = produkt; orderStatus = OrderStatus.PLACED; } public void setOrderConfirmed () {this.orderStatus = OrderStatus.CONFIRMED; } public void setOrderShipped () {this.orderStatus = OrderStatus.SHIPPED; } // getters, equals / hashCode and toString functions} public enum OrderStatus {PLACED, CONFIRMED, SHIPPED}

Aktualizujeme tento model na základe udalostí šíriacich sa prostredníctvom nášho systému. Jar Služby bean aktualizovať náš model bude stačiť:

@Service verejná trieda OrderedProductsEventHandler {súkromná konečná mapa orderedProducts = nový HashMap (); @EventHandler public void on (udalosť OrderPlacedEvent) {String orderId = event.getOrderId (); orderedProducts.put (orderId, new OrderedProduct (orderId, event.getProduct ())); } // Obslužné rutiny udalostí pre OrderConfirmedEvent a OrderShippedEvent ...}

Ako sme použili štartér axon-pružina-boot závislosť na spustení našej aplikácie Axon, rámec automaticky skontroluje všetky fazule na existujúce funkcie správy správ.

Ako OrderedProductsEventHandlerEventHandler anotované funkcie na uloženie Objednaný produkt a aktualizovať ho, táto fazuľa bude rámcom zaregistrovaná ako trieda, ktorá by mala prijímať udalosti bez toho, aby sme z našej strany vyžadovali akúkoľvek konfiguráciu.

9. The Query Model - Query Handlers

Ďalej, aby sme dotazovali tento model, napríklad na získanie všetkých objednaných produktov, mali by sme najskôr predstaviť správu dotazu do nášho základného API:

verejná trieda FindAllOrderedProductsQuery {}

Po druhé, budeme musieť aktualizovať OrderedProductsEventHandler byť schopný zvládnuť FindAllOrderedProductsQuery:

@ Verzia verejného zoznamu @QueryHandler (dopyt FindAllOrderedProductsQuery) {return new ArrayList (orderedProducts.values ​​()); }

The QueryHandler komentovaná funkcia zvládne FindAllOrderedProductsQuery a je nastavený na návrat a Zoznam bez ohľadu na to, podobne ako pri každom dotaze „nájsť všetko“.

10. Dať všetko dokopy

Rozvinuli sme naše základné API pomocou príkazov, udalostí a dotazov a nastavili sme náš model príkazov a dotazov tak, že máme OrderAggregate a Objednané produkty Model.

Ďalej je potrebné zviazať voľné konce našej infraštruktúry. Ako používame štartér axon-pružina-boot, nastaví sa automaticky veľa požadovanej konfigurácie.

Najprv, pretože chceme využiť Event Sourcing pre náš agregát, budeme potrebovať EventStore. Túto dieru vyplní server Axon, ktorý sme spustili v treťom kroku.

Po druhé, potrebujeme mechanizmus na ukladanie našich informácií Objednaný produkt dotazový model. Pre tento príklad môžeme pridať h2 ako databáza v pamäti a spring-boot-starter-data-jpa pre jednoduché použitie:

 org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime 

10.1. Nastavenie koncového bodu REST

Ďalej musíme mať prístup k našej aplikácii, pre ktorú budeme využívať koncový bod REST pridaním spring-boot-starter-web závislosť:

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

Z nášho koncového bodu REST môžeme začať odosielať príkazy a dotazy:

@RestController verejná trieda OrderRestEndpoint {private final CommandGateway commandGateway; súkromná konečná otázka QueryGateway queryGateway; // Konštruktor automatického zapojenia a koncové body POST / GET}

The CommandGateway sa používa ako mechanizmus na zasielanie našich príkazových správ a QueryGateway, na zasielanie správ s dotazom. Brány poskytujú jednoduchšie a priamočiarejšie API v porovnaní s CommandBus a QueryBus s ktorými sa spájajú.

Od tejto chvíle náš OrderRestEndpoint by mal mať koncový bod POST na uskutočnenie, potvrdenie a odoslanie objednávky:

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

Toto zaokrúhľuje príkazovú stránku našej aplikácie CQRS.

Teraz už zostáva iba ZÍSKAŤ koncový bod, pomocou ktorého budete môcť vyhľadávať všetky údaje Objednané produkty:

@GetMapping ("/ all-orders") public List findAllOrderedProducts () {return queryGateway.query (new FindAllOrderedProductsQuery (), ResponseTypes.multipleInstancesOf (OrderedProduct.class)). Join (); }

V koncovom bode GET využívame QueryGateway na odoslanie dotazu bod-bod. Pritom vytvoríme predvolené nastavenie FindAllOrderedProductsQuery, ale musíme tiež určiť typ očakávaného návratu.

Ako očakávame viac Objednaný produkt inštancie, ktoré sa majú vrátiť, využívame statické údaje ResponseTypes # multipleInstancesOf (trieda) funkcie. Týmto sme poskytli základný vstup do časti Dotaz našej objednávkovej služby.

Dokončili sme nastavenie, takže teraz môžeme spustiť niektoré príkazy a dotazy prostredníctvom nášho ovládača REST OrderApplication.

POST-ing do koncového bodu / objednávka na loď vytvorí inštanciu OrderAggregate ktoré zverejnia udalosti, ktoré následne uložia / aktualizujú naše Objednané produkty. ZÍSKAME z / všetky objednávky koncový bod zverejní správu s dotazom, ktorú spracuje OrderedProductsEventHandler, čím sa vrátia všetky existujúce Objednané produkty.

11. Záver

V tomto článku sme predstavili Axon Framework ako silnú základňu pre vytváranie aplikácií využívajúcich výhody CQRS a Event Sourcing.

Implementovali sme jednoduchú službu Objednávka pomocou rámca, ktorá ukazuje, ako by mala byť takáto aplikácia v praxi štruktúrovaná.

Nakoniec Axon Server predstavoval náš Event Store a mechanizmus smerovania správ.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete na GitHub.

Ak máte ďalšie otázky, pozrite si tiež skupinu používateľov Axon Framework.


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