Introduction
In this second part of this article series, we will implement the 命令
module, responsible for application state changes。Final code is in Github.
REST API Layer
我们将有效地从外部层开始对应用程序进行编码,并继续进行内部工作。 的产品控制器
类将负责向端点公开以请求状态更改。
此类的唯一依赖项是Axon的命令Gateway
负责派遣命令
对象。 初始结构为:
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
public ProductController(final 命令Gateway commandGateway) {
this.commandGateway = commandGateway;
}
private CommandGateway commandGateway;
@PostMapping
public CompletableFuture<String> create(@RequestBody 产品DTO dto) {
return null;
}
@PutMapping
public CompletableFuture<String> update(@RequestBody ProductDTO dto) {
return null;
}
}
哪里ProductDTO
类只是一个POJO来映射json
请求。
public class ProductDTO {
private Long id;
private String name;
private int quantity;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
Command model
Command
该应用程序上的实例将表示状态更改的意图。 应用程序状态的子集将由骨料
宾语。 例如,将新产品添加到购物车的意图表示为AddProductCommand
类:
public class AddProductCommand {
public AddProductCommand(
final Long id,
final String name,
final int quantity) {
this.id = id;
this.name = name;
this.quantity = quantity;
}
@Target骨料Identifier
private final Long id;
private final String name;
private final int quantity;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public int getQuantity() {
return quantity;
}
}
哪里Target骨料Identifier
批注用于标识哪个实例骨料
类型应由此命令处理。
现在从我们的派遣此命令RestController
我们只需要实例化它并作为参数传递CommandGateway
发送
方法:
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
public ProductController(final CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
private CommandGateway commandGateway;
@PostMapping
public CompletableFuture<String> create(@RequestBody ProductDTO dto) {
AddProductCommand command = new AddProductCommand(
dto.getId(),
dto.getName(),
dto.getQuantity());
return commandGateway.send(command);
}
// ....
}
骨料
如前所述,骨料
类将负责代表部分应用程序状态以及Command
为此处理Aggregate
。
一个例子Aggregate
可能:
{
"id": 1,
"name": "iPhone",
"quantity": 2
}
翻译成代码,结果是:
@Aggregate
public class 产品汇总 {
@AggregateIdentifier
private Long id;
private String name;
private int quantity;
@命令处理程序
public ProductAggregate(AddProductCommand cmd) {
// Verifies state consistency and applies events
}
}
CommandHandler
上的注释建设者
意味着AddProductCommand
命令用于创建一个新的Aggregate
.
此时,您的应用程序状态尚未更改。 命令处理是执行业务逻辑的位置(即:检查数量是否为正值)并可能适用大事记
这将导致状态改变。
Event model
首先,我们创建AddProductEvent
具有触发我们期望的状态更改所需的属性的类。 在这种情况下,它将与各自的情况非常相似Command
模型。
现在我们将改变ProductAggregate
建设者
派遣AddProductEvent
每当一个AddProductCommand
已发送。 此类也可以充当AddProductEvent
,执行应用程序状态的更改。
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
@Aggregate
public class ProductAggregate {
@AggregateIdentifier
private Long id;
private String name;
private int quantity;
@CommandHandler
public ProductAggregate(AddProductCommand cmd) {
apply(new AddProductEvent(cmd.getId(), cmd.getName(), cmd.getQuantity()));
}
@EventSourcingHandler
public void on(AddProductEvent event) {
this.id = event.getId();
this.name = event.getName();
this.quantity = event.getQuantity();
}
}
到目前为止,我们已经建立了一个检查点,使我们可以测试应用程序并观察发生了什么。 按照终端命令运行指挥部
应用:
docker-compose up -d
./gradlew clean assemble
java -jar commandside/build/libs/commandside.jar
现在,我们可以通过添加新产品来测试端点:
curl -X POST http://localhost:8080/products -H 'Content-Type: application/json' -d '{"id": 1, "name": "iPhone", "quantity": 7}'
我们可以在mongo数据库中验证域事件
集合中存储了一个新事件:
{
"_id":"5e0a2924b813b63783e1e092",
"aggregateIdentifier":"1",
"type":"ProductAggregate",
"sequenceNumber":"0",
"serializedPayload":"<com.example.project.command.addproduct.AddProductEvent><id>1</id><name>iPhone</name><quantity>7</quantity></com.example.project.command.addproduct.AddProductEvent>",
"timestamp":"2019-12-30T16:43:16.851862731Z",
"payloadType":"com.example.project.command.addproduct.AddProductEvent",
"payloadRevision":null,
"serializedMetaData":"<meta-data><entry><string>traceId</string><string>e62c8e0d-7505-4e99-ab7e-84b4619ee159</string></entry><entry><string>correlationId</string><string>e62c8e0d-7505-4e99-ab7e-84b4619ee159</string></entry></meta-data>",
"eventIdentifier":"6eef19d8-b22a-4be6-9fd9-7681a31580b8"
}
对于通过POST请求添加的每个新产品,我们将在域事件
从现在开始。 那就是我们应用程序发生的历史。
Aggregate persistence
除了单个事件的持久性,我们还希望在每次更改时都保持聚合(状态存储的聚合)。 为此,我们只需要添加JPA批注以将我们的聚合类变成一个实体
:
@Aggregate
@Entity // This class can now be mapped to a table
public class ProductAggregate {
@AggregateIdentifier
@Id // Defines the primary key
private Long id;
@Column // Map to a column with same name
private String name;
@Column // Map to a column with same name
private int quantity;
@CommandHandler
public ProductAggregate(AddProductCommand cmd) {
apply(new AddProductEvent(cmd.getId(), cmd.getName(), cmd.getQuantity()));
}
@EventSourcingHandler
public void on(AddProductEvent event) {
this.id = event.getId();
this.name = event.getName();
this.quantity = event.getQuantity();
}
}
现在,使用上述端点在购物车中添加新产品,并检查product_table
在您的Postgres数据库上,以验证存储的新条目是否与所需的汇总匹配。 您的Mongo数据库还应该包含新事件。
Conclusion
现在,在Mongo数据库上,我们具有所有事件的历史记录,可用来了解应用程序如何达到其当前状态。 另一方面,Postgres数据库具有我们可以用来显示给最终用户的数据,例如在结帐屏幕上。
我们可以一路回头,自己实现CQRS和事件源,但幸运的是,我们可以使用Axon的几个注释来达到相同的结果。
from: https://dev.to//fabiothiroki/cqrs-using-java-and-axon-command-module-57h5