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

这篇具有很好参考价值的文章主要介绍了领域驱动设计之银行转账:Wow框架实战。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

银行转账流程

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

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

运行案例

  • 运行 TransferExampleServer.java
  • 查看 Swagger-UI : http://localhost:8080/swagger-ui.html
  • 执行 API 测试:Transfer.http

自动生成 API 端点

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

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

模块划分

模块 说明
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());
    }
}

单元测试

借助 Wow 单元测试套件,可以轻松的编写聚合根和 Saga 的单元测试。从而提升代码覆盖率,保证代码质量。

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

使用 aggregateVerifier 进行聚合根单元测试,可以有效的减少单元测试的编写工作量。

Account 聚合根单元测试

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

    @Test
    fun prepare() {
        aggregateVerifier<Account, AccountState>()
            .given(AccountCreated("name", 100))
            .`when`(Prepare("name", 100))
            .expectEventType(AmountLocked::class.java, Prepared::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(0))
            }
            .verify()
    }

    @Test
    fun entry() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100))
            .`when`(Entry(aggregateId, "sourceId", 100))
            .expectEventType(AmountEntered::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(200))
            }
            .verify()
    }

    @Test
    fun entryGivenFrozen() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AccountFrozen(""))
            .`when`(Entry(aggregateId, "sourceId", 100))
            .expectEventType(EntryFailed::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.isFrozen, equalTo(true))
            }
            .verify()
    }

    @Test
    fun confirm() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AmountLocked(100))
            .`when`(Confirm(aggregateId, 100))
            .expectEventType(Confirmed::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(0))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(false))
            }
            .verify()
    }

    @Test
    fun unlockAmount() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AmountLocked(100))
            .`when`(UnlockAmount(aggregateId, 100))
            .expectEventType(AmountUnlocked::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(false))
            }
            .verify()
    }

    @Test
    fun freezeAccount() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100))
            .`when`(FreezeAccount(""))
            .expectEventType(AccountFrozen::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(true))
            }
            .verify()
    }
}

使用 sagaVerifier 进行 Saga 单元测试,可以有效的减少单元测试的编写工作量。

TransferSaga 单元测试文章来源地址https://www.toymoban.com/news/detail-746672.html

internal class TransferSagaTest {

    @Test
    fun onPrepared() {
        val event = Prepared("to", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<Entry> {
                assertThat(it.id, equalTo(event.to))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }

    @Test
    fun onAmountEntered() {
        val event = AmountEntered("sourceId", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<Confirm> {
                assertThat(it.id, equalTo(event.sourceId))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }

    @Test
    fun onEntryFailed() {
        val event = EntryFailed("sourceId", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<UnlockAmount> {
                assertThat(it.id, equalTo(event.sourceId))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }
}

到了这里,关于领域驱动设计之银行转账:Wow框架实战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 该怎么用设计测试用例测网上银行转账?

    目录 前言 1、网上银行转账是怎么测的,设计一下测试用例。 回答思路: 2、测试工作的流程?缺陷状态有什么?设计测试用例有几种方法? 修改完以后,有两种处理情况: 3、在项目中找到的经典BUG是什么? 4、定期存款到期自动转存该怎么测?

    2024年02月08日
    浏览(39)
  • 领域驱动设计——DDD领域驱动设计进阶

    进阶篇主要讲解领域事件、DDD 分层架构、几种常见的微服务架构模型以及中台设计思想等内容。如何通过领域事件实现微服务解耦?、怎样进行微服务分层设计?、如何实现层与层之间的服务协作?、通过几种微服务架构模型的对比分析,让你了解领域模型和微服务分层的作

    2024年01月15日
    浏览(52)
  • 《领域驱动设计》:从领域视角深入仓储(Repository)的设计和实现

    一、前言 “ DDD设计的目标是关注领域模型而并非技术来创建更好的软件,假设开发人员构建了一个SQL,并将它传递给基础设施层中的某个查询服务然后根据表数据的结构集取出所需信息,最后将这些信息提供给构造函数或者Factory,开发人员在做这一切的时候早已不把模型看

    2024年02月08日
    浏览(81)
  • 领域驱动设计入门指南

    ​ 领域驱动设计(Domain-Driven Design,简称DDD)是一种软件架构风格,它强调在软件开发过程中紧密关注业务需求和领域知识。本文将简要介绍领域驱动设计的核心概念,帮助人开始学习和实践领域驱动设计。 什么是领域驱动设计? 领域驱动设计是一种软件开发方法,它侧重

    2024年02月11日
    浏览(38)
  • DDD领域驱动设计(六)

    领域对象需要资源存储。存储手段多样化,常见就是数据库,分布式缓存,localCache.资源库的作用,就是对领域的存储和访问进行统一管理对象。在抽奖平台中。通过下面这种方式组织资源库。

    2024年01月24日
    浏览(72)
  • 领域驱动设计四论

        经过多年的研究与思考,实践与总结,本人逐渐对 DDD 有所领悟,本文以一个较短的篇幅,提纲挈领地梳理出 DDD 的核心脉络,希望与各位做一探讨。 1776 年亚当斯密发表《国富论》,标志着经济学的诞生。2004 年,一本名为《领域驱动设计·软件核心复杂性应对之道》的

    2024年02月08日
    浏览(50)
  • 领域驱动设计之认知篇

    作为技术人,都有一个成为大牛的梦。 有些人可以通过自己掌握了比较底层、有深度、有难度的技术来证明自己的能力。 但对于绝大多数的应用研发工程师来说,其大部分的时间精力,会被消耗在读不懂、讲不清的屎山代码中,以及复杂多变的业务迭代中。很少会有需要去

    2024年02月08日
    浏览(43)
  • 05.领域驱动设计:掌握领域事件,解耦微服务的关键

    目录 1、概述 2、领域事件 2.1 如何识别领域事件 1.微服务内的领域事件 2.微服务之间的领域事件 3、领域事件总体架构 3.1 事件构建和发布 3.2 事件数据持久化 3.3 事件总线 (EventBus) 3.4 消息中间件 3.5 事件接收和处理 4、案例 5、总结 1、概述 在事件风暴(Event Storming)时,我们

    2024年02月22日
    浏览(48)
  • 领域驱动设计(Domain Driven Design)之建立领域模型

    在实际项目中,模型设计者往往过早陷入具体构造块类型的识别,比如实体、聚合、领域服务,而忽略了领域模型表达领域概念的目的。我们应该基于领域概念设计领域模型,然后再采用合适的模式降低领域模型的复杂度,进一步增加领域模型的表达能力。 领域模型的作用,

    2024年02月03日
    浏览(37)
  • 聊一聊对领域驱动设计中“领域”这个词语的理解与分析方法

    百度百科对领域的解释: 领域具体指一种特定的范围或区域 领域一般指的是业务的问题域,领域是有边界的,边界内,规定了我们要做什么,要做的范围,软件项目从开始到交付的过程中, 所有涵盖的业务,每个业务模块或者方向都有自己的业务范围和问题 比如做家装行业

    2023年04月14日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包