DDD该怎么去落地实现(3)通用的仓库和工厂

通用的仓库和工厂

我有一个梦,就是希望DDD能够成为今后软件研发的主流,越来越多研发团队都转型DDD,采用DDD的设计思想和方法,设计开发软件系统。这个梦想在不久的将来是有可能达成的,因为DDD是软件复杂性的解决之道,而今后的软件会越来越庞大,越来越复杂。然而,经过这几年的DDD热潮,阻碍各研发团队转型DDD的拦路虎是什么呢?我认为是DDD落地开发编码过于复杂,编码工作量大,阻碍了DDD的推广。试想,一个新的开发思想能降低研发的工作量,必然得到大家的普遍欢迎,推行就比较容易,而反之则非常困难,抵触情绪大。

那么,为什么DDD落地开发的成本很高,编码的工作量大呢?针对这个问题,我们要好好分析分析,进而找到解决的思路。按照DDD的开发规范,当团队经过一系列的业务梳理,形成领域模型以后,就开始以领域模型为核心,设计开发业务系统。如图所示是DDD的分层架构:

通过这张图可以看到,展现层就是前端界面(如WebUI、客户端程序、移动App等等),应用层就是Controller,领域层的服务就是Service,聚合、实体、值对象都是从领域模型中映射过来的领域对象,其它都是基础设施(其中也包括了上一期提到的仓库、工厂、缓存)。这样一个架构集中体现了“整洁架构”的设计思想,即将上层的业务代码与底层的技术框架通过分层进行分离,进而实现解耦。也就是说,只有领域层的服务Service、领域对象(聚合、实体、值对象)才能编写业务代码,实现业务规则、业务操作、业务流程,是领域模型的映射。其它层次的代码都是技术,包括前端UI、应用层的Controller、基础设施,以及这张图没有画出来的服务网关、数据库,等等。按照这样的设计思想,最后代码编写的效果就变成了这样:

在整个系统中,每个功能都必须要有各自的Controller、Service和该功能所需的领域对象(聚合、实体、值对象),然后在持久化到数据库的时候,还得有对应的仓库、工厂、缓存。并且,每个功能的仓库、工厂、缓存都不一样。譬如,订单仓库中存在聚合,在增删改订单表的同时,还有增删改订单明细表,而库存仓库只需要管库存表的增删改;订单工厂在查询的时候需要装配与订单相关的客户、地址、订单明细,而库存工厂只需要装配与库存相关的商品对象。正因为如此,每个功能在开发的时候,都需要编写大量代码。

不仅如此,在不同层次中,数据是存储在不同格式的数据对象中,因此数据在各个层次中流转时,还要编写各种格式的数据对象及转换程序,在Json, DTO, DO与PO中转换数据。这样一套下来,DDD的软件开发就麻烦死了,如果我是程序员,我也会不胜其烦。这就是DDD目前的问题所在。

很多时候就是这样的,当分析和查找到问题以后,就离解决问题不远了。我的思路就是,通过一个底层平台(如低代码平台)将DDD中那些繁杂的操作统一起来,实现集约化,那么开发人员就只需要去编写那些各自的业务代码,那么工作量不就变小了吗?也就是说,如果数据接入层、应用层、基础设施层都通过平台实现了,开发人员就只需要编写领域层的领域服务Service和领域对象(聚合、实体、值对象),以及前端的UI界面,开发人员的工作量就减少了,就可以更加专注地按照领域模型去设计编码,DDD不就更容易落地了。

这的确是一个非常完美的思路,然而要实现这个思路,很显然需要一个支持DDD的强大底层平台。那么,这个强大的底层平台需要提供哪些功能呢?我认为有3个:通用的Controller、仓库及其工厂。按照CQRS(Command Query Responsibility Segregation)架构的设计思想,该平台可以划分成两部分设计:增删改(即命令)和查询,我们先看看增删改的设计思路。

在业务系统实现增删改的操作(即命令操作)时,和过去一样,每个功能在前端都有各自的UI。但和过去不一样的是,所有的UI在请求后端时,都是请求的那一个Controller(即OrmController)。前端请求的Url中包含了要请求的功能,因此这个Controller通过反射去请求后端的Service,并自动将Json转换为领域对象。这里有一个很神奇的事情就是,这个Controller怎么能自动将Json转换为领域对象呢?我们可以在开发规范上规定,前端请求后端时,Json对象必须与后台的领域对象保持一致。如前端提交订单,其Json对象长这样:

{"id": 1,"customerId": 10001,"addressId": 1000100,"orderItems": [{"id": 10,"productId": 30001,"quantity": 2}]
}

可以看到,Json对象不必包含领域对象的所有属性,而是必要属性。后端的OrmController收到这个Json以后,就可以通过DDD工厂去读取DSL,将Json转换为订单对象,并请求OrderService中的create()方法,就可以完成创建订单的操作。当然,如果用户请求的是“下单”,要做的就不仅仅是创建订单,还有支付、库存扣减等操作,需要分布式事务,因此请求的是OrderAggService的placeOrder()方法,详细的设计详见测试用例:

OrderService的测试用例

OrderAggService的测试用例

接着,整个业务操作都在领域层的Service和领域对象中进行(详见《充血模型 or 贫血模型》)。当所有的业务操作的执行完以后,通过仓库进行数据持久化。这时,所有的Service都注入了通用仓库,由它去完成相关的增删改操作(详见上一期的通用仓库设计思路)。

有了DDD的底层平台的支持,所有的领域对象和Service完成的都是对业务的操作,它们只知道领域对象长什么样,只对领域对象进行操作,并不知道后面有数据库,从而实现整洁架构中业务与技术的解耦。接着,将增删改等数据库操作交给底层的通用仓库,包括聚合关系的增删改,实现了CQRS中的“C”。

那么,领域对象的查询(即CQRS中的“Q”)又该如何实现呢?按照DDD的设计思想,当我们将领域模型中对象的关系映射到程序中领域对象的关系以后,在查询领域对象时,底层也要保持这种关系。也就是说,当查询订单时,底层不仅仅是查询订单表,还要查询与订单相关的用户表、地址表与订单明细表,最后将它们装配成一个完整的订单对象。这个查询与装配的工作就交给了DDD的“工厂”来完成。

同样,过去的DDD编码实现,需要为每个领域对象编写工厂,来完成这个查询与装配的工作,这无疑会增加DDD的开发工作量。为了简化DDD的编码,降低落地难度,我们的思路同样是由底层提供一个通用的工厂,所有对领域对象的查询统统都交给这个通用工厂。当通用工厂要查询数据时,先查找DSL获取该对象对应的数据库表与所有的关系。然后,通用工厂根据这些信息,依次到数据库各对应表中去进行查询。最后,再依据DSL进行装配,返回一个完整的领域对象。这个领域对象在返回给Service前,还会在仓库中进行缓存,以提高下次查询的效率。所以,通用工厂不直接面向Service,而是被通用仓库封装。通用仓库封装了通用仓库与缓存,就可以完成上层Service的所有增删改与查询的操作。

譬如,现在要实现对订单的查询,首先通过MyBatis去编写一个mapper:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.edev.emall.query.dao.CustomerMapper"><sql id="select">SELECT * FROM t_customer WHERE 1 = 1</sql><sql id="conditions"><if test="id != '' and id != null">and id in (${id})</if><if test="value != '' and value != null">and (id like '%${value}' or name like '%${value}')</if><if test="gender != '' and gender != null">and gender = #{gender}</if></sql><sql id="isPage"><if test="size != null  and size !=''">limit #{size} offset #{firstRow} </if></sql><select id="query" parameterType="java.util.HashMap" resultType="java.util.HashMap"><include refid="select"/><include refid="conditions"/><include refid="isPage"/></select><select id="count" parameterType="java.util.HashMap" resultType="java.lang.Long">select count(*) from (<include refid="select"/><include refid="conditions"/>) count</select><select id="aggregate" parameterType="java.util.HashMap" resultType="java.util.HashMap">select ${aggregation} from (<include refid="select"/><include refid="conditions"/>) aggregation</select>
</mapper>

在这个mapper中可以看到,对订单的查询就只查询订单表,而不进行其它任何的关联。接着,在spring中进行如下的装配:

@Configuration
public class QryConfig {@Autowired @Qualifier("basicDaoWithCache")private BasicDao basicDaoWithCache;@Autowired @Qualifier("repositoryWithCache")private BasicDao repositoryWithCache;@Beanpublic QueryDao customerQryDao() {return new QueryDaoMybastisImplForDdd("com.edev.emall.customer.entity.Customer","com.edev.emall.query.dao.CustomerMapper");}@Beanpublic QueryService customerQry() {return new AutofillQueryServiceImpl(customerQryDao(), repositoryWithCache);}@Beanpublic QueryDao accountQryDao() {return new QueryDaoMybastisImplForDdd("com.edev.emall.customer.entity.Account","com.edev.emall.query.dao.AccountMapper");}@Beanpublic QueryService accountQry() {return new AutofillQueryServiceImpl(accountQryDao(), basicDaoWithCache);}
}

先装配一个QueryDao,它有一个QueryDaoMybastisImplForDdd的实现类,通过MyBatis对订单进行查询,然后按照DDD返回订单对象的列表。接着,再注入到QueryService中,它有一个AutofillQueryServiceImpl的实现类。这样,当QueryDao通过分页查询出这一页的20条记录以后,AutofillQueryServiceImpl就会根据DSL进行数据补填,将这每一个订单对象的用户、地址、明细,都到数据库中进行查询,然后完成补填与装配,最后获得一个完整的领域对象列表。有了这样的设计,每一个模块的查询都变得简单了。你只需要按照领域模型先形成领域对象和DSL,然后编写一个MyBatis的mapper,进行spring的装配,查询的开发工作就完成了。注意,AutofillQueryServiceImpl的第二个参数是在进行补填时,用谁来查询并补填。如果要补填的对象里还有关系(如地址里有省、市、县的关联),则选择repositoryWithCache,否则就用basicDaoWithCache。

最后,每个查询功能都有各自的UI界面,但它们在查询时,请求的都是这一个Controller(即QueryController)。这样,通过领域建模,通过一些简单的配置就可以快速完成各个模块的查询功能。

有了这个平台,按照DDD的设计思想,我们只需要进行领域建模,将我们对业务的理解形成领域模型,就可以快速完成软件的开发。如今有了AI编程,甚至可以训练一个Agent,我们只要深入地理解业务,形成领域模型,就可以让AI按照这样的思路快速开发系统。有了这样的思路,不仅可以让我们将更多的精力放到业务理解而不是软件开发,给我们减负,又可以给AI编程制定规范,有利于日后长期的维护与变更。相信在这样的背景下,DDD又可以焕发生机,成为日后软件开发的主流。

(待续)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国家队出手!DeepSeek上线国家超算互联网平台!

目前,国家超算互联网平台已推出 DeepSeek – R1 模型的 1.5B、7B、8B、14B 版本,后续还会在近期更新 32B、70B 等版本。 DeepSeek太火爆了!在这个春节档,直接成了全民热议的话题。 DeepSeek也毫无悬念地干到了全球增速最快的AI应用。这几天,国内的云计算厂家都在支持Dee…

内容中台驱动企业数字化内容管理高效协同架构

内容概要 在数字化转型加速的背景下&#xff0c;企业对内容管理的需求从单一存储向全链路协同演进。内容中台作为核心支撑架构&#xff0c;通过统一的内容资源池与智能化管理工具&#xff0c;重塑了内容生产、存储、分发及迭代的流程。其核心价值在于打破部门壁垒&#xff0c;…

算法1-1 玩具谜题

题目描述 小南有一套可爱的玩具小人&#xff0c;它们各有不同的职业。 有一天&#xff0c;这些玩具小人把小南的眼镜藏了起来。小南发现玩具小人们围成了一个圈&#xff0c;它们有的面朝圈内&#xff0c;有的面朝圈外。如下图&#xff1a; 这时 singer 告诉小南一个谜题&…

vs2022支持.netframework4.0

下载nuget包 .netframework4.0 解压nuget 复制到C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework 参考 https://www.cnblogs.com/bdqczhl/p/18670152 https://blog.csdn.net/xiaomeng1998_/article/details/135979884

数据治理常用的开源项目有哪些?

数据治理是企业在大数据时代中确保数据质量、安全性和可用性的关键环节。开源项目在数据治理中扮演着重要角色&#xff0c;提供了灵活、经济高效且功能强大的解决方案。以下是一些常用的开源数据治理项目&#xff1a; Apache Atlas&#xff1a; 功能&#xff1a;元数据管理、数…

登录弹窗效果

1&#xff0c;要求 点击登录按钮&#xff0c;弹出登录窗口 提示1&#xff1a;登录窗口 display:none 隐藏状态&#xff1b; 提示2&#xff1a;登录按钮点击后&#xff0c;触发事件&#xff0c;修改 display:block 显示状态 提示3&#xff1a;登录窗口中点击关闭按钮&#xff0…

docker nginx 配置文件详解

在平常的开发工作中&#xff0c;我们经常需要访问静态资源&#xff08;图片、HTML页面等&#xff09;、访问文件目录、部署项目时进行负载均衡等。那么我们就会使用到Nginx&#xff0c;nginx.conf 的配置至关重要。那么今天主要结合访问静态资源、负载均衡等总结下 nginx.conf …

Python+appium实现自动化测试

目录 一、工具与环境准备 二、开始测试 1、插上手机&#xff0c;打开usb调试&#xff0c;选中文件传输&#xff0c;我这里用华为手机为例 2、启动Appium Server GUI​编辑 3、启动 Inspector Session 4、录制脚本 使用Python和Appium进行自动化测试是一种常见的移动应用…

DeepSeek正重构具身大模型和人形机器人赛道!

中国人工智能公司DeepSeek&#xff08;深度求索&#xff09;以“低成本、高效率、强开放”的研发范式横空出世&#xff0c;火遍并震撼全球科技圈&#xff1b;DeepSeek展现出来的核心竞争力&#xff0c;除了低成本及推理能力&#xff0c;更重要的是开源模型能力追赶上了最新的闭…

Facebook精准获客:外贸企业如何通过社群营销将产品推广到海外

作为全球用户量超40亿的社交平台&#xff0c;Facebook已成为外贸企业拓展海外市场的重要渠道。本文将系统解析基于Facebook平台的高效获客策略&#xff0c;重点针对手机壳等消费电子品类&#xff0c;提供可复制的精准客户开发方案&#xff0c;并且可以利用AI社媒引流王工具进行…

尚硅谷课程【笔记】——大数据之Hadoop【一】

课程视频链接&#xff1a;尚硅谷Hadoop3.x教程 一、大数据概论 1&#xff09;大数据概念 大数据&#xff08;Big Data&#xff09;&#xff1a;指无法再一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发…

JMeter工具介绍、元件和组件的介绍

Jmeter功能概要 JDK常用文件目录介绍 Bin目录&#xff1a;存放可执行文件和配置文件 Docs目录&#xff1a;是Jmeter的API文档&#xff0c;用于开发扩展组件 printable_docs目录&#xff1a;用户帮助手册 lib目录&#xff1a;存放JMeter依赖的jar包和用户扩展所依赖的Jar包…

【云安全】云原生- K8S kubeconfig 文件泄露

什么是 kubeconfig 文件&#xff1f; kubeconfig 文件是 Kubernetes 的配置文件&#xff0c;用于存储集群的访问凭证、API Server 的地址和认证信息&#xff0c;允许用户和 kubectl 等工具与 Kubernetes 集群进行交互。它通常包含多个集群的配置&#xff0c;支持通过上下文&am…

springboot整合mybatis-plus(保姆教学) 及搭建项目

一、Spring整合MyBatis (1)将MyBatis的DataSource交给Spring IoC容器创建并管理&#xff0c;使用第三方数据库连接池(Druid&#xff0c;C3P0等)代替MyBatis内置的数据库连接池 (2)将MyBatis的SqlSessionFactory交给Spring IoC容器创建并管理&#xff0c;使用spring-mybatis整…

数据结构6

一、哈希散列--通讯录查找 #include "hash.h" #include <stdio.h> #include <stdlib.h> #include <string.h>//int *a[10];int hash_function(char key) {if (key > a && key < z){return key - a;}else if (key > A && …

Java 大视界 -- 全球数据治理格局下 Java 大数据的发展路径(89)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

基于CanMV IDE 开发软件对K210图像识别模块的开发

简介 CanMV IDE 是一款专为 K210 芯片设计的图形识别 Python 软件&#xff0c;它提供了强大的功能&#xff0c;帮助开发者轻松实现基于 K210 芯片的图形识别应用。无论你是初学者还是经验丰富的开发者&#xff0c;CanMV IDE 都能为你提供便捷的开发环境和丰富的资源。 硬件资…

Unity学习part3

此为b站视频【【Unity教程】零基础带你从小白到超神】 https://www.bilibili.com/video/BV1gQ4y1e7SS/?p55&share_sourcecopy_web&vd_source6e7a3cbb802eb986578ad26fae1eeaab的笔记 1、反向动力学 打开ik处理 public class PlayerMoveController : MonoBehaviour {…

STM32——HAL库开发笔记19(串口中断接收实验)(参考来源:b站铁头山羊)

本实验&#xff0c;我们以中断的方式使得串口发送数据控制LED的闪烁速度&#xff0c;发送1&#xff0c;慢闪&#xff1b;发送2&#xff0c;速度正常&#xff1b;发送3&#xff0c;快闪。 一、电路连接图 二、实现思路&CubeMx配置 1、实现控制LED的闪烁速度 uint32_t bli…

Golang关于结构体组合赋值的问题

现在有一个结构体&#xff0c;其中一个属性组合了另外一个结构体&#xff0c;如下所示&#xff1a; type User struct {Id int64Name stringAge int64UserInfo }type UserInfo struct {Phone stringAddress string }如果要给 User 结构体的 Phone 和 Address 赋值的话&am…