En guide til Axon Framework

1. Oversigt

I denne artikel vil vi se på Axon og hvordan det hjælper os med at implementere applikationer med CQRS (Segmentering af ansvar for kommandoforespørgsel) og Arrangementssourcing i tankerne.

I denne vejledning vil både Axon Framework og Axon Server blive brugt. Førstnævnte vil indeholde vores implementering, og sidstnævnte vil være vores dedikerede Event Store og Message Routing-løsning.

Den eksempelapplikation, vi bygger, fokuserer på en Bestille domæne. For det, Vi udnytter CQRS- og Event Sourcing-byggestenene, som Axon giver os.

Bemærk, at mange af de delte koncepter kommer lige ud af DDD, hvilket er uden for anvendelsesområdet for denne aktuelle artikel.

2. Maven-afhængigheder

Vi opretter en Axon / Spring Boot-applikation. Derfor skal vi tilføje det nyeste axon-fjeder-boot-starter afhængighed af vores pom.xml, samt axon-test afhængighed til testning:

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

3. Axon Server

Vi bruger Axon Server til at være vores Event Store og vores dedikerede løsning til kommando, begivenhed og forespørgsel.

Som en Event Store giver det os de ideelle egenskaber, der kræves, når vi gemmer events. Denne artikel giver baggrund for, hvorfor dette er ønskeligt.

Som en løsning til meddelelsesrute giver det os mulighed for at forbinde flere forekomster sammen uden at fokusere på at konfigurere ting som et RabbitMQ eller et Kafka-emne til at dele og sende meddelelser.

Axon Server kan downloades her. Da det er en simpel JAR-fil, er følgende handling tilstrækkelig til at starte den:

java -jar axonserver.jar

Dette starter en enkelt Axon Server-forekomst, som er tilgængelig via lokal vært: 8024. Slutpunktet giver et overblik over de tilsluttede applikationer og de meddelelser, de kan håndtere, samt en forespørgselsmekanisme mod Event Store indeholdt i Axon Server.

Standardkonfigurationen af ​​Axon Server sammen med axon-fjeder-boot-starter afhængighed vil sikre, at vores ordretjeneste automatisk opretter forbindelse til den.

4. Order Service API - kommandoer

Vi opretter vores ordretjeneste med CQRS i tankerne. Derfor vil vi understrege de meddelelser, der flyder gennem vores applikation.

Først definerer vi kommandoer, hvilket betyder udtryk for hensigt. Order-tjenesten er i stand til at håndtere tre forskellige typer handlinger:

  1. Afgivelse af en ny ordre
  2. Bekræftelse af en ordre
  3. Forsendelse af en ordre

Naturligvis vil der være tre kommandobeskeder, som vores domæne kan håndtere - PlaceOrderCommand, BekræftOrderCommandog ShipOrderCommand:

offentlig klasse PlaceOrderCommand {@TargetAggregateIdentifier privat endelig String orderId; private endelige String produkt; // constructor, getters, equals / hashCode and toString} public class ConfirmOrderCommand {@TargetAggregateIdentifier private final String orderId; // constructor, getters, equals / hashCode and toString} public class ShipOrderCommand {@TargetAggregateIdentifier private final String orderId; // konstruktør, getters, lig / hashCode og toString}

Det TargetAggregateIdentifier annotation fortæller Axon, at det kommenterede felt er et id for et givet aggregat, som kommandoen skal målrettes mod. Vi vil kort berøre aggregater senere i denne artikel.

Bemærk også, at vi markerede felterne i kommandoerne som endelig. Dette er bevidst, som det er en bedste praksis for nogen meddelelsesimplementering er uforanderlig.

5. Order Service API - Begivenheder

Vores aggregat håndterer kommandoerne, da det har ansvaret for at beslutte, om en ordre kan placeres, bekræftes eller sendes.

Den underretter resten af ​​ansøgningen om sin beslutning ved at offentliggøre en begivenhed. Vi har tre typer begivenheder - OrderPlacedEvent, OrderConfirmedEventog OrderShippedEvent:

public class OrderPlacedEvent {private final String orderId; private endelige String produkt; // standardkonstruktør, getters, er lig med / hashCode og toString} offentlig klasse OrderConfirmedEvent {private final String orderId; // standardkonstruktør, getters, er lig med / hashCode og toString} public class OrderShippedEvent {private final String orderId; // standardkonstruktør, getters, er lig med / hashCode og toString}

6. Kommandomodellen - Bestillingsaggregat

Nu hvor vi har modelleret vores kerne-API med hensyn til kommandoer og begivenheder, kan vi begynde at oprette kommandomodellen.

Da vores domæne fokuserer på at håndtere ordrer, vi opretter en OrderAggregate som centrum for vores kommandomodel.

6.1. Samlet klasse

Lad os således oprette vores grundlæggende samlede klasse:

@Aggregate public class OrderAggregate {@AggregateIdentifier private String orderId; privat boolsk ordreBekræftet; @CommandHandler offentlig OrderAggregate (PlaceOrderCommand-kommando) {AggregateLifecycle.apply (ny OrderPlacedEvent (command.getOrderId (), command.getProduct ())); } @EventSourcingHandler ugyldigt på (OrderPlacedEvent begivenhed) {this.orderId = event.getOrderId (); orderConfirmed = false; } beskyttet OrderAggregate () {}}

Det Samlet annotation er en Axon Spring-specifik annotering, der markerer denne klasse som et samlet. Det underretter rammen om, at de krævede CQRS- og Event Sourcing-specifikke byggesten skal instantieres til dette OrderAggregate.

Da et aggregat håndterer kommandoer, der er målrettet mod en bestemt samlet forekomst, skal vi specificere identifikatoren med AggregateIdentifier kommentar.

Vores aggregat begynder sin livscyklus efter håndtering af PlaceOrderCommand i OrderAggregate 'Kommandohåndteringskonstruktør'. For at fortælle rammen, at den givne funktion er i stand til at håndtere kommandoer, tilføjer vi CommandHandler kommentar.

Ved håndtering af PlaceOrderCommand, meddeler den resten af ​​applikationen, at en ordre blev afgivet ved at offentliggøre OrderPlacedEvent. For at udgive en begivenhed inden for et samlet, bruger vi AggregateLifecycle # gælder (Objekt ...).

Fra dette punkt kan vi faktisk begynde at inkorporere Event Sourcing som drivkraft til at genskabe en samlet instans fra dens strøm af begivenheder.

Vi starter dette med den 'samlede oprettelsesbegivenhed' OrderPlacedEvent, som håndteres i en EventSourcingHandler kommenteret funktion til at indstille Ordre ID og ordreBekræftet ordenens samlede tilstand.

Bemærk også, at Axon kræver en standardkonstruktør for at kunne kilde et aggregat baseret på dets begivenheder.

6.2. Aggregate Command Handlers

Nu hvor vi har vores basale aggregat, kan vi begynde at implementere de resterende kommandohåndterere:

@CommandHandler offentligt ugyldigt håndtag (ConfirmOrderCommand kommando) {gælder (ny OrderConfirmedEvent (orderId)); } @ CommandHandler offentligt ugyldigt håndtag (ShipOrderCommand kommando) {if (! OrderConfirmed) {smid nyt UnconfirmedOrderException (); } anvende (ny OrderShippedEvent (orderId)); } @EventSourcingHandler ugyldigt på (OrderConfirmedEvent begivenhed) {orderConfirmed = true; }

Underskriften på vores kommandoer og begivenhedssourcinghandlere angiver simpelthen håndtag ({kommandoen}) og på ({the-event}) at opretholde et kortfattet format.

Derudover har vi defineret, at en ordre kun kan sendes, hvis den er bekræftet. Således kaster vi en UbekræftetOrderException hvis dette ikke er tilfældet.

Dette eksemplificerer behovet for OrderConfirmedEvent sourcing handler til at opdatere ordreBekræftet stat til rigtigt for ordregruppen.

7. Test af kommandomodellen

Først skal vi oprette vores test ved at oprette en FixtureConfiguration til OrderAggregate:

privat FixtureConfiguration fixture; @Før offentlig ugyldig setUp () {fixture = ny AggregateTestFixture (OrderAggregate.class); }

Den første testsag skal dække den enkleste situation. Når aggregatet håndterer PlaceOrderCommand, det skal producere en OrderPlacedEvent:

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

Dernæst kan vi teste beslutningslogikken om kun at kunne sende en ordre, hvis den er bekræftet. På grund af dette har vi to scenarier - et hvor vi forventer en undtagelse og et hvor vi forventer en OrderShippedEvent.

Lad os se på det første scenario, hvor vi forventer en undtagelse:

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

Og nu det andet scenario, hvor vi forventer en OrderShippedEvent:

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

8. Forespørgselsmodellen - begivenhedshåndterere

Indtil videre har vi etableret vores kerne-API med kommandoerne og begivenhederne, og vi har kommandomodellen af ​​vores CQRS-ordretjeneste, ordren samlet, på plads.

Næste, vi kan begynde at tænke på en af ​​de forespørgselsmodeller, som vores applikation skal tjene.

En af disse modeller er OrderedProducts:

offentlig klasse OrderedProduct {private final String orderId; private endelige String produkt; privat 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, lig med / hashCode og toString-funktioner} offentlig enum OrderStatus {PLACED, CONFIRMED, SHIPPED}

Vi opdaterer denne model baseret på de begivenheder, der spredes gennem vores system. En forår Service bønne for at opdatere vores model vil gøre tricket:

@Service offentlig klasse OrderedProductsEventHandler {privat endelig Map besteldProdukter = ny HashMap (); @EventHandler offentlig ugyldig (OrderPlacedEvent begivenhed) {String orderId = event.getOrderId (); orderProducts.put (orderId, ny OrderedProduct (orderId, event.getProduct ())); } // Event Handlers for OrderConfirmedEvent og OrderShippedEvent ...}

Som vi har brugt axon-fjeder-boot-starter afhængighed af at starte vores Axon-applikation, scanner rammen automatisk alle bønner for eksisterende beskedhåndteringsfunktioner.

Som den OrderedProductsEventHandler har EventHandler bemærkede funktioner til lagring af en OrderedProduct og opdater det, vil denne bønne blive registreret af rammen som en klasse, der skal modtage begivenheder uden at kræve nogen konfiguration fra vores side.

9. Forespørgselsmodellen - Forespørgselshandlere

Dernæst, for at forespørge denne model, for eksempel for at hente alle de bestilte produkter, skal vi først introducere en forespørgselsmeddelelse til vores centrale API:

offentlig klasse FindAllOrderedProductsQuery {}

For det andet bliver vi nødt til at opdatere OrderedProductsEventHandler at være i stand til at håndtere FindAllOrderedProductsQuery:

@QueryHandler offentlig listehåndtag (FindAllOrderedProductsQuery-forespørgsel) {returner ny ArrayList (orderProducts.values ​​()); }

Det QueryHandler kommenteret funktion håndterer FindAllOrderedProductsQuery og er indstillet til at returnere a Liste uanset på samme måde som enhver "find all" -forespørgsel.

10. Sætte alt sammen

Vi har uddybet vores kerne-API med kommandoer, begivenheder og forespørgsler og oprettet vores kommando- og forespørgselsmodel ved at have en OrderAggregate og OrderedProducts model.

Næste er at binde de løse ender af vores infrastruktur. Som vi bruger axon-fjeder-boot-starter, dette indstiller en masse af den nødvendige konfiguration automatisk.

Først, da vi ønsker at udnytte hændelsessourcing til vores samlede, har vi brug for en EventStore. Axon Server, som vi har startet i trin tre, udfylder dette hul.

For det andet har vi brug for en mekanisme til at gemme vores OrderedProduct forespørgselsmodel. I dette eksempel kan vi tilføje h2 som en hukommelsesdatabase og spring-boot-starter-data-jpa for nem brug:

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

10.1. Opsætning af et REST-slutpunkt

Dernæst skal vi have adgang til vores applikation, som vi udnytter et REST-slutpunkt for ved at tilføje spring-boot-starter-web afhængighed:

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

Fra vores REST-slutpunkt kan vi begynde at sende kommandoer og forespørgsler:

@RestController offentlig klasse OrderRestEndpoint {privat endelig CommandGateway commandGateway; privat endelig QueryGateway queryGateway; // Autowiring-konstruktør og POST / GET-slutpunkter}

Det CommandGateway bruges som mekanisme til at sende vores kommandobeskeder, og QueryGatewaytil gengæld at sende forespørgselsmeddelelser. Gateways giver en enklere og mere ligetil API sammenlignet med CommandBus og QueryBus som de forbinder med.

Herfra, vores OrderRestEndpoint skal have et POST-slutpunkt til at placere, bekræfte og sende en ordre:

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

Dette afrunder kommandosiden af ​​vores CQRS-applikation.

Nu er alt, hvad der er tilbage, et GET-slutpunkt til forespørgsel på alt det OrderedProducts:

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

I GET-slutpunktet udnytter vi QueryGateway at sende en punkt-til-punkt-forespørgsel. Dermed opretter vi en standard FindAllOrderedProductsQuery, men vi skal også angive den forventede returneringstype.

Som vi forventer flere OrderedProduct tilfælde, der skal returneres, udnytter vi det statiske ResponseTypes # multipleInstancesOf (klasse) fungere. Med dette har vi givet en grundlæggende indgang til forespørgselssiden af ​​vores ordretjeneste.

Vi afsluttede opsætningen, så nu kan vi sende nogle kommandoer og forespørgsler gennem vores REST-controller, når vi først har startet Bestillingsapplikation.

POST-ing til slutpunkt / skibsordre vil instantiere en OrderAggregate der vil offentliggøre begivenheder, som igen vil gemme / opdatere vores OrderedProducts. GET-ing fra / alle ordrer slutpunkt vil offentliggøre en forespørgselsmeddelelse, der håndteres af OrderedProductsEventHandler, som returnerer alle eksisterende OrderedProducts.

11. Konklusion

I denne artikel introducerede vi Axon Framework som en stærk base til opbygning af en applikation, der udnytter fordelene ved CQRS og Event Sourcing.

Vi implementerede en simpel ordretjeneste ved hjælp af rammen for at vise, hvordan en sådan applikation skal struktureres i praksis.

Endelig udgjorde Axon Server som vores Event Store og meddelelsesrute-mekanismen.

Implementeringen af ​​alle disse eksempler og kodestykker findes på GitHub.

For yderligere spørgsmål, du måtte have, skal du også tjekke Axon Framework User Group.


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