add source
This commit is contained in:
50
README.md
50
README.md
@@ -3,6 +3,24 @@
|
||||
본 Hans-On Lab에서는 Axon Framework를 활용해서 CQRS와 Event Sourcing에 대해서 실습합니다.
|
||||
|
||||
|
||||
# Pre-Requisite
|
||||
|
||||
- git 설치 : https://git-scm.com/book/ko/v1/시작하기-Git-설치
|
||||
- docker : https://docs.docker.com/install/
|
||||
- JDK1.8 : http://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
- maven : http://maven.apache.org/download.cgi
|
||||
- Java IDE(Eclipse, IntelliJ등 )
|
||||
|
||||
,,,
|
||||
//Source Code 다운로드
|
||||
git clone https://github.com/DannyKang/CQRS-ESwithAxon
|
||||
|
||||
//MySql image 다운로드
|
||||
docker pull mysql
|
||||
|
||||
//mongodb image 다운로드
|
||||
docker pull mongo
|
||||
,,,
|
||||
|
||||
|
||||
## Axon Framework
|
||||
@@ -630,17 +648,17 @@ AxonAutoConfiguration 내부에서 CommandBus, EventBus, EventStorageEngine, Ser
|
||||
|
||||

|
||||
|
||||
CQRS에서는 Command-Side Repository와 Query-Side Repository를 별도로 가지도록 한다. 이 예제에서는 Command-Side는 MongoDB를, Query는 MySQL을 이용하도록 한다.
|
||||
CQRS에서는 Command-Side Repository와 Query-Side Repository를 별도로 가지도록 한다. 이 예제에서는 Command-Side는 MongoDB를, Query는 MySQL을 이용하도록 합니다.
|
||||
|
||||
#### 시나리오
|
||||
>백오피스의 직원이 쇼핑몰에 신규 상품을 생성하면, 고객이 상품 아이템을 선택해서 주문을 하고 결제를 하는 시나리오이다.
|
||||
>Product (id, name, stock, price)
|
||||
>상품 추가 프로세스
|
||||
>CreateProductCommand -> new ProductAggregate instance -> ProductCreatedEvent
|
||||
>여기서 주로 Event는 과거에 일어난 이벤트로 과거시제를 주로 사용한다.
|
||||
>Order (id, username, payment, products)
|
||||
>주문 프로세스
|
||||
CreateOrderCommand-> new OrderAggregateinstance -> OrderCreatedEvent
|
||||
>백오피스의 직원이 쇼핑몰에 신규 상품을 생성하면, 고객이 상품 아이템을 선택해서 주문을 하고 결제를 하는 시나리오입니다.
|
||||
>Product (id, name, stock, price)
|
||||
>상품 추가 프로세스
|
||||
>CreateProductCommand -> new ProductAggregate instance -> ProductCreatedEvent
|
||||
>여기서 주로 Event는 과거에 일어난 이벤트로 과거시제를 주로 사용합니다.
|
||||
>Order (id, username, payment, products)
|
||||
>주문 프로세스
|
||||
>CreateOrderCommand-> new OrderAggregateinstance -> OrderCreatedEvent
|
||||
|
||||
### Command-Side
|
||||
|
||||
@@ -1050,7 +1068,7 @@ public class OrderProductEntry {
|
||||
|
||||
|
||||
### Hands-On
|
||||
|
||||
***MySQL Database 추가 생성 "ProductOrder"***
|
||||
```
|
||||
//MySql image 다운로드
|
||||
docker pull mysql
|
||||
@@ -1062,12 +1080,12 @@ docker run -p 3306:3306 --name mysql1 -e MYSQL_ROOT_PASSWORD=Welcome1 -d mysql
|
||||
//mongodb 컨테이너 기동
|
||||
docker run -p 27017:27017 --name mongodb -d mongo
|
||||
|
||||
// MySql 데이터 베이스 생성 CQRS
|
||||
// MySql 데이터 베이스 생성 ProductOrder
|
||||
docker exec -it mysql1 bash
|
||||
$mysql -uroot -p
|
||||
Enter Password : Welcome1
|
||||
mysql> create database cqrs; -- Create the new database
|
||||
mysql> grant all on cqrs.* to 'root'@'localhost';
|
||||
mysql> create database productorder; -- Create the new database
|
||||
mysql> grant all on productorder.* to 'root'@'localhost';
|
||||
|
||||
select host, user from mysql.user;
|
||||
|
||||
@@ -1079,7 +1097,7 @@ docker exec -it mongodb bash
|
||||
POST http://localhost:8080/product/1?name=SoccerBall&price=10&stock=100
|
||||
|
||||
```
|
||||
curl -X POST http://localhost:8080/product/1?name=SoccerBall&price=10&stock=100
|
||||
curl -d "name=SoccerBall&price=10&stock=200" http://localhost:8080/product/1
|
||||
```
|
||||
|
||||
|
||||
@@ -1100,7 +1118,11 @@ JSON
|
||||
}
|
||||
|
||||
3. Query DB
|
||||
|
||||
```
|
||||
$docker exec -it mongodb1
|
||||
$mongo
|
||||
|
||||
> use axon
|
||||
> show collections
|
||||
events
|
||||
|
||||
1058
README.md.old
Normal file
1058
README.md.old
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 74 KiB |
1
lesson-1/.gitignore
vendored
Normal file
1
lesson-1/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/.metadata/
|
||||
11
lesson-1/README.md
Normal file
11
lesson-1/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Lesson-1
|
||||
---
|
||||
A very basic sample of Axon framework.
|
||||
|
||||
In this Sample, you will learn the basic concept of Axon and CQRS & EventSourcing.
|
||||
- Command
|
||||
- Event
|
||||
- CommandGateway
|
||||
- Command Handler
|
||||
- Event Handler
|
||||
|
||||
16
lesson-1/pom.xml
Normal file
16
lesson-1/pom.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sbs-axon</artifactId>
|
||||
<groupId>com.edi.learn</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>lesson-1</artifactId>
|
||||
<version>1</version>
|
||||
|
||||
</project>
|
||||
34
lesson-1/src/main/java/com/edi/learn/axon/Application.java
Normal file
34
lesson-1/src/main/java/com/edi/learn/axon/Application.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.edi.learn.axon;
|
||||
|
||||
|
||||
import com.edi.learn.axon.command.aggregates.BankAccount;
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.config.Configuration;
|
||||
import org.axonframework.config.DefaultConfigurer;
|
||||
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
public class Application {
|
||||
|
||||
private static final Logger LOGGER = getLogger(Application.class);
|
||||
|
||||
public static void main(String args[]){
|
||||
Configuration config = DefaultConfigurer.defaultConfiguration()
|
||||
.configureAggregate(BankAccount.class)
|
||||
.configureEmbeddedEventStore(c -> new InMemoryEventStorageEngine())
|
||||
.buildConfiguration();
|
||||
config.start();
|
||||
AccountId id = new AccountId();
|
||||
config.commandGateway().send(new CreateAccountCommand(id, "MyAccount",1000));
|
||||
config.commandGateway().send(new WithdrawMoneyCommand(id, 500));
|
||||
config.commandGateway().send(new WithdrawMoneyCommand(id, 500));
|
||||
/*config.commandBus().dispatch(asCommandMessage(new CreateAccountCommand(id, "MyAccount", 1000)));
|
||||
config.commandBus().dispatch(asCommandMessage(new WithdrawMoneyCommand(id, 500)));*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.edi.learn.axon.command.aggregates;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import com.edi.learn.axon.common.events.AccountCreatedEvent;
|
||||
import com.edi.learn.axon.common.events.MoneyWithdrawnEvent;
|
||||
import org.axonframework.commandhandling.CommandHandler;
|
||||
import org.axonframework.commandhandling.model.AggregateIdentifier;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
public class BankAccount {
|
||||
|
||||
private static final Logger LOGGER = getLogger(BankAccount.class);
|
||||
|
||||
@AggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private BigDecimal balance;
|
||||
|
||||
public BankAccount() {
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public BankAccount(CreateAccountCommand command){
|
||||
LOGGER.debug("Construct a new BankAccount");
|
||||
apply(new AccountCreatedEvent(command.getAccountId(), command.getAccountName(), command.getAmount()));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public void handle(WithdrawMoneyCommand command){
|
||||
apply(new MoneyWithdrawnEvent(command.getAccountId(), command.getAmount()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(AccountCreatedEvent event){
|
||||
this.accountId = event.getAccountId();
|
||||
this.accountName = event.getAccountName();
|
||||
this.balance = new BigDecimal(event.getAmount());
|
||||
LOGGER.info("Account {} is created with balance {}", accountId, this.balance);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(MoneyWithdrawnEvent event){
|
||||
BigDecimal result = this.balance.subtract(new BigDecimal(event.getAmount()));
|
||||
if(result.compareTo(BigDecimal.ZERO)<0)
|
||||
LOGGER.error("Cannot withdraw more money than the balance!");
|
||||
else {
|
||||
this.balance = result;
|
||||
LOGGER.info("Withdraw {} from account {}, balance result: {}", event.getAmount(), accountId, balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class CreateAccountCommand {
|
||||
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public CreateAccountCommand(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.commandhandling.TargetAggregateIdentifier;
|
||||
|
||||
|
||||
public class WithdrawMoneyCommand {
|
||||
|
||||
@TargetAggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
|
||||
public WithdrawMoneyCommand(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.edi.learn.axon.common.domain;
|
||||
|
||||
import org.axonframework.common.Assert;
|
||||
import org.axonframework.common.IdentifierFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class AccountId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7119961474083133148L;
|
||||
private final String identifier;
|
||||
|
||||
private final int hashCode;
|
||||
|
||||
public AccountId() {
|
||||
this.identifier = IdentifierFactory.getInstance().generateIdentifier();
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
public AccountId(String identifier) {
|
||||
Assert.notNull(identifier, ()->"Identifier may not be null");
|
||||
this.identifier = identifier;
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AccountId accountId = (AccountId) o;
|
||||
|
||||
return identifier.equals(accountId.identifier);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class AccountCreatedEvent {
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public AccountCreatedEvent(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class MoneyWithdrawnEvent {
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
public MoneyWithdrawnEvent(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
14
lesson-1/src/main/resources/logback.xml
Normal file
14
lesson-1/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
7
lesson-2/README.md
Normal file
7
lesson-2/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Lesson-2
|
||||
---
|
||||
In this sample, we integrate SpringBoot and Axon framework to do the same thing with Lesson-1.
|
||||
Meanwhile, we also learned how to use JPA repository to store the state of Aggregates, which is one of the two ways supported
|
||||
by Axon,
|
||||
- Standard Repository to store the state of Aggregate directly
|
||||
- Event sourcing Repository to store the events ever happened and get the state by replaying all the events
|
||||
34
lesson-2/pom.xml
Normal file
34
lesson-2/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sbs-axon</artifactId>
|
||||
<groupId>com.edi.learn</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>lesson-2</artifactId>
|
||||
<version>2</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>-->
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
21
lesson-2/src/main/java/com/edi/learn/axon/Application.java
Normal file
21
lesson-2/src/main/java/com/edi/learn/axon/Application.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.edi.learn.axon;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {"com.edi.learn"})
|
||||
public class Application {
|
||||
|
||||
private static final Logger LOGGER = getLogger(Application.class);
|
||||
|
||||
public static void main(String args[]){
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.edi.learn.axon.command.aggregates;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import com.edi.learn.axon.common.events.AccountCreatedEvent;
|
||||
import com.edi.learn.axon.common.events.MoneyWithdrawnEvent;
|
||||
import org.axonframework.commandhandling.CommandHandler;
|
||||
import org.axonframework.commandhandling.model.AggregateIdentifier;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.axonframework.spring.stereotype.Aggregate;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@Aggregate(repository = "accountRepository")
|
||||
@Entity
|
||||
public class BankAccount {
|
||||
|
||||
private static final Logger LOGGER = getLogger(BankAccount.class);
|
||||
|
||||
@AggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private BigDecimal balance;
|
||||
|
||||
public BankAccount() {
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public BankAccount(CreateAccountCommand command){
|
||||
LOGGER.debug("Construct a new BankAccount");
|
||||
apply(new AccountCreatedEvent(command.getAccountId(), command.getAccountName(), command.getAmount()));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public void handle(WithdrawMoneyCommand command){
|
||||
apply(new MoneyWithdrawnEvent(command.getAccountId(), command.getAmount()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(AccountCreatedEvent event){
|
||||
this.accountId = event.getAccountId();
|
||||
this.accountName = event.getAccountName();
|
||||
this.balance = new BigDecimal(event.getAmount());
|
||||
LOGGER.info("Account {} is created with balance {}", accountId, this.balance);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(MoneyWithdrawnEvent event){
|
||||
BigDecimal result = this.balance.subtract(new BigDecimal(event.getAmount()));
|
||||
if(result.compareTo(BigDecimal.ZERO)<0)
|
||||
LOGGER.error("Cannot withdraw more money than the balance!");
|
||||
else {
|
||||
this.balance = result;
|
||||
LOGGER.info("Withdraw {} from account {}, balance result: {}", event.getAmount(), accountId, balance);
|
||||
}
|
||||
}
|
||||
|
||||
@Id
|
||||
public String getAccountId() {
|
||||
return accountId.toString();
|
||||
}
|
||||
|
||||
public void setAccountId(String accountId) {
|
||||
this.accountId = new AccountId(accountId);
|
||||
}
|
||||
|
||||
@Column
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public void setAccountName(String accountName) {
|
||||
this.accountName = accountName;
|
||||
}
|
||||
|
||||
@Column
|
||||
public BigDecimal getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public void setBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class CreateAccountCommand {
|
||||
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public CreateAccountCommand(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.commandhandling.TargetAggregateIdentifier;
|
||||
|
||||
|
||||
public class WithdrawMoneyCommand {
|
||||
|
||||
@TargetAggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
|
||||
public WithdrawMoneyCommand(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.edi.learn.axon.command.controller;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.commandhandling.gateway.CommandGateway;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Created by Edison on 2017/3/9.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/bank")
|
||||
public class BankAccountController {
|
||||
|
||||
private static final Logger LOGGER = getLogger(BankAccountController.class);
|
||||
|
||||
@Autowired
|
||||
private CommandGateway commandGateway;
|
||||
|
||||
@Autowired
|
||||
private HttpServletResponse response;
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public void create() {
|
||||
LOGGER.info("start");
|
||||
AccountId id = new AccountId();
|
||||
LOGGER.debug("Account id: {}", id.toString());
|
||||
commandGateway.send(new CreateAccountCommand(id, "MyAccount",1000));
|
||||
commandGateway.send(new WithdrawMoneyCommand(id, 500));
|
||||
commandGateway.send(new WithdrawMoneyCommand(id, 300));
|
||||
commandGateway.send(new CreateAccountCommand(id, "MyAccount", 1000));
|
||||
commandGateway.send(new WithdrawMoneyCommand(id, 500));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.edi.learn.axon.common.config;
|
||||
|
||||
import com.edi.learn.axon.command.aggregates.BankAccount;
|
||||
import org.axonframework.commandhandling.CommandBus;
|
||||
import org.axonframework.commandhandling.SimpleCommandBus;
|
||||
import org.axonframework.commandhandling.model.GenericJpaRepository;
|
||||
import org.axonframework.commandhandling.model.Repository;
|
||||
import org.axonframework.common.jpa.ContainerManagedEntityManagerProvider;
|
||||
import org.axonframework.common.jpa.EntityManagerProvider;
|
||||
import org.axonframework.common.transaction.TransactionManager;
|
||||
import org.axonframework.eventhandling.EventBus;
|
||||
import org.axonframework.eventhandling.SimpleEventBus;
|
||||
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
|
||||
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
|
||||
import org.axonframework.messaging.interceptors.TransactionManagingInterceptor;
|
||||
import org.axonframework.monitoring.NoOpMessageMonitor;
|
||||
import org.axonframework.spring.config.EnableAxon;
|
||||
import org.axonframework.spring.messaging.unitofwork.SpringTransactionManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Created by Edison on 2017/3/9.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAxon
|
||||
public class JpaConfig {
|
||||
|
||||
private static final Logger LOGGER = getLogger(JpaConfig.class);
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Bean
|
||||
public EventStorageEngine eventStorageEngine(){
|
||||
return new InMemoryEventStorageEngine();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransactionManager axonTransactionManager() {
|
||||
return new SpringTransactionManager(transactionManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EventBus eventBus(){
|
||||
return new SimpleEventBus();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CommandBus commandBus() {
|
||||
SimpleCommandBus commandBus = new SimpleCommandBus(axonTransactionManager(), NoOpMessageMonitor.INSTANCE);
|
||||
//commandBus.registerHandlerInterceptor(transactionManagingInterceptor());
|
||||
return commandBus;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransactionManagingInterceptor transactionManagingInterceptor(){
|
||||
return new TransactionManagingInterceptor(new SpringTransactionManager(transactionManager));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EntityManagerProvider entityManagerProvider() {
|
||||
return new ContainerManagedEntityManagerProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Repository<BankAccount> accountRepository(){
|
||||
return new GenericJpaRepository<BankAccount>(entityManagerProvider(),BankAccount.class, eventBus());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.edi.learn.axon.common.domain;
|
||||
|
||||
import org.axonframework.common.Assert;
|
||||
import org.axonframework.common.IdentifierFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class AccountId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7119961474083133148L;
|
||||
private final String identifier;
|
||||
|
||||
private final int hashCode;
|
||||
|
||||
public AccountId() {
|
||||
this.identifier = IdentifierFactory.getInstance().generateIdentifier();
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
public AccountId(String identifier) {
|
||||
Assert.notNull(identifier, ()->"Identifier may not be null");
|
||||
this.identifier = identifier;
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AccountId accountId = (AccountId) o;
|
||||
|
||||
return identifier.equals(accountId.identifier);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class AccountCreatedEvent {
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public AccountCreatedEvent(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class MoneyWithdrawnEvent {
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
public MoneyWithdrawnEvent(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
19
lesson-2/src/main/resources/application.properties
Normal file
19
lesson-2/src/main/resources/application.properties
Normal file
@@ -0,0 +1,19 @@
|
||||
# Datasource configuration
|
||||
spring.datasource.url=jdbc:h2:mem:exploredb
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
#spring.datasource.url=jdbc:mysql://localhost:3306/cqrs
|
||||
#spring.datasource.driverClassName=com.mysql.jdbc.Driver
|
||||
#spring.datasource.username=root
|
||||
#spring.datasource.password=Welcome1
|
||||
#spring.datasource.validation-query=SELECT 1;
|
||||
#spring.datasource.initial-size=2
|
||||
#spring.datasource.sql-script-encoding=UTF-8
|
||||
|
||||
spring.jpa.database=h2
|
||||
#spring.jpa.database=mysql
|
||||
#spring.jpa.show-sql=true
|
||||
#spring.jpa.hibernate.ddl-auto=create-drop
|
||||
|
||||
spring.h2.console.enabled=true
|
||||
17
lesson-2/src/main/resources/logback.xml
Normal file
17
lesson-2/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<logger name="com.edi.learn" level="debug" />
|
||||
<logger name="org.axonframework" level="info" />
|
||||
</configuration>
|
||||
9
lesson-3/README.md
Normal file
9
lesson-3/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Lesson-3
|
||||
---
|
||||
As mentioned in Lesson-2, Axon framework supports two mechanisms to maintain the state of aggregates.
|
||||
- Standard Repository to store the state of Aggregate directly
|
||||
- Event sourcing Repository to store the events ever happened and get the state by replaying all the events
|
||||
|
||||
In this lesson, we implement the event sourcing repository, which is the default option when using axon-spring-boot-autoconfigure
|
||||
module.
|
||||
All the events will be stored in the MySql through JPA.
|
||||
38
lesson-3/pom.xml
Normal file
38
lesson-3/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sbs-axon</artifactId>
|
||||
<groupId>com.edi.learn</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>lesson-3</artifactId>
|
||||
<version>3</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.edi.learn.axon.command.aggregates;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import com.edi.learn.axon.common.events.AccountCreatedEvent;
|
||||
import com.edi.learn.axon.common.events.MoneyWithdrawnEvent;
|
||||
import org.axonframework.commandhandling.CommandHandler;
|
||||
import org.axonframework.commandhandling.model.AggregateIdentifier;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.axonframework.spring.stereotype.Aggregate;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@Aggregate
|
||||
public class BankAccount {
|
||||
|
||||
private static final Logger LOGGER = getLogger(BankAccount.class);
|
||||
|
||||
@AggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private BigDecimal balance;
|
||||
|
||||
public BankAccount() {
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public BankAccount(CreateAccountCommand command){
|
||||
LOGGER.debug("Construct a new BankAccount");
|
||||
apply(new AccountCreatedEvent(command.getAccountId(), command.getAccountName(), command.getAmount()));
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public void handle(WithdrawMoneyCommand command){
|
||||
apply(new MoneyWithdrawnEvent(command.getAccountId(), command.getAmount()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(AccountCreatedEvent event){
|
||||
this.accountId = event.getAccountId();
|
||||
this.accountName = event.getAccountName();
|
||||
this.balance = new BigDecimal(event.getAmount());
|
||||
LOGGER.info("Account {} is created with balance {}", accountId, this.balance);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(MoneyWithdrawnEvent event){
|
||||
BigDecimal result = this.balance.subtract(new BigDecimal(event.getAmount()));
|
||||
if(result.compareTo(BigDecimal.ZERO)<0)
|
||||
LOGGER.error("Cannot withdraw more money than the balance!");
|
||||
else {
|
||||
this.balance = result;
|
||||
LOGGER.info("Withdraw {} from account {}, balance result: {}", event.getAmount(), accountId, balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class CreateAccountCommand {
|
||||
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public CreateAccountCommand(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.commandhandling.TargetAggregateIdentifier;
|
||||
|
||||
|
||||
public class WithdrawMoneyCommand {
|
||||
|
||||
@TargetAggregateIdentifier
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
|
||||
public WithdrawMoneyCommand(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.edi.learn.axon.common.domain;
|
||||
|
||||
import org.axonframework.common.Assert;
|
||||
import org.axonframework.common.IdentifierFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class AccountId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7119961474083133148L;
|
||||
private final String identifier;
|
||||
|
||||
private final int hashCode;
|
||||
|
||||
public AccountId() {
|
||||
this.identifier = IdentifierFactory.getInstance().generateIdentifier();
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
public AccountId(String identifier) {
|
||||
Assert.notNull(identifier, ()->"Identifier may not be null");
|
||||
this.identifier = identifier;
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AccountId accountId = (AccountId) o;
|
||||
|
||||
return identifier.equals(accountId.identifier);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class AccountCreatedEvent {
|
||||
private AccountId accountId;
|
||||
private String accountName;
|
||||
private long amount;
|
||||
|
||||
public AccountCreatedEvent(AccountId accountId, String accountName, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.accountName = accountName;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
|
||||
|
||||
public class MoneyWithdrawnEvent {
|
||||
private AccountId accountId;
|
||||
private long amount;
|
||||
|
||||
public MoneyWithdrawnEvent(AccountId accountId, long amount) {
|
||||
this.accountId = accountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public AccountId getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.edi.learn.axon.controller;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateAccountCommand;
|
||||
import com.edi.learn.axon.command.commands.WithdrawMoneyCommand;
|
||||
import com.edi.learn.axon.common.domain.AccountId;
|
||||
import org.axonframework.commandhandling.gateway.CommandGateway;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Created by Edison on 2017/3/9.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/bank")
|
||||
public class BankAccountController {
|
||||
|
||||
private static final Logger LOGGER = getLogger(BankAccountController.class);
|
||||
|
||||
@Autowired
|
||||
private CommandGateway commandGateway;
|
||||
|
||||
@Autowired
|
||||
private HttpServletResponse response;
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public void create() {
|
||||
LOGGER.info("start");
|
||||
AccountId id = new AccountId();
|
||||
LOGGER.debug("Account id: {}", id.toString());
|
||||
commandGateway.send(new CreateAccountCommand(id, "MyAccount",1000));
|
||||
commandGateway.send(new WithdrawMoneyCommand(id, 500));
|
||||
commandGateway.send(new WithdrawMoneyCommand(id, 300));
|
||||
/*config.commandBus().dispatch(asCommandMessage(new CreateAccountCommand(id, "MyAccount", 1000)));
|
||||
config.commandBus().dispatch(asCommandMessage(new WithdrawMoneyCommand(id, 500)));*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.edi.learn.axon.main;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {"com.edi.learn"})
|
||||
public class Application {
|
||||
|
||||
private static final Logger LOGGER = getLogger(Application.class);
|
||||
|
||||
public static void main(String args[]){
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
19
lesson-3/src/main/resources/application.properties
Normal file
19
lesson-3/src/main/resources/application.properties
Normal file
@@ -0,0 +1,19 @@
|
||||
# Datasource configuration
|
||||
# spring.datasource.url=jdbc:h2:mem:exploredb
|
||||
# spring.datasource.driverClassName=org.h2.Driver
|
||||
# spring.datasource.username=sa
|
||||
# spring.datasource.password=
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/cqrs
|
||||
spring.datasource.driverClassName=com.mysql.jdbc.Driver
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=Welcome1
|
||||
spring.datasource.validation-query=SELECT 1;
|
||||
spring.datasource.initial-size=2
|
||||
spring.datasource.sql-script-encoding=UTF-8
|
||||
|
||||
|
||||
|
||||
# spring.jpa.database=h2
|
||||
spring.jpa.database=mysql
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
17
lesson-3/src/main/resources/logback.xml
Normal file
17
lesson-3/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<logger name="com.edi.learn" level="debug" />
|
||||
<logger name="org.axonframework" level="info" />
|
||||
</configuration>
|
||||
11
lesson-4/README.md
Normal file
11
lesson-4/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Lesson-4
|
||||
---
|
||||
From this lesson, we will try to use Axon to implement a more complex case - the online shop.
|
||||
We'll climb the mountain step by step.
|
||||
In this lesson, we simply learn the following things.
|
||||
- How to use MongoDB as the event store
|
||||
- How to use MySql as the query database
|
||||
- In the CQRS, how to separate the command & query
|
||||
|
||||
Note:
|
||||
To avoid losing accuracy caused by using `float/double`, I multiply 100 with the price, and use `long` to store the price.
|
||||
52
lesson-4/pom.xml
Normal file
52
lesson-4/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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sbs-axon</artifactId>
|
||||
<groupId>com.edi.learn</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>lesson-4</artifactId>
|
||||
<version>4</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-mongo</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.6.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.edi.learn.axon.command.aggregates;
|
||||
|
||||
import com.edi.learn.axon.common.domain.OrderId;
|
||||
import com.edi.learn.axon.common.domain.OrderProduct;
|
||||
import com.edi.learn.axon.common.events.OrderCreatedEvent;
|
||||
import org.axonframework.commandhandling.model.AggregateIdentifier;
|
||||
import org.axonframework.commandhandling.model.AggregateMember;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.axonframework.spring.stereotype.Aggregate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;
|
||||
|
||||
|
||||
@Aggregate
|
||||
public class OrderAggregate {
|
||||
|
||||
@AggregateIdentifier
|
||||
private OrderId id;
|
||||
private String username;
|
||||
private double payment;
|
||||
|
||||
@AggregateMember
|
||||
private Map<String, OrderProduct> products;
|
||||
|
||||
public OrderAggregate(){}
|
||||
|
||||
public OrderAggregate(OrderId id, String username, Map<String, OrderProduct> products) {
|
||||
apply(new OrderCreatedEvent(id, username, products));
|
||||
}
|
||||
|
||||
public OrderId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public Map<String, OrderProduct> getProducts() {
|
||||
return products;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(OrderCreatedEvent event){
|
||||
this.id = event.getOrderId();
|
||||
this.username = event.getUsername();
|
||||
this.products = event.getProducts();
|
||||
computePrice();
|
||||
}
|
||||
|
||||
private void computePrice() {
|
||||
products.forEach((id, product) -> {
|
||||
payment += product.getPrice() * product.getAmount();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Divided 100 here because of the transformation of accuracy
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public double getPayment() {
|
||||
return payment/100;
|
||||
}
|
||||
|
||||
public void addProduct(OrderProduct product){
|
||||
this.products.put(product.getId(), product);
|
||||
payment += product.getPrice() * product.getAmount();
|
||||
}
|
||||
|
||||
public void removeProduct(String productId){
|
||||
OrderProduct product = this.products.remove(productId);
|
||||
payment = payment - product.getPrice() * product.getAmount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.edi.learn.axon.command.aggregates;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateProductCommand;
|
||||
import com.edi.learn.axon.common.events.ProductCreatedEvent;
|
||||
import org.axonframework.commandhandling.CommandHandler;
|
||||
import org.axonframework.commandhandling.model.AggregateIdentifier;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.axonframework.spring.stereotype.Aggregate;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@Aggregate
|
||||
public class ProductAggregate {
|
||||
|
||||
private static final Logger LOGGER = getLogger(ProductAggregate.class);
|
||||
|
||||
@AggregateIdentifier
|
||||
private String id;
|
||||
private String name;
|
||||
private int stock;
|
||||
private long price;
|
||||
|
||||
public ProductAggregate() {
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
public ProductAggregate(CreateProductCommand command) {
|
||||
apply(new ProductCreatedEvent(command.getId(),command.getName(),command.getPrice(),command.getStock()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(ProductCreatedEvent event){
|
||||
this.id = event.getId();
|
||||
this.name = event.getName();
|
||||
this.price = event.getPrice();
|
||||
this.stock = event.getStock();
|
||||
LOGGER.debug("Product [{}] {} {}x{} is created.", id,name,price,stock);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return stock;
|
||||
}
|
||||
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import com.edi.learn.axon.common.domain.OrderId;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class CreateOrderCommand {
|
||||
|
||||
private OrderId orderId;
|
||||
private String username;
|
||||
private Map<String, Integer> products;
|
||||
|
||||
public CreateOrderCommand(String username, Map<String, Integer> products) {
|
||||
this.orderId = new OrderId();
|
||||
this.username = username;
|
||||
this.products = products;
|
||||
}
|
||||
|
||||
public OrderId getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getProducts() {
|
||||
return products;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
|
||||
public class CreateProductCommand {
|
||||
|
||||
//@TargetAggregateIdentifier
|
||||
// here @TargetAggregateIdentifier annotation is optional because it's a construct command
|
||||
private String id;
|
||||
private String name;
|
||||
private long price;
|
||||
private int stock;
|
||||
|
||||
public CreateProductCommand(String id, String name, long price, int stock) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return stock;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.edi.learn.axon.command.commands;
|
||||
|
||||
import org.axonframework.commandhandling.TargetAggregateIdentifier;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/9.
|
||||
*/
|
||||
public class ReserveStockCommand {
|
||||
|
||||
@TargetAggregateIdentifier
|
||||
private String productId;
|
||||
private long number;
|
||||
|
||||
public ReserveStockCommand(String productId, long number) {
|
||||
this.productId = productId;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.edi.learn.axon.command.config;
|
||||
|
||||
import com.edi.learn.axon.command.aggregates.OrderAggregate;
|
||||
import org.axonframework.commandhandling.model.Repository;
|
||||
import org.axonframework.eventsourcing.AggregateFactory;
|
||||
import org.axonframework.eventsourcing.EventSourcingRepository;
|
||||
import org.axonframework.eventsourcing.eventstore.EventStore;
|
||||
import org.axonframework.spring.eventsourcing.SpringPrototypeAggregateFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/14.
|
||||
*/
|
||||
@Configuration
|
||||
public class OrderConfig {
|
||||
|
||||
@Autowired
|
||||
private EventStore eventStore;
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public OrderAggregate orderAggregate(){
|
||||
return new OrderAggregate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AggregateFactory<OrderAggregate> orderAggregateAggregateFactory(){
|
||||
SpringPrototypeAggregateFactory<OrderAggregate> aggregateFactory = new SpringPrototypeAggregateFactory<>();
|
||||
aggregateFactory.setPrototypeBeanName("orderAggregate");
|
||||
return aggregateFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Repository<OrderAggregate> orderAggregateRepository(){
|
||||
EventSourcingRepository<OrderAggregate> repository = new EventSourcingRepository<OrderAggregate>(
|
||||
orderAggregateAggregateFactory(),
|
||||
eventStore
|
||||
);
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.edi.learn.axon.command.config;
|
||||
|
||||
import com.edi.learn.axon.command.aggregates.ProductAggregate;
|
||||
import org.axonframework.commandhandling.model.Repository;
|
||||
import org.axonframework.eventsourcing.AggregateFactory;
|
||||
import org.axonframework.eventsourcing.EventSourcingRepository;
|
||||
import org.axonframework.eventsourcing.eventstore.EventStore;
|
||||
import org.axonframework.spring.eventsourcing.SpringPrototypeAggregateFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/14.
|
||||
*/
|
||||
@Configuration
|
||||
public class ProductConfig {
|
||||
|
||||
@Autowired
|
||||
private EventStore eventStore;
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public ProductAggregate productAggregate(){
|
||||
return new ProductAggregate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AggregateFactory<ProductAggregate> productAggregateAggregateFactory(){
|
||||
SpringPrototypeAggregateFactory<ProductAggregate> aggregateFactory = new SpringPrototypeAggregateFactory<>();
|
||||
aggregateFactory.setPrototypeBeanName("productAggregate");
|
||||
return aggregateFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Repository<ProductAggregate> productAggregateRepository(){
|
||||
EventSourcingRepository<ProductAggregate> repository = new EventSourcingRepository<ProductAggregate>(
|
||||
productAggregateAggregateFactory(),
|
||||
eventStore
|
||||
);
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.edi.learn.axon.command.handlers;
|
||||
|
||||
import com.edi.learn.axon.command.aggregates.OrderAggregate;
|
||||
import com.edi.learn.axon.command.aggregates.ProductAggregate;
|
||||
import com.edi.learn.axon.command.commands.CreateOrderCommand;
|
||||
import com.edi.learn.axon.common.domain.OrderProduct;
|
||||
import org.axonframework.commandhandling.CommandHandler;
|
||||
import org.axonframework.commandhandling.model.Aggregate;
|
||||
import org.axonframework.commandhandling.model.Repository;
|
||||
import org.axonframework.eventhandling.EventBus;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Created by Edison on 2017/3/9.
|
||||
*/
|
||||
@Component
|
||||
public class OrderHandler {
|
||||
|
||||
private static final Logger LOGGER = getLogger(OrderHandler.class);
|
||||
|
||||
@Autowired
|
||||
private Repository<OrderAggregate> repository;
|
||||
|
||||
@Autowired
|
||||
private Repository<ProductAggregate> productRepository;
|
||||
|
||||
@Autowired
|
||||
private EventBus eventBus;
|
||||
|
||||
@CommandHandler
|
||||
public void handle(CreateOrderCommand command) throws Exception {
|
||||
Map<String, OrderProduct> products = new HashMap<>();
|
||||
command.getProducts().forEach((productId,number)->{
|
||||
LOGGER.debug("Loading product information with productId: {}",productId);
|
||||
Aggregate<ProductAggregate> aggregate = productRepository.load(productId);
|
||||
products.put(productId,
|
||||
new OrderProduct(productId,
|
||||
aggregate.invoke(productAggregate -> productAggregate.getName()),
|
||||
aggregate.invoke(productAggregate -> productAggregate.getPrice()),
|
||||
number));
|
||||
});
|
||||
repository.newInstance(() -> new OrderAggregate(command.getOrderId(), command.getUsername(), products));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.edi.learn.axon.command.web.controllers;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.edi.learn.axon.command.commands.CreateOrderCommand;
|
||||
import org.axonframework.commandhandling.gateway.CommandGateway;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
public class OrderController {
|
||||
|
||||
private static final Logger LOGGER = getLogger(OrderController.class);
|
||||
|
||||
@Autowired
|
||||
private CommandGateway commandGateway;
|
||||
|
||||
@Autowired
|
||||
private HttpServletResponse response;
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public void create(@RequestBody(required = true) JSONObject input){
|
||||
LOGGER.info(input.toJSONString());
|
||||
|
||||
int responseCode = HttpServletResponse.SC_BAD_REQUEST;
|
||||
|
||||
if(input.containsKey("username") && input.containsKey("products")){
|
||||
String username = input.getString("username");
|
||||
JSONArray products = input.getJSONArray("products");
|
||||
if(!StringUtils.isEmpty(username) && products.size()>0){
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
CreateOrderCommand command = new CreateOrderCommand(username, map);
|
||||
for(Object each:products){
|
||||
JSONObject o = (JSONObject)each;
|
||||
if(!o.containsKey("id") || !o.containsKey("number"))
|
||||
return;
|
||||
map.put(o.getString("id"), o.getInteger("number"));
|
||||
}
|
||||
commandGateway.sendAndWait(command);
|
||||
responseCode = HttpServletResponse.SC_CREATED;
|
||||
}
|
||||
}
|
||||
|
||||
response.setStatus(responseCode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.edi.learn.axon.command.web.controllers;
|
||||
|
||||
import com.edi.learn.axon.command.commands.CreateProductCommand;
|
||||
import org.axonframework.commandhandling.CommandExecutionException;
|
||||
import org.axonframework.commandhandling.gateway.CommandGateway;
|
||||
import org.axonframework.commandhandling.model.ConcurrencyException;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/product")
|
||||
public class ProductController {
|
||||
|
||||
private static final Logger LOGGER = getLogger(ProductController.class);
|
||||
|
||||
@Autowired
|
||||
private CommandGateway commandGateway;
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
|
||||
public void create(@PathVariable(value = "id") String id,
|
||||
@RequestParam(value = "name", required = true) String name,
|
||||
@RequestParam(value = "price", required = true) long price,
|
||||
@RequestParam(value = "stock",required = true) int stock,
|
||||
HttpServletResponse response) {
|
||||
|
||||
LOGGER.debug("Adding Product [{}] '{}' {}x{}", id, name, price, stock);
|
||||
|
||||
try {
|
||||
// multiply 100 on the price to avoid float number
|
||||
CreateProductCommand command = new CreateProductCommand(id,name,price*100,stock);
|
||||
commandGateway.sendAndWait(command);
|
||||
response.setStatus(HttpServletResponse.SC_CREATED);// Set up the 201 CREATED response
|
||||
return;
|
||||
} catch (CommandExecutionException cex) {
|
||||
LOGGER.warn("Add Command FAILED with Message: {}", cex.getMessage());
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
if (null != cex.getCause()) {
|
||||
LOGGER.warn("Caused by: {} {}", cex.getCause().getClass().getName(), cex.getCause().getMessage());
|
||||
if (cex.getCause() instanceof ConcurrencyException) {
|
||||
LOGGER.warn("A duplicate product with the same ID [{}] already exists.", id);
|
||||
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.edi.learn.axon.common.config;
|
||||
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.ServerAddress;
|
||||
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
|
||||
import org.axonframework.mongo.eventsourcing.eventstore.DefaultMongoTemplate;
|
||||
import org.axonframework.mongo.eventsourcing.eventstore.MongoEventStorageEngine;
|
||||
import org.axonframework.mongo.eventsourcing.eventstore.MongoFactory;
|
||||
import org.axonframework.mongo.eventsourcing.eventstore.MongoTemplate;
|
||||
import org.axonframework.mongo.eventsourcing.eventstore.documentperevent.DocumentPerEventStorageStrategy;
|
||||
import org.axonframework.serialization.Serializer;
|
||||
import org.axonframework.serialization.json.JacksonSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class CommandRepositoryConfiguration {
|
||||
|
||||
@Value("${mongodb.url}")
|
||||
private String mongoUrl;
|
||||
|
||||
@Value("${mongodb.dbname}")
|
||||
private String mongoDbName;
|
||||
|
||||
@Value("${mongodb.events.collection.name}")
|
||||
private String eventsCollectionName;
|
||||
|
||||
@Value("${mongodb.events.snapshot.collection.name}")
|
||||
private String snapshotCollectionName;
|
||||
|
||||
@Bean
|
||||
public Serializer axonJsonSerializer() {
|
||||
return new JacksonSerializer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EventStorageEngine eventStorageEngine(){
|
||||
return new MongoEventStorageEngine(
|
||||
axonJsonSerializer(),null, axonMongoTemplate(), new DocumentPerEventStorageStrategy());
|
||||
}
|
||||
|
||||
@Bean(name = "axonMongoTemplate")
|
||||
public MongoTemplate axonMongoTemplate() {
|
||||
MongoTemplate template = new DefaultMongoTemplate(mongoClient(), mongoDbName, eventsCollectionName, snapshotCollectionName);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoClient mongoClient(){
|
||||
MongoFactory mongoFactory = new MongoFactory();
|
||||
mongoFactory.setMongoAddresses(Arrays.asList(new ServerAddress(mongoUrl)));
|
||||
return mongoFactory.createMongo();
|
||||
}
|
||||
|
||||
/*@Bean
|
||||
public SagaStore sagaStore(){
|
||||
org.axonframework.mongo.eventhandling.saga.repository.MongoTemplate mongoTemplate =
|
||||
new org.axonframework.mongo.eventhandling.saga.repository.DefaultMongoTemplate(mongoClient());
|
||||
return new MongoSagaStore(mongoTemplate, axonJsonSerializer());
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.edi.learn.axon.common.domain;
|
||||
|
||||
import org.axonframework.common.Assert;
|
||||
import org.axonframework.common.IdentifierFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class OrderId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -4163440749566043686L;
|
||||
|
||||
private final String identifier;
|
||||
private final int hashCode;
|
||||
|
||||
public OrderId() {
|
||||
this.identifier = IdentifierFactory.getInstance().generateIdentifier();
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
public OrderId(String identifier) {
|
||||
Assert.notNull(identifier, ()->"Identifier may not be null");
|
||||
this.identifier = identifier;
|
||||
this.hashCode = identifier.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof OrderId)) return false;
|
||||
|
||||
OrderId orderId = (OrderId) o;
|
||||
|
||||
return identifier.equals(orderId.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return identifier.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.identifier;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.edi.learn.axon.common.domain;
|
||||
|
||||
/**
|
||||
* Created by Edison on 2017/3/9.
|
||||
*/
|
||||
public class OrderProduct {
|
||||
private String id;
|
||||
private String name;
|
||||
private long price;
|
||||
private int amount;
|
||||
|
||||
public OrderProduct(String id, String name, long price, int amount) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
import com.edi.learn.axon.common.domain.OrderId;
|
||||
import com.edi.learn.axon.common.domain.OrderProduct;
|
||||
import org.axonframework.commandhandling.TargetAggregateIdentifier;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class OrderCreatedEvent {
|
||||
|
||||
@TargetAggregateIdentifier
|
||||
private OrderId orderId;
|
||||
private String username;
|
||||
private Map<String, OrderProduct> products;
|
||||
|
||||
public OrderCreatedEvent() {
|
||||
}
|
||||
|
||||
public OrderCreatedEvent(OrderId orderId, String username, Map<String, OrderProduct> products) {
|
||||
this.orderId = orderId;
|
||||
this.username = username;
|
||||
this.products = products;
|
||||
}
|
||||
|
||||
public OrderId getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public Map<String, OrderProduct> getProducts() {
|
||||
return products;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.edi.learn.axon.common.events;
|
||||
|
||||
|
||||
public class ProductCreatedEvent {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private long price;
|
||||
private int stock;
|
||||
|
||||
/**
|
||||
* If no empty args constructor, jackson will throw serialization exceptions
|
||||
*/
|
||||
public ProductCreatedEvent() {
|
||||
System.out.println("Product event created");
|
||||
}
|
||||
|
||||
public ProductCreatedEvent(String id, String name, long price, int stock) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public int getStock() {
|
||||
return stock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.edi.learn.axon.common.util;
|
||||
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Component
|
||||
public class FastJsonConverter {
|
||||
|
||||
@Bean
|
||||
public HttpMessageConverters fastJsonHttpMessageConverters() {
|
||||
FastJsonHttpMessageConverter4 fastConverter = new FastJsonHttpMessageConverter4();
|
||||
List<MediaType> supportedMediaTypes = new ArrayList<>();
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
|
||||
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
|
||||
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
|
||||
fastConverter.setFastJsonConfig(fastJsonConfig);
|
||||
HttpMessageConverter<?> converter = fastConverter;
|
||||
return new HttpMessageConverters(converter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.edi.learn.axon.main;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {"com.edi.learn"})
|
||||
@EntityScan(basePackages = {"com.edi.learn",
|
||||
"org.axonframework.eventsourcing.eventstore.jpa",
|
||||
"org.axonframework.eventhandling.saga.repository.jpa",
|
||||
"org.axonframework.eventhandling.tokenstore.jpa"})
|
||||
@EnableJpaRepositories(basePackages = {"com.edi.learn.axon.query"})
|
||||
public class Application {
|
||||
|
||||
private static final Logger LOGGER = getLogger(Application.class);
|
||||
|
||||
public static void main(String args[]){
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.edi.learn.axon.query.entries;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/15.
|
||||
*/
|
||||
@Entity
|
||||
public class OrderEntry {
|
||||
@Id
|
||||
private String id;
|
||||
@Column
|
||||
private String username;
|
||||
@Column
|
||||
private double payment;
|
||||
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
@JoinColumn(name = "order_id")
|
||||
@MapKey(name = "id")
|
||||
private Map<String, OrderProductEntry> products;
|
||||
|
||||
public OrderEntry() {
|
||||
}
|
||||
|
||||
public OrderEntry(String id, String username, Map<String, OrderProductEntry> products) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.payment = payment;
|
||||
this.products = products;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public double getPayment() {
|
||||
return payment;
|
||||
}
|
||||
|
||||
public void setPayment(double payment) {
|
||||
this.payment = payment;
|
||||
}
|
||||
|
||||
public Map<String, OrderProductEntry> getProducts() {
|
||||
return products;
|
||||
}
|
||||
|
||||
public void setProducts(Map<String, OrderProductEntry> products) {
|
||||
this.products = products;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.edi.learn.axon.query.entries;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/15.
|
||||
*/
|
||||
@Entity
|
||||
public class OrderProductEntry {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long jpaId;
|
||||
private String id;
|
||||
@Column
|
||||
private String name;
|
||||
@Column
|
||||
private long price;
|
||||
@Column
|
||||
private int amount;
|
||||
|
||||
public OrderProductEntry() {
|
||||
}
|
||||
|
||||
public OrderProductEntry(String id, String name, long price, int amount) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getJpaId() {
|
||||
return jpaId;
|
||||
}
|
||||
|
||||
public void setJpaId(Long jpaId) {
|
||||
this.jpaId = jpaId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public long getPrice() {
|
||||
return price;
|
||||
}
|
||||
public void setPrice(long price) {
|
||||
this.price = price;
|
||||
}
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
public void setAmount(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.edi.learn.axon.query.entries;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* Remember to add {@code EntityScan }annotation in your start application.
|
||||
* <br>
|
||||
* Meanwhile, you need to add some packages in Axon Framework to make it work.
|
||||
* <br>
|
||||
* Created by Edison Xu on 2017/3/14.
|
||||
*/
|
||||
@Entity
|
||||
public class ProductEntry {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
@Column
|
||||
private String name;
|
||||
@Column
|
||||
private long price;
|
||||
@Column
|
||||
private int stock;
|
||||
|
||||
public ProductEntry() {
|
||||
}
|
||||
|
||||
public ProductEntry(String id, String name, long price, int stock) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getStock() {
|
||||
return stock;
|
||||
}
|
||||
|
||||
public void setStock(int stock) {
|
||||
this.stock = stock;
|
||||
}
|
||||
|
||||
public int getPrice() {
|
||||
return (int)price;
|
||||
}
|
||||
|
||||
public void setPrice(long price) {
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.edi.learn.axon.query.handlers;
|
||||
|
||||
import com.edi.learn.axon.common.events.OrderCreatedEvent;
|
||||
import com.edi.learn.axon.query.entries.OrderEntry;
|
||||
import com.edi.learn.axon.query.entries.OrderProductEntry;
|
||||
import com.edi.learn.axon.query.repository.OrderEntryRepository;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* This event handler is used to update the repository data of the query side in the CQRS.
|
||||
* Created by Edison Xu on 2017/3/15.
|
||||
*/
|
||||
@Component
|
||||
public class OrderEventHandler {
|
||||
|
||||
private static final Logger LOGGER = getLogger(OrderEventHandler.class);
|
||||
|
||||
@Autowired
|
||||
private OrderEntryRepository repository;
|
||||
|
||||
@EventHandler
|
||||
public void on(OrderCreatedEvent event){
|
||||
Map<String, OrderProductEntry> map = new HashMap<>();
|
||||
event.getProducts().forEach((id, product)->{
|
||||
map.put(id,
|
||||
new OrderProductEntry(
|
||||
product.getId(),
|
||||
product.getName(),
|
||||
product.getPrice(),
|
||||
product.getAmount()));
|
||||
});
|
||||
OrderEntry order = new OrderEntry(event.getOrderId().toString(), event.getUsername(), map);
|
||||
repository.save(order);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.edi.learn.axon.query.handlers;
|
||||
|
||||
import com.edi.learn.axon.common.events.ProductCreatedEvent;
|
||||
import com.edi.learn.axon.query.entries.ProductEntry;
|
||||
import com.edi.learn.axon.query.repository.ProductEntryRepository;
|
||||
import org.axonframework.eventhandling.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* This event handler is used to update the repository data of the query side in the CQRS.
|
||||
* Created by Edison Xu on 2017/3/7.
|
||||
*/
|
||||
@Component
|
||||
public class ProductEventHandler {
|
||||
|
||||
private static final Logger LOGGER = getLogger(ProductEventHandler.class);
|
||||
|
||||
@Autowired
|
||||
ProductEntryRepository repository;
|
||||
|
||||
@EventHandler
|
||||
public void on(ProductCreatedEvent event){
|
||||
// update the data in the cache or db of the query side
|
||||
LOGGER.debug("repository data is updated");
|
||||
repository.save(new ProductEntry(event.getId(), event.getName(), event.getPrice(), event.getStock()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.edi.learn.axon.query.repository;
|
||||
|
||||
import com.edi.learn.axon.query.entries.OrderEntry;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
/**
|
||||
* Created by Edison Xu on 2017/3/15.
|
||||
*/
|
||||
@RepositoryRestResource(collectionResourceRel = "orders", path = "orders")
|
||||
public interface OrderEntryRepository extends PagingAndSortingRepository<OrderEntry, String> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.edi.learn.axon.query.repository;
|
||||
|
||||
import com.edi.learn.axon.query.entries.ProductEntry;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
/**
|
||||
* Remember to add {@code EnableJpaRepositories} in the start Application
|
||||
* <br>
|
||||
* Created by Edison Xu on 2017/3/14.
|
||||
*/
|
||||
@RepositoryRestResource(collectionResourceRel = "products", path = "products")
|
||||
public interface ProductEntryRepository extends PagingAndSortingRepository<ProductEntry, String> {
|
||||
|
||||
}
|
||||
28
lesson-4/src/main/resources/application.properties
Normal file
28
lesson-4/src/main/resources/application.properties
Normal file
@@ -0,0 +1,28 @@
|
||||
# Datasource configuration
|
||||
# spring.datasource.url=jdbc:h2:mem:exploredb
|
||||
# spring.datasource.driverClassName=org.h2.Driver
|
||||
# spring.datasource.username=sa
|
||||
# spring.datasource.password=
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/productorder
|
||||
spring.datasource.driverClassName=com.mysql.jdbc.Driver
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=Welcome1
|
||||
spring.datasource.validation-query=SELECT 1;
|
||||
spring.datasource.initial-size=2
|
||||
spring.datasource.sql-script-encoding=UTF-8
|
||||
|
||||
|
||||
|
||||
# spring.jpa.database=h2
|
||||
spring.jpa.database=mysql
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
|
||||
# mongo
|
||||
mongodb.url=localhost
|
||||
mongodb.port=27017
|
||||
# mongodb.username=
|
||||
# mongodb.password=
|
||||
mongodb.dbname=axon
|
||||
mongodb.events.collection.name=events
|
||||
mongodb.events.snapshot.collection.name=snapshots
|
||||
17
lesson-4/src/main/resources/logback.xml
Normal file
17
lesson-4/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<logger name="com.edi.learn" level="debug" />
|
||||
<logger name="org.axonframework" level="info" />
|
||||
</configuration>
|
||||
131
pom.xml
Normal file
131
pom.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?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 http://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>1.5.2.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.edi.learn</groupId>
|
||||
<artifactId>sbs-axon</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<modules>
|
||||
<module>lesson-1</module>
|
||||
<module>lesson-2</module>
|
||||
<module>lesson-3</module>
|
||||
<module>lesson-4</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<axon.version>3.0.4</axon.version>
|
||||
<slf4j.version>1.7.23</slf4j.version>
|
||||
<logback.version>1.2.1</logback.version>
|
||||
<springframework.version>4.3.4.RELEASE</springframework.version>
|
||||
|
||||
<!-- Maven plugins -->
|
||||
<maven.compiler.plugin>3.1</maven.compiler.plugin>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-core</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-spring-boot-autoconfigure</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-amqp</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>org.axonframework</groupId>
|
||||
<artifactId>axon-mongo</artifactId>
|
||||
<version>${axon.version}</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-access</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.24</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven.compiler.plugin}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Reference in New Issue
Block a user