银行账户转账案例是一个经典的领域驱动设计(ddd)应用场景。
接下来我们通过一个简单的银行账户转账案例,来了解如何使用 wow 进行领域驱动设计以及服务开发。
银行转账流程
- 准备转账(prepare): 用户发起转账请求,触发 prepare 步骤。这个步骤会向源账户发送准备转账的请求。
- 校验余额(checkbalance): 源账户在收到准备转账请求后,会执行校验余额的操作,确保账户有足够的余额进行转账。
- 锁定金额(lockamount): 如果余额足够,源账户会锁定转账金额,防止其他操作干扰。
- 入账(entry): 接着,转账流程进入到目标账户,执行入账操作。
- 确认转账(confirm): 如果入账成功,确认转账;否则,执行解锁金额操作。
- 成功路径(success): 如果一切顺利,完成转账流程。
- 失败路径(fail): 如果入账失败,执行解锁金额操作,并处理失败情况。
运行案例
- 运行 transferexampleserver.java
- 查看 swagger-ui : http://localhost:8080/swagger-ui.html
- 执行 api 测试:transfer.http
自动生成 api 端点
模块划分
模块 | 说明 |
---|---|
example-transfer-api | api 层,定义聚合命令(command)、领域事件(domain event)以及查询视图模型(query view model),这个模块充当了各个模块之间通信的“发布语言”。 |
example-transfer-domain | 领域层,包含聚合根和业务约束的实现。聚合根:领域模型的入口点,负责协调领域对象的操作。业务约束:包括验证规则、领域事件的处理等。 |
example-transfer-server | 宿主服务,应用程序的启动点。负责整合其他模块,并提供应用程序的入口。涉及配置依赖项、连接数据库、启动 api 服务 |
领域建模
账户聚合根
状态聚合根(accountstate
)与命令聚合根(account
)分离设计保证了在执行命令过程中,不会修改状态聚合根的状态。
状态聚合根(accountstate
)建模
public class accountstate implements identifier {
private final string id;
private string name;
/**
* 余额
*/
private long balanceamount = 0l;
/**
* 已锁定金额
*/
private long lockedamount = 0l;
/**
* 账号已冻结标记
*/
private boolean frozen = false;
@jsoncreator
public accountstate(@jsonproperty("id") string id) {
this.id = id;
}
@notnull
@override
public string getid() {
return id;
}
public string getname() {
return name;
}
public long getbalanceamount() {
return balanceamount;
}
public long getlockedamount() {
return lockedamount;
}
public boolean isfrozen() {
return frozen;
}
void onsourcing(accountcreated accountcreated) {
this.name = accountcreated.name();
this.balanceamount = accountcreated.balance();
}
void onsourcing(amountlocked amountlocked) {
balanceamount = balanceamount - amountlocked.amount();
lockedamount = lockedamount + amountlocked.amount();
}
void onsourcing(amountentered amountentered) {
balanceamount = balanceamount + amountentered.amount();
}
void onsourcing(confirmed confirmed) {
lockedamount = lockedamount - confirmed.amount();
}
void onsourcing(amountunlocked amountunlocked) {
lockedamount = lockedamount - amountunlocked.amount();
balanceamount = balanceamount + amountunlocked.amount();
}
void onsourcing(accountfrozen accountfrozen) {
this.frozen = true;
}
}
命令聚合根(account
)建模
@statictenantid
@aggregateroot
public class account {
private final accountstate state;
public account(accountstate state) {
this.state = state;
}
accountcreated oncommand(createaccount createaccount) {
return new accountcreated(createaccount.name(), createaccount.balance());
}
@oncommand(returns = {amountlocked.class, prepared.class})
list<?> oncommand(prepare prepare) {
checkbalance(prepare.amount());
return list.of(new amountlocked(prepare.amount()), new prepared(prepare.to(), prepare.amount()));
}
private void checkbalance(long amount) {
if (state.isfrozen()) {
throw new illegalstateexception("账号已冻结无法转账.");
}
if (state.getbalanceamount() < amount) {
throw new illegalstateexception("账号余额不足.");
}
}
object oncommand(entry entry) {
if (state.isfrozen()) {
return new entryfailed(entry.sourceid(), entry.amount());
}
return new amountentered(entry.sourceid(), entry.amount());
}
confirmed oncommand(confirm confirm) {
return new confirmed(confirm.amount());
}
amountunlocked oncommand(unlockamount unlockamount) {
return new amountunlocked(unlockamount.amount());
}
accountfrozen oncommand(freezeaccount freezeaccount) {
return new accountfrozen(freezeaccount.reason());
}
}
转账流程管理器(transfersaga
)
转账流程管理器(transfersaga
)负责协调处理转账的事件,并生成相应的命令。
onevent(prepared)
: 订阅转账已准备就绪事件(prepared
),并生成入账命令(entry
)。onevent(amountentered)
: 订阅转账已入账事件(amountentered
),并生成确认转账命令(confirm
)。onevent(entryfailed)
: 订阅转账入账失败事件(entryfailed
),并生成解锁金额命令(unlockamount
)。
@statelesssaga
public class transfersaga {
entry onevent(prepared prepared, aggregateid aggregateid) {
return new entry(prepared.to(), aggregateid.getid(), prepared.amount());
}
confirm onevent(amountentered amountentered) {
return new confirm(amountentered.sourceid(), amountentered.amount());
}
unlockamount onevent(entryfailed entryfailed) {
return new unlockamount(entryfailed.sourceid(), entryfailed.amount());
}
}
单元测试
internal class accountktest {
@test
fun createaccount() {
aggregateverifier<account, accountstate>()
.given()
.`when`(createaccount("name", 100))
.expecteventtype(accountcreated::class.java)
.expectstate {
assertthat(it.name, equalto("name"))
assertthat(it.balanceamount, equalto(100))
}
.verify()
}
}
发表评论