From 4b1bf0962d76780b08f22b4b99a6494e21993f71 Mon Sep 17 00:00:00 2001 From: sbawa <45673906+sdbawa@users.noreply.github.com> Date: Mon, 23 Sep 2019 14:01:55 -0700 Subject: [PATCH] Add files via upload initial commit --- Readme.md | 66 +++++++++++++++++++ pom.xml | 52 +++++++++++++++ .../com/example/match/PlanMatchService.java | 13 ++++ .../match/api/InquiryCreateCommand.java | 20 ++++++ .../match/api/InquiryCreatedEvent.java | 18 +++++ .../match/api/InquiryScoreCommand.java | 20 ++++++ .../example/match/api/InquiryScoredEvent.java | 18 +++++ .../match/api/InquirySummaryQuery.java | 15 +++++ .../example/match/client/DomainService.java | 43 ++++++++++++ .../com/example/match/command/Inquiry.java | 63 ++++++++++++++++++ .../match/query/InquirySummaryProjector.java | 45 +++++++++++++ .../match/query/InquirySummaryView.java | 27 ++++++++ src/main/resources/application.properties | 10 +++ 13 files changed, 410 insertions(+) create mode 100644 Readme.md create mode 100644 pom.xml create mode 100644 src/main/java/com/example/match/PlanMatchService.java create mode 100644 src/main/java/com/example/match/api/InquiryCreateCommand.java create mode 100644 src/main/java/com/example/match/api/InquiryCreatedEvent.java create mode 100644 src/main/java/com/example/match/api/InquiryScoreCommand.java create mode 100644 src/main/java/com/example/match/api/InquiryScoredEvent.java create mode 100644 src/main/java/com/example/match/api/InquirySummaryQuery.java create mode 100644 src/main/java/com/example/match/client/DomainService.java create mode 100644 src/main/java/com/example/match/command/Inquiry.java create mode 100644 src/main/java/com/example/match/query/InquirySummaryProjector.java create mode 100644 src/main/java/com/example/match/query/InquirySummaryView.java create mode 100644 src/main/resources/application.properties diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..b6b2ff9 --- /dev/null +++ b/Readme.md @@ -0,0 +1,66 @@ +This projects helps to build and deploy a microservice by applying +DDD, CQRS, Event Souring principles on AXON JVM stack. + + +

What you'll build

+You'll build a sample microservice with one bounded context. + +

What you'll need

+AXON stack - framework and server
+Java
+SpringBoot
+Postgres
+ +Learn more about AXON stack
+AXON is a open source stack to develop java based microservices . https://axoniq.io + + +

package structure

+This stack requires you to architect/structure your code is a particular way. + + +

api

+This is where the core APIs live. +APIs are exposed as command and queries which a client can use to interact +with the system. +This package also contains the Domain Events leveraged by commands and queries + + +

command

+This package contains model which serves the command side of application. +These model serves the commands only. + + +

query

+This package contains model which serves the query side of application. +These model serves the queries only. + +

client

+This package contains a domain service for test purposes. + +---- +set up +---- +For this DEMO, I am using the Event store which comes as part of AXON stack. + +1. spin up the docker container instance of AXON server : +docker run -it --rm --name axonserver -p 8024:8024 -p 8124:8124 axoniq/axonserver + +Check AXON dasboard : http://localhost:8024/ + +2. spin up the docker container instance of postgres : +docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=planmatch -e POSTGRES_PASSWORD=secret postgres:9.6 + +---- +run application +---- +Run this springboot app from PlanMatchService. + +---- +verify +---- + +---- +Summary +---- +Congratulations on spinning up your microservices with AXON stack. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..01229c3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.7.RELEASE + + + com.example + planmatchservice + 1.0 + leadgen + Demo AXON based microservice for LeadGen using DDD, CQRS, EventSourcing + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + org.axonframework + axon-spring-boot-starter + 4.1 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/example/match/PlanMatchService.java b/src/main/java/com/example/match/PlanMatchService.java new file mode 100644 index 0000000..6f118bc --- /dev/null +++ b/src/main/java/com/example/match/PlanMatchService.java @@ -0,0 +1,13 @@ +package com.example.match; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PlanMatchService { + + public static void main(String[] args) { + SpringApplication.run(PlanMatchService.class, args); + } + +} diff --git a/src/main/java/com/example/match/api/InquiryCreateCommand.java b/src/main/java/com/example/match/api/InquiryCreateCommand.java new file mode 100644 index 0000000..4d9b67f --- /dev/null +++ b/src/main/java/com/example/match/api/InquiryCreateCommand.java @@ -0,0 +1,20 @@ +package com.example.match.api; + +import lombok.Value; +import org.axonframework.modelling.command.TargetAggregateIdentifier; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Value +public class InquiryCreateCommand { + + @TargetAggregateIdentifier + UUID id; + + String firstName; + String status; +} diff --git a/src/main/java/com/example/match/api/InquiryCreatedEvent.java b/src/main/java/com/example/match/api/InquiryCreatedEvent.java new file mode 100644 index 0000000..c565444 --- /dev/null +++ b/src/main/java/com/example/match/api/InquiryCreatedEvent.java @@ -0,0 +1,18 @@ +package com.example.match.api; + +import lombok.Value; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Value +public class InquiryCreatedEvent { + + UUID id; + + String firstName; + String status; +} diff --git a/src/main/java/com/example/match/api/InquiryScoreCommand.java b/src/main/java/com/example/match/api/InquiryScoreCommand.java new file mode 100644 index 0000000..ab8c712 --- /dev/null +++ b/src/main/java/com/example/match/api/InquiryScoreCommand.java @@ -0,0 +1,20 @@ +package com.example.match.api; + +import lombok.Value; +import org.axonframework.modelling.command.TargetAggregateIdentifier; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Value +public class InquiryScoreCommand { + + @TargetAggregateIdentifier + UUID id; + + Integer score; + String status; +} diff --git a/src/main/java/com/example/match/api/InquiryScoredEvent.java b/src/main/java/com/example/match/api/InquiryScoredEvent.java new file mode 100644 index 0000000..9c859d9 --- /dev/null +++ b/src/main/java/com/example/match/api/InquiryScoredEvent.java @@ -0,0 +1,18 @@ +package com.example.match.api; + +import lombok.Value; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Value +public class InquiryScoredEvent { + + UUID id; + + Integer score; + String status; +} diff --git a/src/main/java/com/example/match/api/InquirySummaryQuery.java b/src/main/java/com/example/match/api/InquirySummaryQuery.java new file mode 100644 index 0000000..c72a5ea --- /dev/null +++ b/src/main/java/com/example/match/api/InquirySummaryQuery.java @@ -0,0 +1,15 @@ +package com.example.match.api; + +import lombok.Value; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Value +public class InquirySummaryQuery { + + UUID id; +} diff --git a/src/main/java/com/example/match/client/DomainService.java b/src/main/java/com/example/match/client/DomainService.java new file mode 100644 index 0000000..9583ab5 --- /dev/null +++ b/src/main/java/com/example/match/client/DomainService.java @@ -0,0 +1,43 @@ +package com.example.match.client; + +import com.example.match.api.InquiryCreateCommand; +import com.example.match.api.InquiryScoreCommand; +import com.example.match.query.InquirySummaryView; +import com.example.match.api.InquirySummaryQuery; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.axonframework.commandhandling.gateway.CommandGateway; +import org.axonframework.messaging.responsetypes.ResponseTypes; +import org.axonframework.queryhandling.QueryGateway; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +/** + * @author simar bawa + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class DomainService implements CommandLineRunner{ + + private final CommandGateway commandGateway; + private final QueryGateway queryGateway; + + public void run(String ... args) throws Exception { + + UUID id = UUID.randomUUID(); + + log.debug("issuing create command"); + commandGateway.sendAndWait(new InquiryCreateCommand(id, "simar", "create")); + + log.debug("issuing score command"); + commandGateway.sendAndWait(new InquiryScoreCommand(id, 5, "complete")); + + log.debug("querying inquiry"); + InquirySummaryView summary = queryGateway.query(new InquirySummaryQuery(id), ResponseTypes.instanceOf(InquirySummaryView.class)).join(); + log.debug("inquiry queried {}", summary); + + } +} diff --git a/src/main/java/com/example/match/command/Inquiry.java b/src/main/java/com/example/match/command/Inquiry.java new file mode 100644 index 0000000..b01d8d9 --- /dev/null +++ b/src/main/java/com/example/match/command/Inquiry.java @@ -0,0 +1,63 @@ +package com.example.match.command; + +import com.example.match.api.InquiryCreateCommand; +import com.example.match.api.InquiryCreatedEvent; +import com.example.match.api.InquiryScoreCommand; +import com.example.match.api.InquiryScoredEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.axonframework.commandhandling.CommandHandler; +import org.axonframework.eventsourcing.EventSourcingHandler; +import org.axonframework.modelling.command.AggregateIdentifier; +import org.axonframework.modelling.command.AggregateLifecycle; +import org.axonframework.spring.stereotype.Aggregate; + +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Aggregate +@RequiredArgsConstructor +@Slf4j +//@Profile("command") +public class Inquiry { + + @AggregateIdentifier + UUID id; + + String firstName; + Integer score; + String status; + + @CommandHandler + public Inquiry(InquiryCreateCommand cmd) { + log.debug("handling cmd {}", cmd); + if (cmd.getFirstName().equalsIgnoreCase("invalid")) throw new IllegalArgumentException("name is invalid"); + AggregateLifecycle.apply(new InquiryCreatedEvent(cmd.getId(), cmd.getFirstName(), cmd.getStatus())); + } + + + @CommandHandler + public void handle(InquiryScoreCommand cmd) { + log.debug("handling cmd {}", cmd); + if (cmd.getScore() <= 0) throw new IllegalArgumentException("score <= 0"); + AggregateLifecycle.apply(new InquiryScoredEvent(cmd.getId(), cmd.getScore(), cmd.getStatus())); + } + + @EventSourcingHandler + public void on(InquiryCreatedEvent evt) { + log.debug("applying evt {}", evt); + id = evt.getId(); + firstName = evt.getFirstName(); + status = evt.getStatus(); + } + + @EventSourcingHandler + public void on(InquiryScoredEvent evt) { + log.debug("applying evt {}", evt); + score = evt.getScore(); + status = evt.getStatus(); + } +} diff --git a/src/main/java/com/example/match/query/InquirySummaryProjector.java b/src/main/java/com/example/match/query/InquirySummaryProjector.java new file mode 100644 index 0000000..2fa427e --- /dev/null +++ b/src/main/java/com/example/match/query/InquirySummaryProjector.java @@ -0,0 +1,45 @@ +package com.example.match.query; + +import com.example.match.api.InquiryCreatedEvent; +import com.example.match.api.InquiryScoredEvent; +import com.example.match.api.InquirySummaryQuery; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.axonframework.eventhandling.EventHandler; +import org.axonframework.queryhandling.QueryHandler; +import org.springframework.stereotype.Component; + +import javax.persistence.EntityManager; + +/** + * @author simar bawa + */ + +@Component +@RequiredArgsConstructor +@Slf4j +//@Profile("query") +public class InquirySummaryProjector { + + private final EntityManager entityManager; + + @EventHandler + public void on(InquiryCreatedEvent evt) { + log.debug("projecting evt {}", evt); + entityManager.persist(new InquirySummaryView(evt.getId(), evt.getFirstName(), evt.getStatus(), evt.getStatus(), 0)); + } + + @EventHandler + public void on(InquiryScoredEvent evt) { + log.debug("projecting evt {}", evt); + InquirySummaryView summary = entityManager.find(InquirySummaryView.class, evt.getId()); + summary.score = evt.getScore(); + summary.currentState = evt.getStatus(); + } + + @QueryHandler + public InquirySummaryView handle(InquirySummaryQuery qry) { + return entityManager.find(InquirySummaryView.class, qry.getId()); + } + +} diff --git a/src/main/java/com/example/match/query/InquirySummaryView.java b/src/main/java/com/example/match/query/InquirySummaryView.java new file mode 100644 index 0000000..c768d75 --- /dev/null +++ b/src/main/java/com/example/match/query/InquirySummaryView.java @@ -0,0 +1,27 @@ +package com.example.match.query; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.UUID; + +/** + * @author simar bawa + */ + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class InquirySummaryView { + + @Id + UUID id; + String firstName; + String initialState; + String currentState; + Integer score; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..895f3b7 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=planmatchservice +logging.level.com.example.match=DEBUG + +spring.datasource.url=jdbc:postgresql://localhost:5432/planmatch +spring.datasource.username=planmatch +spring.datasource.password=secret +spring.jpa.generate-ddl=true +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true + +axon.axonserver.servers=localhost:8124