为此,我特别将“群买菜”生鲜电商系统服务端代码新旧代码结构都显示出来,让您看看原来的旧代码——也就是“事务脚本式”代码长啥样(应该是目前大部分 java 程序员写代码的样子),再让您看看 DDD 改造设计后的新代码长什么样子。然后再通过分析,说清楚为什么传统的“事务脚本”代码不是对真实世界的“同构映射”,而 DDD 代码的“同构映射”在哪。
需要提醒您的是:从今天这个专题开始,可能需要你多花点时间、深入地阅读我写的代码、和文字的每一句话,反复对照着看,甚至来回反复多看几遍,才能真的去理解这些文字了。
我们先来看旧代码的目录结构截图。注意看下面的 1、2、3、4 标注位置(解释下,我这里用的是 spring-boot 开发框架,MyBatisPlus 数据持久框架、MySql5.6 数据库):
您注意到这里标注的 1、2、3、4 代码位置了吗?是不是代码结构很像大部分 spring-boot 应用框架下代码结构?为了避免您可能不太了解这种代码结构,我还是简单解释下。
标号 1 位置:这里放的是 Controller(控制器)层代码,也就是所有前端访问的接口都在这里实现。按照 MVC 的分层原则,一般来说,这里只会放一些客户端输入参数的解析、以及对 service 层(见下文)的业务方法调用。一般来说,这里的代码都长成下面这样:
标号 2 位置:这里放的是 entity(数据 bean)层代码,其实都是 POJO 代码,所有类都一一对应到数据库表。一般来说,这里的代码都长成这样:
标号 3 位置:mapper 层,对于 mybatis 持久层框架来说,mapper 和 entity 共同实现了 ORM(对象模型到关系模型的映射)。一般来说,这里的代码长成这样(这里 CustomerMapper 类只是定义了 entity 类 Customer 的映射关系,以及自定义的数据操作方法):
以及这样(在 MP 中,只有需要实现自定义的 SQL 操作方法,才需要这个 CustomerMapper.xml 文件):
标号 4 位置:Service(服务)层,这里是所有业务逻辑实现的核心代码处,几乎所有的业务逻辑都是在这里实现的。一般来说,这里会有 interface+implementation 组合的实现方式。比如:OrderService 和 OrderServiceImpl,分别长下面这样:
OrderService 接口类:
OrderServiceImpl 实现类;
从上面的代码中 ,我们可以很明显地看出如下几点:
之所以叫“事务脚本”,我个人的理解:本质上跟 20 年前写数据库存储过程代码没有本质区别(只是换了个语言书写、运行代码的位置从数据库服务器内部提到了应用服务器);
又之所以叫“贫血模型”代码,是因为 entity 层的那些 POJO 对象如 Order 等,没有任何业务行为的封装(比如:Order 类应该自己生成自己的订单号、提货号等),只有属性而没有行为的对象,就是“贫血”对象,基于“贫血”对象实现的业务逻辑代码,就叫“贫血模型”代码。
根据这里的代码分析,我们是不是能够发现一个关键问题:这里的 Controller/entity/mapper/service,事实上和真实世界的业务之间关系,是没有任何映射的——也就是说:“代码世界”和“真实世界”是异构的。具体来说,我们可以分以下几点来看。
首先,从业务模块划分这个“最粗”的粒度来说,我们其实是可以简单的、凭直觉进行模块划分的,不用全部业务模块放在一个工程项目中,是可以按照业务模块(比如:店铺管理、订单管理、商品管理等)进行项目目录划分、也就是项目团队分组的。
事实上,目前市面上的大多数软件公司,就是根据业务经验或直觉简单粗暴的将项目划分了多个团队在进行开发。但这种划分方式,虽然也可以七七八八准确——但我们需要意识到的是,这样简单粗暴的凭经验直觉的划分,跟 DDD 方法论做的设计划分相比(划分到限界上下文这个粒度的设计,在 DDD 中叫做“战略设计”),至少有 3 个不足:
其次,到模块内部,其代码的层次结构划分,如果按照 mvc 思想,最后还是又回到了类似 controller/entity/mapper/service 这样的划分方式。而这种划分方式,又和“真实世界”有什么同构映射关系呢?可以说,没有!
所以,最终我们还是可以得出结论:这种传统的代码架构,是没有考虑和真实世界的“同构映射”的。而这种对“同构映射”的缺失,才是导致我们出现“真实业务其实没多大变化、但某个需求却为什么引起软件代码翻天覆地的变化呢?”这样疑惑的根本原因——DDD 方法论,就是用来解决这个问题的!
我们再来看看使用 DDD 设计后,新的代码结构长什么样。下面是新代码的结构截图(同样注意下面的 1~8 标号):
对上面的代码标号位置,我来逐个解释如下(需要说明的是:这里目录排序是 IDEA 开发工具自动按字母顺序排序,不是代码设计先后顺序):
标号 1 位置:这里放的是边缘层(edge)代码。由于“群买菜”小程序前端界面已经开发完成,并且这是一个前后端分离项目,前端代码我并没有打算修改,所以这里就多了个“界面适配”的代码工作。一般来说,这种代码就叫“边缘层”。边缘层放的代码,都是类似这种为了前端界面适配、第三方系统接口适配之类的代码。这种代码,也可以叫做“为前端提供的后端”(Backend for Frontend, BFF)。理论上,这种 BFF 层的代码,可以由前端团队开发的,我可以选择技术栈是 Node.js,使用 js 或 ts 语言进行开发。
标号 2 位置:这里显示的是“基础层”(foudation)。在 DDD 的系统架构中,限界上下文(具体概念介绍见后面,这里你只需要理解为它类似于子系统或业务模块划分就好)是可以根据“业务子域”不是核心层,而分为“基础层”和“业务价值层”。一般来说,“业务价值层”对应到最核心的业务模块,是一个软件系统的核心竞争力所在,是需要严格按照 DDD 的理念进行战术设计、并采用测试驱动开发模式、投入最懂业务的程序员去工作的;而“基础层”一般都是非核心业务模块,比如:业务相关基础类、工具类、伴生系统的对接等——需要注意的是:“基础层”不是“基础资源层”,基础层指的是业务模块处于非核心地位、而基础资源指的是数据库、中间件这些技术组件。
标号 3 位置:这里显示了多个限界上下文,都是以 xxxcontext 这样的目录取名。在“基础层”和“业务价值层”中,都会出现多个“限界上下文”。每个限界上下文可以分离到不同的项目团队去负责、甚至分离到不同的微服务中心中。还是那句话,现在你还不用太深入的理解“限界上下文”,暂时只需要理解它是一种模块划分的说法就好(后面会逐步深入解释)。
标号 4 位置:这里显示出来了“业务价值层”的代码——也就是该软件系统中需要作为最核心竞争力的那些模块,同样下面也会有多个“限界上下文”。
标号 5 位置:DDD 战术设计软件分层的“菱形架构”下,“领域”(domain)层的代码放这里,也是业务逻辑最核心的代码——所有的“充血”模型代码。从这里开始,我们解释某个“限界上下文”内的代码结构。具体这些代码怎么设计的细节,我们后面会讲,现在你只需要知道这里放的是“业务逻辑核心”即可。
标号 6、8 位置:在 DDD 战术设计软件分层的“菱形架构”下,为了让“限界上下文”在满足外部的各种调用需求、以及需要调用或与别的“限界上下文”通讯时,不至于因为与本模块业务逻辑无关的、各种外在因素变化而引起本模块内代码逻辑的“动荡不安”,而引入了“北向网关”、“南向网关”概念。分别说明如下:
标号 6 就里面就是“北向网关”的代码,里面又分为 local 和 remote 两个典型的目录。“北向网关”的作用,就是让限界上下文可以向外输出各类应用服务。local 目录下方的是本限界上下文向外提供的“应用服务”,是将 domain 内各种“充血模型”代码进行封装后的、完整的业务逻辑;而 remote 目录下,放的是对 local 目录为了满足“远程调用”而进行的代码封装——如 RPC 调用、跨服务器消息事件订阅等,并不存在任何业务逻辑。
标号 8 里面就是“南向网关”的代码,里面又分为“端口(port)”和“适配器(adaper)”两个典型的目录。“南向网关”的作用,就是是让本限界上下文通过其请求外部资源。典型的 3 类外部资源请求有:访问数据持久层(关系或非关系数据库)、调用别的限界上下文服务(在微服务架构中,往往是 RPC 远程调用)、向别的限界上下文发布消息。我们都知道,这些对外部资源的请求,可能会因为外部资源的技术底层不同,而存在不同的实现方式。为了能够隔离“领域层”对具体技术底层的依赖,就分离出来 port 层和 adapter 层。在 java 语言实现中,port 层就是 interface,没有任何实现代码,只有方法定义;而 adaper 层就是 implemetaion,具体实现到不同持久层(如不同关系数据库 oracle/mysql 等、不同 nosql 数据库 redis/mongodb 等)。然后,根据 IoC(依赖倒置)原则在 java 中通过“依赖注入”来将 adaper 目录下的具体实现与 domain 层的代码连接起来。
标号 7 位置:这里是“发布语言”(published language, pl)层。说白了,“发布语言”就是让“北向网关”向外输出服务时,能与服务调用者之间有个“统一语言”,比如:输入输出参数的结构性定义、事件消息的格式定义等等。因为,我们是不用将限界上下文内部的“领域”层的内部对象结构“泄露”到外部的,所以我们必须要有这个“发布语言”层。
好了,解释完了按照 DDD 进行的代码结构设计,我们还是要回答一个问题:DDD 对真实世界进行“同构映射”后的代码逻辑到底在哪里呢?
答案是:在“领域”(Domain)层里面!所谓的“北向网关”、“发布语言”、“南向网关”层的作用,都只是为了让外部的请求、被请求资源的底层技术,不要去“打扰”我们“业务逻辑”的“同构化”映射!
这就相当于是说:领域层才是 DDD 对“业务逻辑”映射后的核心,其它都只是对这些“核心业务逻辑”的层次“包装”而已!
那么,显然,从技术角度来说,懂得领域层如何设计才是 DDD 战术设计层面最重要的技能!因为,“北向网关”、“发布语言”、“南向网关”这 3 层的代码开发,都是常规套路,没啥“业务知识”含量,甚至可以用机器人来实现(也就是通过代码自动生产工具)。
最后,解释下反复提到的 DDD 战略设计和战术设计的区别:
大体上来说,DDD 战略设计,就是识别出有哪些限界上下文、以及清晰的定义限界上下文的关系和边界,就基本完成了(虽然还有些修修补补的工作,比如要不要边缘层、本软件系统跟哪些第三方外部系统接口等,但这些其实已经不能叫“设计”了,因为不需要花多少脑子了);
DDD 战术设计,核心就是完成“领域”层内的“聚合”、“领域服务”的设计,也就是“核心业务逻辑”的设计。具体怎么玩,我后面会一点点演示。
文章题目:DDD实战之看看代码结构长啥样
网站地址:http://www.shufengxianlan.com/qtweb/news30/442130.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联