Add files via upload
initial commit
This commit is contained in:
66
Readme.md
Normal file
66
Readme.md
Normal file
@@ -0,0 +1,66 @@
|
||||
This projects helps to build and deploy a microservice by applying
|
||||
DDD, CQRS, Event Souring principles on AXON JVM stack.
|
||||
|
||||
|
||||
<h2>What you'll build</h2>
|
||||
You'll build a sample microservice with one bounded context.
|
||||
|
||||
<h2> What you'll need </h2>
|
||||
AXON stack - framework and server <br/>
|
||||
Java <br/>
|
||||
SpringBoot <br/>
|
||||
Postgres <br/>
|
||||
|
||||
Learn more about AXON stack<br/>
|
||||
AXON is a open source stack to develop java based microservices . https://axoniq.io
|
||||
|
||||
|
||||
<h2>package structure</h2>
|
||||
This stack requires you to architect/structure your code is a particular way.
|
||||
|
||||
|
||||
<h3>api</h3>
|
||||
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
|
||||
|
||||
|
||||
<h3>command</h3>
|
||||
This package contains model which serves the command side of application.
|
||||
These model serves the commands only.
|
||||
|
||||
|
||||
<h3>query</h3>
|
||||
This package contains model which serves the query side of application.
|
||||
These model serves the queries only.
|
||||
|
||||
<h3>client</h3>
|
||||
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.
|
||||
52
pom.xml
Normal file
52
pom.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.7.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>planmatchservice</artifactId>
|
||||
<version>1.0</version>
|
||||
<name>leadgen</name>
|
||||
<description>Demo AXON based microservice for LeadGen using DDD, CQRS, EventSourcing</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring-boot-starter</artifactId>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
13
src/main/java/com/example/match/PlanMatchService.java
Normal file
13
src/main/java/com/example/match/PlanMatchService.java
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
18
src/main/java/com/example/match/api/InquiryCreatedEvent.java
Normal file
18
src/main/java/com/example/match/api/InquiryCreatedEvent.java
Normal file
@@ -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;
|
||||
}
|
||||
20
src/main/java/com/example/match/api/InquiryScoreCommand.java
Normal file
20
src/main/java/com/example/match/api/InquiryScoreCommand.java
Normal file
@@ -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;
|
||||
}
|
||||
18
src/main/java/com/example/match/api/InquiryScoredEvent.java
Normal file
18
src/main/java/com/example/match/api/InquiryScoredEvent.java
Normal file
@@ -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;
|
||||
}
|
||||
15
src/main/java/com/example/match/api/InquirySummaryQuery.java
Normal file
15
src/main/java/com/example/match/api/InquirySummaryQuery.java
Normal file
@@ -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;
|
||||
}
|
||||
43
src/main/java/com/example/match/client/DomainService.java
Normal file
43
src/main/java/com/example/match/client/DomainService.java
Normal file
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
63
src/main/java/com/example/match/command/Inquiry.java
Normal file
63
src/main/java/com/example/match/command/Inquiry.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
10
src/main/resources/application.properties
Normal file
10
src/main/resources/application.properties
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user