it编程 > 软件设计 > 架构设计

领域驱动设计之银行转账:Wow 框架实战

89人参与 2024-08-04 架构设计

银行账户转账案例是一个经典的领域驱动设计(ddd)应用场景。

接下来我们通过一个简单的银行账户转账案例,来了解如何使用 wow 进行领域驱动设计以及服务开发。

银行转账流程

  1. 准备转账(prepare): 用户发起转账请求,触发 prepare 步骤。这个步骤会向源账户发送准备转账的请求。
  2. 校验余额(checkbalance): 源账户在收到准备转账请求后,会执行校验余额的操作,确保账户有足够的余额进行转账。
  3. 锁定金额(lockamount): 如果余额足够,源账户会锁定转账金额,防止其他操作干扰。
  4. 入账(entry): 接着,转账流程进入到目标账户,执行入账操作。
  5. 确认转账(confirm): 如果入账成功,确认转账;否则,执行解锁金额操作。
    1. 成功路径(success): 如果一切顺利,完成转账流程。
    2. 失败路径(fail): 如果入账失败,执行解锁金额操作,并处理失败情况。

saga-transfer

运行案例

自动生成 api 端点

saga-transfer

模块划分

模块 说明
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)负责协调处理转账的事件,并生成相应的命令。

@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()
    }
}
(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

2024 年了,云原生与微服务架构还有什么新鲜事儿?

08-04

吵了6年的数据库话题,会在冯若航这里终结吗?

08-04

缓存有大key?你得知道的一些手段

08-04

Go-Job让你的任务调度不再繁琐

08-04

手把手案例!怎样拿开源的 GPT-2 训练小模型,挑战 GPT-3.5

08-04

纯php编写rtmp服务

08-04

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论