首页 > 其他分享 >领域驱动设计之银行转账:Wow框架实战

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

时间:2023-11-21 17:37:02浏览次数:20  
标签:转账 实战 return balanceAmount Wow amount new public

银行账户转账案例

银行账户转账案例是一个经典的领域驱动设计(DDD)应用场景。接下来我们通过一个简单的银行账户转账案例,来了解如何使用 Wow 进行领域驱动设计以及服务开发。

银行转账流程

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

Saga-Transfer

运行案例

自动生成 API 端点

运行之后,访问 Swagger-UI : http://localhost:8080/swagger-ui.html
该 RESTful API 端点是由 Wow 自动生成的,无需手动编写。

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

  • 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()
    }
}

标签:转账,实战,return,balanceAmount,Wow,amount,new,public
From: https://www.cnblogs.com/Ahoo-Wang/p/Wow-Transfer.html

相关文章

  • YonBuilder应用构建实战案例-体检管理(上)
    YonBuilder平台为开发者提供无代码和低代码的可视化开发能力,并结合开发资产复用,实现快速、简单的应用构建。体检管理是应用构建的典型案例之一,接下来将用两篇文章来讲述此案例。一.业务流程二.功能概述三.使用能力介绍四.实操教程(一).体检套餐1.业务对象创建体检套餐业务对象,以下......
  • YonBuilder应用构建实战案例-体检管理(下)
    紧接着上篇文章,我们将继续讲解体检管理案例操作。体检单一、业务对象创建体检单业务对象,以下是业务对象下的实体为主子孙结构1.体检单2.体检项目_体检单子表二、页面建模1、根据业务对象创建一主多子结构单据命名为体检单2、设置主表字段不允许修改组织、客户、累计出库年月、单据......
  • 2023版 STM32实战3 按键外部中断(电路与代码都讲解)
    常规电路(带上拉电阻)阻值可选3.3/4.7/5.1/10单位K  偷懒电路利用GPIO内部的上拉模式 代码(直接拷贝使用)这是一个外部中断控制变量a增加减少的demo为了新手方便我直接都写在了main.c文件 #include"stm32f10x.h"u8keyflag=0;u8a=0;voidKEY_Init(void){......
  • 决策树C4.5算法的技术深度剖析、实战解读
    在本篇深入探讨的文章中,我们全面分析了C4.5决策树算法,包括其核心原理、实现流程、实战案例,以及与其他流行决策树算法(如ID3、CART和RandomForests)的比较。文章不仅涵盖了丰富的理论细节和实际应用,还提出了独特的洞见,旨在帮助读者全面了解C4.5算法的优缺点和应用场景。关注Tech......
  • 建设招商农业邮政工商一体化模拟器,回执单转账余额截图都可以,JAVA模拟代码!
    闲着用JAVA研究了一个模拟器,但是我加了水印的,这个你做不了啥坏事,主要就是学习一下我写的代码和代码的实现逻辑,包括“主类和模块”还有“截图生成模块”以及“信息的输入和处理”三大模块,说复杂不复杂,说简单也不简单,下面框架图是网上找的,需要和代码相互结合才能实现具体的效果。UI......
  • 农业建设工商招商邮政银行一体化模拟器,余额回执单转账,JDBC或JPA集成开源
    JDBC或JPA集成开发的一个模拟器,一体化的,仅供娱乐使用,代码我之前就开发好了,模版当然也是网上找的,非常多,我这个加了水印,不加水印我也不敢发,平台也不会通过,然后代码是JAVA+JDBC或JPA集成开发的,这个不带db数据库,所以打开户修改重启软件不带记忆功能的,我就分享下代码逻辑,不分享软件的下......
  • FlutterApp实战·第01天:Flutter安装和配置
    Flutter安装文档:官方文档:https://docs.flutter.dev/get-started/install中文文档:https://flutter.cn/docs/get-started/installDart升级Dart安装参考前面文章(Dart安装和初体验):https://ntopic.cn/p/2023092301后续学习我们采用最新Flutter版本,因此建议升级Dart最新版本(当前......
  • 探索人工智能的世界:构建智能问答系统之实战篇
    引言前面我们已经做好了必要的准备工作,包括对相关知识点的了解以及环境的安装。今天我们将重点关注代码方面的内容。如果你已经具备了Java编程基础,那么理解Python语法应该不会成为问题,毕竟只是语法的差异而已。随着时间的推移,你自然会逐渐熟悉和掌握这门语言。现在让我们开始吧!环......
  • 2023版 STM32实战1 LED灯驱动(电路与代码都讲解)
    电路图常规画法(带限流电阻计算公式)设LED电流为20mA(统一单位为0.02A)电压为3.3V限流电阻=(电源电压-负载正向工作电压)/工作电流限流电阻=(5V-3.3V)/0.02mA=1.7/V0.02A=85R省事画法(直接用IO输出)代码(直接拷贝使用)找对引脚!!!这是一个LED闪烁的demo为了新手方便我直接都写在了main.c......
  • Angular 应用实现 Lazy Load(懒加载)的项目实战经验分享
    笔者之前两篇掘金社区文章,分别介绍了企业级Angular应用开启PWA特性和服务器端渲染,从而提升用户体验的两种设计思路:Angular应用支持PWA(ProgressiveWebApplication)特性的开发步骤分享基于AngularUniversal引擎进行服务器端渲染的前端应用StateTransfer故障排查......