分布式系统开发实战:微服务架构,实战:基于CQRS微服务通信

1496 篇文章 10 订阅
1494 篇文章 14 订阅

实战:基于CQRS微服务通信

Axon Framework是一个适用于Java的、基于事件驱动的轻量级CQRS框架,既支持直接持久化Aggregate状态,也支持采用EventSourcing。

Axon Framework的应用架构如图9-6所示。

图9-6 Axon Framework应用架构

本节,我们将基于Axon Framework来实现一个CQRS应用“axon-cqrs”。该应用展示了:“开通银行账户,取钱”这样一个逻辑业务。

配置

配置依赖如下。

<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>5.2.3.RELEASE</spring.version><axon.version>3.4.3</axon.version><log4j.version>2.13.0</log4j.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><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.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-jcl</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>${log4j.version}</version></dependency></dependencies>

其中,采用了Axon Framework以及日志框架。

Aggregate

BankAccount是DDD中的Aggregate,代表了银行账户。

package com.waylau.axon.cqrs.command.aggregates;
importstatic org.axonframework.commandhandling.model.AggregateLifecycle.apply;
importstatic org.slf4j.LoggerFactory.getLogger;
import java.math.BigDecimal;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.commandhandling.model.AggregateIdentifier;
import org.axonframework.eventhandling.EventHandler;
import org.slf4j.Logger;
import com.waylau.axon.cqrs.command.commands.CreateAccountCommand;
import com.waylau.axon.cqrs.command.commands.WithdrawMoneyCommand;
import com.waylau.axon.cqrs.common.domain.AccountId;
import com.waylau.axon.cqrs.common.events.CreateAccountEvent;
import com.waylau.axon.cqrs.common.events.WithdrawMoneyEvent;
publicclassBankAccount{
privatestaticfinal Logger LOGGER = getLogger(BankAccount.class);@AggregateIdentifierprivate AccountId accountId;
private String accountName;
private BigDecimal balance;
publicBankAccount(){
}
@CommandHandlerpublicBankAccount(CreateAccountCommand command){
LOGGER.debug("Construct a new BankAccount");
apply(new CreateAccountEvent(command.getAccountId(),
command.getAccountName(),
command.getAmount()));
}
@CommandHandlerpublicvoidhandle(WithdrawMoneyCommand command){
apply(new WithdrawMoneyEvent(command.getAccountId(),
command.getAmount()));
}
@EventHandlerpublicvoidon(CreateAccountEvent 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);
}
@EventHandlerpublicvoidon(WithdrawMoneyEvent 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);
}
}
}

其中,注解@CommandHandler和@EventHandler代表了对Command和Event的处理。@AggregateIdentifier代表的AccountId,是一个Aggregate的全局唯一的标识符。

AccountId声明如下。

package com.waylau.axon.cqrs.common.domain;
import org.axonframework.common.Assert;
import org.axonframework.common.IdentifierFactory;
import java.io.Serializable;
publicclassAccountIdimplementsSerializable{privatestaticfinallong serialVersionUID = 7119961474083133148L;
privatefinal String identifier;
privatefinalint hashCode;
publicAccountId(){
this.identifier =
IdentifierFactory.getInstance().generateIdentifier();
this.hashCode = identifier.hashCode();
}
publicAccountId(String identifier){
Assert.notNull(identifier, ()->"Identifier may not be null");
this.identifier = identifier;
this.hashCode = identifier.hashCode();
}
@Overridepublicbooleanequals(Object o){
if (this == o) returntrue;
if (o == null || getClass() != o.getClass()) returnfalse;
AccountId accountId = (AccountId) o;
return identifier.equals(accountId.identifier);
}
@OverridepublicinthashCode(){
return hashCode;
}
@Overridepublic String toString(){
return identifier;
}
}

其中:

·实现equal和hashCode方法,因为它会被拿来与其他标识对比。

·实现toString方法,其结果也应该是全局唯一的。

·实现Serializable接口以表明可序列化。

Command

该应用共有两个Command:创建账户和取钱。

CreateAccountCommand代表了创建账户。

package com.waylau.axon.cqrs.command.commands;
import com.waylau.axon.cqrs.common.domain.AccountId;
publicclassCreateAccountCommand{
private AccountId accountId;
private String accountName;
privatelong amount;publicCreateAccountCommand(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;
}
publiclonggetAmount(){
return amount;
}
}

WithdrawMoneyCommand代表了取钱。

package com.waylau.axon.cqrs.command.commands;
import org.axonframework.commandhandling.TargetAggregateIdentifier;
import com.waylau.axon.cqrs.common.domain.AccountId;
publicclassWithdrawMoneyCommand{
@TargetAggregateIdentifierprivate AccountId accountId;
privatelong amount;
publicWithdrawMoneyCommand(AccountId accountId, long amount){
this.accountId = accountId;
this.amount = amount;
}
public AccountId getAccountId(){
return accountId;
}
publiclonggetAmount(){
return amount;
}
}

Event

Event是系统中发生任何改变时产生的事件类,典型的Event就是对Aggregate状态的修改。与Command相对应,会有两个事件。

CreateAccountEvent代表了创建账户的Event。

package com.waylau.axon.cqrs.common.events;import com.waylau.axon.cqrs.common.domain.AccountId;
publicclassCreateAccountEvent{
private AccountId accountId;
private String accountName;
privatelong amount;
publicCreateAccountEvent(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;
}
publiclonggetAmount(){
return amount;
}
}
WithdrawMoneyEvent代表了取钱的Event。
package com.waylau.axon.cqrs.common.events;
import com.waylau.axon.cqrs.common.domain.AccountId;
publicclassWithdrawMoneyEvent{
private AccountId accountId;
privatelong amount;
publicWithdrawMoneyEvent(AccountId accountId, long amount){
this.accountId = accountId;
this.amount = amount;
}
public AccountId getAccountId(){
return accountId;
}
publiclonggetAmount(){
return amount;
}
}

测试

为了方便测试,我们创建一个Application类。

package com.waylau.axon.cqrs;
importstatic org.slf4j.LoggerFactory.getLogger;
import org.axonframework.config.Configuration;
import org.axonframework.config.DefaultConfigurer;import org.axonframework.eventsourcing.eventstore.inmemory.
InMemoryEventStorageEngine;
import org.slf4j.Logger;
import com.waylau.axon.cqrs.command.aggregates.BankAccount;
import com.waylau.axon.cqrs.command.commands.CreateAccountCommand;
import com.waylau.axon.cqrs.command.commands.WithdrawMoneyCommand;
import com.waylau.axon.cqrs.common.domain.AccountId;
publicclassApplication{
privatestaticfinal Logger LOGGER = getLogger(Application.class);
publicstaticvoidmain(String args[])throws InterruptedException{
LOGGER.info("Application is start.");
Configuration config = DefaultConfigurer.defaultConfiguration()
.configureAggregate(BankAccount.class)
.configureEmbeddedEventStore(c -> newInMemoryEventStorageEngine())
.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.commandGateway().send(new WithdrawMoneyCommand(id, 500));
// 线程先睡5s,等事件处理完
Thread.sleep(5000L);
LOGGER.info("Application is shutdown.");
}
}

在该类中,我们创建了一个账户,并执行了3次取钱动作。由于账户总额为1000,所以,当执行第3次取钱动作时,预期是会因为余额不足而失败。

运行该Application类,能看到以下输出。

22:11:52.432[main]INFOcom.waylau.axon.cqrs.Application-Applicationisstart.
22:11:52.676[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Accountcc0653ff-afbd-49f5-b868-38aff296611ciscreatedwithbalance 1000
22:11:52.719[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Accountcc0653ff-afbd-49f5-b868-38aff296611ciscreatedwithbalance 1000
22:11:52.725[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Withdraw 500 fromaccountcc0653ff-afbd-49f5-b868-38aff296611c, balanceresult: 500
22:11:52.725[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Accountcc0653ff-afbd-49f5-b868-38aff296611ciscreatedwithbalance 1000
22:11:52.725[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Withdraw 500 fromaccountcc0653ff-afbd-49f5-b868-38aff296611c, balanceresult: 500
22:11:52.727[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Withdraw 500 fromaccountcc0653ff-afbd-49f5-b868-38aff296611c, balanceresult: 0
22:11:52.727[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Accountcc0653ff-afbd-49f5-b868-38aff296611ciscreatedwithbalance 1000
22:11:52.728[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Withdraw 500 fromaccountcc0653ff-afbd-49f5-b868-38aff296611c, balanceresult: 500
22:11:52.728[main]INFOcom.waylau.axon.cqrs.command.aggregates.BankAccount –
Withdraw 500 fromaccountcc0653ff-afbd-49f5-b868-38aff296611c, balanceresult: 022:11:52.728[main]ERRORcom.waylau.axon.cqrs.command.aggregates.BankAccount-Cannotwithdrawmoremoneythanthebalance!
22:11:57.729[main]INFOcom.waylau.axon.cqrs.Application-Applicationisshutdown.

本节示例,可以在axon-cqrs项目下找到。

本章小结

本章介绍了微服务架构的概念及构建微服务常用的技术。同时,也介绍了在微服务中常用的3种通信方式:HTTP、消息、事件驱动。在微服务中,我们可以使用CQRS来降低构建微服务通信的复杂度。

本文给大家讲解的内容是分布式系统开发实战:微服务架构,实战:基于CQRS微服务通信

资源获取:
大家 点赞、收藏、关注、评论啦 、 查看 👇🏻 👇🏻 👇🏻 微信公众号获取联系方式 👇🏻 👇🏻 👇🏻
精彩专栏推荐订阅:下方专栏 👇🏻 👇🏻 👇🏻 👇🏻
每天学四小时:Java+Spring+JVM+分布式高并发,架构师指日可待
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值