当前位置: 首页 > news >正文

DDD记账软件实战四|从0-1设计实现企业级记账微服务

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!

DDD记账软件实战四

前情提要

在实战三中,我们完成了登录系统的设计与实现,运用了策略模式、工厂模式等设计模式,实现了一个可扩展的多种登录方式的认证系统。

架构图

我们已经按照DDD划分了目录并创建了项目结构:

  • starter: 接口层,包括HTTP接口、队列的消费者、DTO、启动类
  • api: 接口层,提供RPC接口,包括外部RPC接口需要使用的DTO、枚举等
  • application:应用服务层,放应用服务,负责编排领域服务、聚合根等。
  • domain:领域服务层,放领域相关的一切信息,领域服务负责编排聚合根,聚合根负责完成自身的业务逻辑。
  • infrastructure: 基础设施层,放配置、仓储、工厂、对外部的请求、发送MQ消息等。
  • common: 放一些公共信息。

技术栈版本:

  • Spring Boot 3.5.4
  • Java 21

如何设计一个记账微服务

在完成用户认证系统后,我们要开始实现核心的业务功能了。对于记账软件来说,记账功能是最核心的模块。

记账功能看似简单,但是涉及的业务逻辑其实不少,比如:

  • 账本管理
    • 账本列表
    • 创建账本
    • 更新账本信息
    • 删除账本
    • 更新账本预算
    • 查询账本预算
    • 查询账本信息
    • 邀请用户加入账本
    • 用户加入账本
    • 账本成员管理
  • 收入支出记录管理
    • 查看收支记录
    • 记账
  • 分类管理
    • 用户分类管理
      • 用户分类列表
      • 创建用户分类
      • 删除用户分类
    • 系统分类管理
      • 系统分类列表

面对如此复杂的记账业务,如何设计一个既能满足当前需求,又能支持未来扩展的微服务架构呢?

有办法的,兄弟,DDD来解决。

业务流程

基于DDD的核心思想,我们需要先理解业务,然后识别核心领域,最后通过代码来实现业务逻辑。

而针对复杂的业务场景,在软件设计上已经有了很多前人的智慧,比如领域驱动设计原则、各种企业级设计模式等。

首先,我们应该梳理一下记账的核心业务流程。深入理解业务是做好架构设计的基础。

记账业务流程图

用户进入记账小程序页面后,首先看到的就是一个账本列表,需要从账本列表中选择一个账本,然后点击记账、选择收入/支出类型,接着选择分类(如餐饮、交通等),填写金额和备注,最后保存记录。

从这个流程可以看出,记账涉及三个核心业务概念:

  1. 账本(AccountBook) - 用户可以创建多个账本,比如个人账本、家庭账本
  2. 记录(Record) - 具体的收入支出记录
  3. 分类(Category) - 对收支进行分类管理

因此,我们可以识别出三个核心领域模型。

领域分析

基于DDD的思想,我们需要对记账业务进行领域分析,识别出核心的聚合根、实体和值对象。

账本领域(AccountBook Domain)

账本是用户记账的载体,一个用户可以有多个账本。

账本业务流程图

核心业务逻辑:

  1. 创建账本
  2. 修改账本信息
  3. 删除账本
  4. 查询账本列表
  5. 查询账本信息
  6. 更新账本预算
  7. 加入账本
  8. 查询账本的成员列表

业务规则:

  • 同一用户下的账本名称不能重复
  • 删除账本时需要检查是否还有记录
  • 只有账本管理员可以删除账本
记录领域(Record Domain)

记录是记账的核心,包含收入和支出两种类型。

记录业务流程图

核心业务逻辑:

  1. 添加收支记录
  2. 修改记录信息
  3. 删除记录
  4. 查询记录列表

业务规则:

  • 金额必须大于0
  • 记录必须归属于某个账本
  • 记录必须有分类
  • 记录时间不能超过当前时间
  • 删除记录需要权限验证
分类领域(Category Domain)

分类是记账系统中对收支记录进行归类管理的重要功能,帮助用户更好地分析自己的消费习惯和收入来源。分类分为系统分类用户分类

系统分类是系统自带的一些分类,这些分类是所有用户都可以使用的,一些通用的分类。

用户分类是用户自身维护的一些分类,可以根据用户自己的使用习惯来创建不同的分类,这些分类只能用户自己使用,方便用户自定义自身的需求。

分类业务流程图

核心业务逻辑:

  1. 系统分类管理
    • 查询系统默认分类列表
    • 系统分类不可删除和修改
  2. 用户分类管理
    • 创建用户自定义分类
    • 修改用户分类信息
    • 删除用户分类
    • 查询用户分类列表
  3. 分类层级管理
    • 支持父子分类关系(如:餐饮 -> 早餐/午餐/晚餐)
    • 查询完整分类树结构
    • 分类层级深度限制(建议最多3层)
  4. 分类统计分析
    • 按分类统计收支金额
    • 分类使用频率统计
    • 分类趋势分析

业务规则:

  • 分类层级规则:支持树形结构,最多支持2级分类(一级分类 -> 二级分类)
  • 删除约束:删除分类时需要检查是否被记录引用,如有引用则不能删除
  • 系统分类保护:系统提供的默认分类不可删除和修改,保证基础功能可用
  • 用户分类权限:用户只能管理自己创建的分类,不能修改他人分类
  • 分类名称唯一性:同一层级下的分类名称不能重复
  • 分类图标:每个分类可以设置图标,提升用户体验

常见系统默认分类:
收入分类:

  • 工资收入
  • 投资收益
  • 兼职收入
  • 其他收入

支出分类:

  • 餐饮美食(早餐、午餐、晚餐、夜宵)
  • 交通出行(打车、公交、地铁、加油)
  • 购物消费(服装、数码、日用品)
  • 娱乐休闲(电影、游戏、旅游)
  • 医疗健康(看病、买药、体检)
  • 生活缴费(水电费、房租、话费)
  • 学习教育(培训、书籍、课程)

接下来我们需要分析,这三个领域之间的关系和需要使用哪些能力。

记账业务架构图

从业务架构图可以看出:

  • 账本是聚合根,管理属于它的记录
  • 记录依赖分类进行归类
  • 三个领域之间通过领域事件进行解耦
  • 统计分析等功能可以通过读模型实现

这样,我们就明确了记账微服务的核心业务能力和领域边界。

系统架构

在明确了业务领域和架构边界后,我们需要设计系统架构来支撑这些业务能力。

微服务分层架构

基于DDD的分层架构,记账微服务的系统架构如下:

记账系统架构图

网关层(GateWay Layer)

  • 路由转发
  • 认证鉴权
  • 限流熔断
  • 日志监控

应用服务层(Application Layer)

  • 账本应用服务(LedgerCommandApplicationService)
  • 记录应用服务(TransactionStatementCommandApplicationService)
  • 分类应用服务(CategoryCommandApplicationService)
  • 通知应用服务(NotificationService)

领域层(Domain Layer)

  • 账本聚合(Leger Aggregate)
  • 记录聚合(TransactionStatement Aggregate)
  • 分类聚合(Category Aggregate)
  • 领域服务
  • 领域事件

基础设施层(Infrastructure Layer)

  • 数据库仓储
  • 缓存
  • 消息队列
  • 注册中心
  • 日志分析
数据存储设计

基于领域模型,我们设计了以下数据表结构:

  • ledger: 账本表
  • ledger_budget: 账本预算表
  • leger_members: 账本成员表(支持共享账本)
  • invitation: 邀请码表
  • invitation_usage: 邀请码使用表
  • transaction_statement: 记录表
  • sys_category: 系统分类表
  • user_category: 用户分类表

这样的设计既能保证数据一致性,又能支撑高并发的业务场景。

详细设计

经过上面的架构设计以后,我们明确了下面几点:

  1. 明确了记账微服务的三个核心领域
  2. 明确了各领域的责任边界和交互关系
  3. 明确了系统的技术架构和存储设计

接下来,我们需要进行详细设计,明确具体的实现方案。

用例设计

我们通过用例图来理解记账微服务的核心功能。

记账系统用例图

主要用例:

  1. 账本管理

    • 创建账本
    • 编辑账本
    • 删除账本
    • 账本成员管理
  2. 记录管理

    • 添加收入记录
    • 添加支出记录
    • 编辑记录
    • 删除记录
    • 查询记录
  3. 分类管理

    • 创建分类
    • 编辑分类
    • 删除分类
    • 分类统计
  4. 统计分析

    • 收支统计
    • 分类统计
    • 趋势分析
领域模型UML设计

基于DDD的领域建模,我们设计了以下核心领域模型:

记账领域模型UML图

设计亮点:

  1. 聚合根设计:以AccountBook为聚合根,管理所有相关的Record
  2. 值对象设计:Money、RecordType等作为值对象,封装业务规则
  3. 领域服务:处理复杂的业务逻辑,如统计、校验等
  4. 仓储模式:抽象数据访问,解耦领域和基础设施

架构优势:

  • 高内聚低耦合:各领域职责清晰
  • 易于测试:领域逻辑独立于基础设施
  • 易于扩展:新增功能不影响现有代码
  • 符合业务语言:代码结构反映业务概念

开发

当设计完成以后就可以进入开发阶段了。这里给出记账微服务的核心代码实现。

接口层(Interface Layer)

LedgerController - 账本管理控制器

@RestController
@RequestMapping("/ledger")
public class LedgerController {@Resourceprivate LedgerCommandApplicationService ledgerCommandApplicationService;@Resourceprivate LedgerQueryApplicationService ledgerQueryApplicationService;@PostMapping("/create")public Result<String> createLedger(@RequestBody CreateLedgerRequest request) {String ledgerNo = ledgerCommandApplicationService.createLedger(request);return Result.success(ledgerNo);}@PostMapping("/update")public Result<String> updateLedger(@RequestBody UpdateLedgerRequest request) {ledgerCommandApplicationService.updateLedger(request);return Result.success();}@DeleteMapping()public Result<String> deleteLedger(@RequestBody DeleteLedgerRequest request) {ledgerCommandApplicationService.deleteLedger(request.getLedgerNo());return Result.success();}@GetMapping("/list")public Result<PageRes<LedgerListRes>> getLedgerList(QueryLedgerListRequest request) {return Result.success(ledgerQueryApplicationService.getLedgerList(request));}@GetMapping("/detail")public Result<LedgerDetailRes> getLedgerDetail(QueryLedgerRequest request) {return Result.success(ledgerQueryApplicationService.getLedger(request));}@PostMapping("/update/budget")public Result<String> updateLedgerBudget(@RequestBody UpdateLedgerBudgetRequest request) {ledgerCommandApplicationService.updateLedgerBudget(request);return Result.success();}@PostMapping("/join")public Result<String> joinLedger(@RequestBody JoinLedgerRequest request) {ledgerCommandApplicationService.joinLedger(request);return Result.success();}@GetMapping("/memberList")public Result<List<LedgerMemberListRes>> memberList(QueryLedgerMemberListRequest request) {List<LedgerMemberListRes> memberList = ledgerQueryApplicationService.getMemberList(request);return Result.success(memberList);}
}
应用服务层(Application Layer)

整体采用CQRS来实现,所以应用服务分成两种,分别是增删改的command应用服务和只负责查询的query应用服务
LedgerCommandApplicationService - 账本command应用服务

@Service
@Slf4j
public class LedgerCommandApplicationService {@Resourceprivate ApplicationEventPublisher eventPublisher;@Resourceprivate LedgerDomainService ledgerDomainService;@Resourceprivate LedgerFactory ledgerFactory;@Resourceprivate InvitationDomainService invitationDomainService;public void deleteLedger(String ledgerNo) {LedgerAgg ledgerAgg = ledgerDomainService.findByNo(ledgerNo);if (ledgerAgg == null) {throw new AggNotExistsException(ResultCode.LEDGER_NOT_FOUND);}ledgerAgg.delete();ledgerDomainService.save(ledgerAgg);}public String createLedger(CreateLedgerRequest request) {// 获取用户IDString userNo = UserContextHolder.getCurrentUserNo();// 查询该账本是否已经存在LedgerAgg ledger = ledgerDomainService.findByNameInUser(request.getLedgerName(), userNo);if (ledger != null) {throw new AggNotExistsException(ResultCode.LEDGER_ALREADY_EXISTS);}// 账本不存在则创建LedgerAgg ledgerAgg = ledgerFactory.createLedgerAgg(request.getLedgerName(), userNo, request.getLedgerDesc(),request.getLedgerImage());ledgerAgg.create();// 插入数据库ledgerDomainService.save(ledgerAgg);// 获取注册的事件进行发布List<DomainEvent> domainEventList = ledgerAgg.getDomainEvents();domainEventList.forEach(event -> eventPublisher.publishEvent(event));// 返回账本编号return ledgerAgg.getLedgerNo();}public void updateLedger(UpdateLedgerRequest request) {// 查询该账本是否已经存在LedgerAgg ledgerAgg = ledgerDomainService.findByNo(request.getLedgerNo());if (ledgerAgg == null) {throw new AggNotExistsException(ResultCode.LEDGER_NOT_FOUND);}// 账本存在则更新ledgerAgg.save(request.getLedgerName(), request.getLedgerDesc(), request.getLedgerImage());ledgerDomainService.save(ledgerAgg);// 获取注册的事件进行发布List<DomainEvent> domainEventList = ledgerAgg.getDomainEvents();eventPublisher.publishEvent(domainEventList);}public void updateLedgerBudget(UpdateLedgerBudgetRequest request) {// 查询该账本是否已经存在LedgerAgg ledgerAgg = ledgerDomainService.findByNo(request.getLedgerNo());if (ledgerAgg == null) {throw new AggNotExistsException(ResultCode.LEDGER_NOT_FOUND);}// 账本存在则更新LedgerBudgetVO ledgerBudget = ledgerFactory.createLedgerBudget(request.getLedgerNo(), request.getBudgetAmount(), request.getBudgetDate());ledgerAgg.updateBudget(ledgerBudget);ledgerDomainService.save(ledgerAgg);// 获取注册的事件进行发布List<</
http://www.dtcms.com/a/473699.html

相关文章:

  • 考研408《计算机组成原理》复习笔记,第七章(1)——I/O接口
  • 建设部网站在哪里报名考试大德通网站建设
  • Java 泛型基础:从类型安全到泛型类 / 方法 / 接口全解析
  • git 绑定多个远程仓库指定推送场景
  • 前端学习2:学习时间3-4小时
  • setup与选项式API
  • 后端开发是什么:从服务器到数据库
  • 南宁3及分销网站制作大连建设网信息公开
  • 神经网络中的非线性激活函数:从原理到实践
  • 【IO多路复用】原理与选型(select/poll/epoll 解析)
  • AI 与神经网络:从理论到现代应用
  • 消息积压的问题如何解决
  • 神经网络常用激活函数公式
  • 回归预测 | MATLAB实现CNN(卷积神经网络)多输入单输出+SHAP可解释分析+新数据预测
  • 中国十大旅游网站wordpress视频试看付费
  • Docker部署的gitlab升级的详细步骤(升级到17.6.1版本)
  • 一个基于稀疏混合专家模型(Sparse Mixture of Experts, Sparse MoE) 的 Transformer 语言模型
  • Litho项目架构解析:四阶段流水线如何实现自动化文档生成
  • 济南建站免费模板logo制作用什么软件
  • Docker为什么比虚拟机资源利用率高,启动快
  • AI 颠覆室内设计:SpatialGen 实现 “一句话生成 3D 房间”
  • 有序逻辑回归的概念、适用场景、数据要求,以及其在Stata中的操作命令及注意事项,Stata ologit回归结果怎么看?并附详细示例
  • PHP开发环境搭建
  • 门户网站与官网的区别做照片的ppt模板下载网站
  • Next.js数据获取演进史
  • 【深入理解计算机网络09】路由算法与路由协议
  • 手机域名解析错误刷seo排名
  • Golang 切片(深入了解切片底层扩容机制,部分源码,测试实战+核心用法)
  • go语言结构体内存对齐
  • 爬虫+卷积神经网络项目实战解析——对图像狗的识别分类