En guide til Axon Framework

1. Oversikt

I denne artikkelen skal vi se på Axon og hvordan det hjelper oss med å implementere applikasjoner med CQRS (Command Query Responsibility Segregation) og Arrangementssourcing i tankene.

I løpet av denne guiden vil både Axon Framework og Axon Server bli brukt. Førstnevnte vil inneholde vår implementering og sistnevnte vil være vår dedikerte Event Store og Message Routing-løsning.

Eksempelapplikasjonen vi skal bygge fokuserer på en Rekkefølge domene. For dette, vi vil utnytte CQRS- og Event Sourcing-byggesteinene Axon gir oss.

Merk at mange av de delte konseptene kommer rett ut av DDD, som er utenfor omfanget av denne artikkelen.

2. Maven-avhengigheter

Vi oppretter en Axon / Spring Boot-applikasjon. Derfor må vi legge til det siste axon-spring-boot-starter avhengighet til vår pom.xml, samt axon-test avhengighet for testing:

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

3. Axon Server

Vi vil bruke Axon Server til å være vår Event Store og vår dedikerte løsning for kommando, hendelse og spørringsruting.

Som en Event Store gir den oss de ideelle egenskapene som kreves når du lagrer hendelser. Denne artikkelen gir bakgrunn for hvorfor dette er ønskelig.

Som en Message Routing-løsning gir det oss muligheten til å koble flere forekomster sammen uten å fokusere på å konfigurere ting som et RabbitMQ eller et Kafka-emne for å dele og sende meldinger.

Axon Server kan lastes ned her. Siden det er en enkel JAR-fil, er følgende operasjon tilstrekkelig for å starte den:

java -jar axonserver.jar

Dette starter en enkelt Axon Server-forekomst som er tilgjengelig gjennom lokal vert: 8024. Endepunktet gir en oversikt over de tilkoblede applikasjonene og meldingene de kan håndtere, samt en spørringsmekanisme mot Event Store som finnes i Axon Server.

Standardkonfigurasjonen av Axon Server sammen med axon-spring-boot-starter avhengighet vil sikre at vår ordretjeneste automatisk kobles til den.

4. Order Service API - Kommandoer

Vi setter opp bestillingstjenesten med tanke på CQRS. Derfor legger vi vekt på meldingene som flyter gjennom applikasjonen vår.

Først definerer vi kommandoene, som betyr uttrykk for intensjon. Order-tjenesten er i stand til å håndtere tre forskjellige typer handlinger:

  1. Plasser en ny ordre
  2. Bekrefte en ordre
  3. Frakt en bestilling

Naturligvis vil det være tre kommandomeldinger som domenet vårt kan håndtere - PlaceOrderCommand, ConfirmOrderCommand, og ShipOrderCommand:

offentlig klasse PlaceOrderCommand {@TargetAggregateIdentifier privat endelig streng orderId; privat slutt 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; // constructor, getters, equals / hashCode and toString}

De TargetAggregateIdentifier kommentar forteller Axon at det kommenterte feltet er en id for et gitt aggregat som kommandoen skal målrettes mot. Vi kommer kort inn på aggregater senere i denne artikkelen.

Vær også oppmerksom på at vi merket feltene i kommandoene som endelig. Dette er bevisst, som det er en best praksis for noen melding implementering for å være uforanderlig.

5. Order Service API - Events

Vårt aggregat vil håndtere kommandoene, ettersom det har ansvaret for å avgjøre om en ordre kan plasseres, bekreftes eller sendes.

Den vil varsle resten av søknaden om avgjørelsen ved å publisere et arrangement. Vi har tre typer arrangementer - OrderPlacedEvent, OrderConfirmedEvent, og OrderShippedEvent:

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

6. Kommandomodellen - Bestillingsaggregat

Nå som vi har modellert vår kjerne-API med hensyn til kommandoene og hendelsene, kan vi begynne å lage kommandomodellen.

Da domenet vårt fokuserer på å håndtere ordrer, vi lager en OrderAggregate som sentrum for vår kommandomodell.

6.1. Aggregatklasse

La oss altså lage vår grunnleggende samlede klasse:

@Aggregate public class OrderAggregate {@AggregateIdentifier private String orderId; privat boolsk ordreBekreftet; @CommandHandler offentlig OrderAggregate (PlaceOrderCommand-kommando) {AggregateLifecycle.apply (new OrderPlacedEvent (command.getOrderId (), command.getProduct ())); } @EventSourcingHandler offentlig ugyldig på (OrderPlacedEvent hendelse) {this.orderId = event.getOrderId (); orderConfirmed = false; } beskyttet OrderAggregate () {}}

De Samlet kommentar er en Axon Spring-spesifikk kommentar som markerer denne klassen som et aggregat. Det vil varsle rammeverket om at de nødvendige CQRS- og Event Sourcing-spesifikke byggesteinene må instantieres for dette OrderAggregate.

Ettersom et aggregat vil håndtere kommandoer som er målrettet mot en spesifikk samlet forekomst, må vi spesifisere identifikatoren med AggregateIdentifier kommentar.

Vårt aggregat vil starte livssyklusen etter håndtering av PlaceOrderCommand i OrderAggregate ‘Kommandohåndteringskonstruktør’. For å fortelle rammeverket at den gitte funksjonen er i stand til å håndtere kommandoer, legger vi til CommandHandler kommentar.

Når du håndterer PlaceOrderCommand, vil den varsle resten av søknaden om at en ordre ble plassert ved å publisere OrderPlacedEvent. For å publisere en hendelse fra et samlet, bruker vi AggregateLifecycle # gjelder (Objekt ...).

Fra dette punktet kan vi faktisk begynne å innlemme hendelsessourcing som drivkraft for å gjenskape en samlet forekomst fra sin strøm av hendelser.

Vi starter dette med den 'samlede opprettelseshendelsen' OrderPlacedEvent, som håndteres i en EventSourcingHandler merket funksjon for å stille inn Bestillings ID og ordre bekreftet ordensaggregatet.

Vær også oppmerksom på at Axon krever en standardkonstruktør for å kunne hente et aggregat basert på hendelsene.

6.2. Aggregate Command Handlers

Nå som vi har vårt grunnleggende aggregat, kan vi begynne å implementere de gjenværende kommandobehandlerne:

@CommandHandler offentlig tomhåndtak (ConfirmOrderCommand-kommando) {gjelder (ny OrderConfirmedEvent (orderId)); } @CommandHandler offentlig tomhåndtak (ShipOrderCommand-kommando) {if (! OrderConfirmed) {kast nytt UnconfirmedOrderException (); } gjelder (ny OrderShippedEvent (orderId)); } @EventSourcingHandler ugyldig på (OrderConfirmedEvent hendelse) {orderConfirmed = true; }

Signaturen til våre kommandoer- og hendelsesleverandører sier ganske enkelt håndtak ({kommandoen}) og på ({the-event}) for å opprettholde et kortfattet format.

I tillegg har vi definert at en ordre bare kan sendes hvis den er bekreftet. Dermed vil vi kaste en UnconfirmedOrderException hvis dette ikke er tilfelle.

Dette eksemplifiserer behovet for Bestill bekreftet hendelse sourcing handler for å oppdatere ordre bekreftet stat til ekte for ordregruppen.

7. Testing av kommandomodellen

Først må vi sette opp testen vår ved å lage en FixtureConfiguration for OrderAggregate:

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

Den første prøvesaken skal dekke den enkleste situasjonen. Når aggregatet håndterer PlaceOrderCommand, det skal produsere en OrderPlacedEvent:

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

Deretter kan vi teste beslutningslogikken om bare å kunne sende en ordre hvis den er bekreftet. På grunn av dette har vi to scenarier - ett der vi forventer et unntak, og et der vi forventer et OrderShippedEvent.

La oss ta en titt på det første scenariet, der vi forventer et unntak:

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

Og nå det andre scenariet, der vi forventer et OrderShippedEvent:

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

8. Spørringsmodellen - hendelsesbehandlere

Så langt har vi etablert vår kjerne-API med kommandoene og hendelsene, og vi har kommandomodellen til vår CQRS-ordretjeneste, ordreaggregatet, på plass.

Neste, vi kan begynne å tenke på en av spørringsmodellene som applikasjonen vår skal betjene.

En av disse modellene er OrderedProducts:

offentlig klasse OrderedProduct {private final String orderId; privat slutt 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; } offentlig ugyldig setOrderShipped () {this.orderStatus = OrderStatus.SHIPPED; } // getters, equals / hashCode og toString-funksjoner} public enum OrderStatus {PLACED, CONFIRMED, SHIPPED}

Vi oppdaterer denne modellen basert på hendelsene som forplanter seg gjennom systemet vårt. En fjær Service bønne for å oppdatere modellen vår vil gjøre susen:

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

Som vi har brukt axon-spring-boot-starter avhengighet til å starte Axon-applikasjonen, vil rammeverket automatisk skanne alle bønnene for eksisterende meldingshåndteringsfunksjoner.

Som den OrderedProductsEventHandler har EventHandler merkede funksjoner for å lagre en OrderedProduct og oppdaterer den, vil denne bønnen registreres av rammeverket som en klasse som skal motta hendelser uten å kreve noen konfigurasjon fra vår side.

9. Spørringsmodellen - Spørringshåndterere

Deretter, for å spørre denne modellen, for eksempel for å hente alle de bestilte produktene, bør vi først introdusere en spørringsmelding til kjernen API:

offentlig klasse FindAllOrderedProductsQuery {}

For det andre må vi oppdatere OrderedProductsEventHandler å kunne håndtere FindAllOrderedProductsQuery:

@QueryHandler offentlig listehåndtak (FindAllOrderedProductsQuery-spørring) {returner ny ArrayList (orderProducts.values ​​()); }

De QueryHandler merket funksjon vil håndtere FindAllOrderedProductsQuery og er satt til å returnere a Liste uansett, på samme måte som alle "finn alle" -spørsmål.

10. Sette alt sammen

Vi har utarbeidet kjerneprogrammet vårt med kommandoer, hendelser og spørsmål, og satt opp vår kommando- og spørringsmodell ved å ha en OrderAggregate og OrderedProducts modell.

Det neste er å binde de løse ender av infrastrukturen vår. Når vi bruker axon-spring-boot-starter, dette angir mye av den nødvendige konfigurasjonen automatisk.

Først, ettersom vi ønsker å utnytte hendelsessourcing for vårt aggregat, trenger vi en EventStore. Axon Server som vi har startet opp i trinn tre vil fylle dette hullet.

For det andre trenger vi en mekanisme for å lagre vår OrderedProduct spørringsmodell. For dette eksemplet kan vi legge til h2 som en database i minnet og spring-boot-starter-data-jpa for enkel bruk:

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

10.1. Sette opp et REST-endepunkt

Deretter må vi ha tilgang til applikasjonen vår, som vi bruker et REST-endepunkt for ved å legge til spring-boot-starter-web avhengighet:

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

Fra vårt REST-endepunkt kan vi begynne å sende kommandoer og spørsmål:

@RestController offentlig klasse OrderRestEndpoint {privat slutt CommandGateway commandGateway; privat slutt QueryGateway queryGateway; // Autowiring constructor og POST / GET endpoints}

De CommandGateway brukes som mekanisme for å sende kommandomeldinger, og QueryGatewayi sin tur for å sende spørringsmeldinger. Gatewayene gir en enklere, mer enkel API, sammenlignet med CommandBus og QueryBus som de kobler seg til.

Herfra og videre, våre OrderRestEndpoint skal ha et POST-sluttpunkt for å plassere, bekrefte og sende en bestilling:

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

Dette avrunder kommandosiden av CQRS-applikasjonen vår.

Nå er alt som er igjen et GET-sluttpunkt for å spørre om alt OrderedProducts:

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

I GET-sluttpunktet utnytter vi QueryGateway for å sende et punkt-til-punkt-spørsmål. Ved å gjøre det oppretter vi en standard FindAllOrderedProductsQuery, men vi må også spesifisere forventet returtype.

Som vi forventer flere OrderedProduct tilfeller som skal returneres, utnytter vi det statiske ResponseTypes # multipleInstancesOf (klasse) funksjon. Med dette har vi gitt en grunnleggende inngang til spørresiden av vår bestillingstjeneste.

Vi fullførte oppsettet, så nå kan vi sende noen kommandoer og spørsmål gjennom REST-kontrolleren vår når vi har startet opp Bestill applikasjon.

POSTING til endepunkt / bestilling vil instantiere en OrderAggregate som vil publisere arrangementer, som igjen vil lagre / oppdatere våre OrderedProducts. KOMME fra / alle ordrer endepunkt vil publisere en spørringsmelding som blir håndtert av OrderedProductsEventHandler, som vil returnere alle eksisterende OrderedProducts.

11. Konklusjon

I denne artikkelen introduserte vi Axon Framework som en kraftig base for å bygge en applikasjon som utnytter fordelene med CQRS og Event Sourcing.

Vi implementerte en enkel ordretjeneste ved hjelp av rammeverket for å vise hvordan en slik applikasjon skal være strukturert i praksis.

Til slutt stilte Axon Server ut som vår Event Store og rutemekanismen for meldinger.

Implementeringen av alle disse eksemplene og kodebiter finner du på GitHub.

For ytterligere spørsmål du måtte ha, kan du også sjekke ut Axon Framework User Group.


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