当前位置:网站首页 > API设计与开发 > 正文

领域驱动设计demo(领域驱动设计的定义)



从学习编程语言以来,我们会花大量时间去研究编程语言中比较流行的框架,但我们很少花时间从软件设计的角度去切入学习。

大部份的项目中,一般都会采用经典的三层模式:->->。

之所以三层模式足够流行,是因为它足够简单,就像与生俱来天生就会的技能一样。

当你接触项目越来越多、越来越复杂,不再是每张表的CURD的时候,这时你应该会在想:

  • 这种软件工程难道没有一种更科学的、更有设计味道、更有思想高度的一种架构模式吗?
  • UI层收到请求通知,然后一直到逻辑层,再到数据访问层,是否还有其它的模式?
  • 贫血模式带来的失忆症该怎么解决
  • 一张表对象(PO)到处有赋值行为,时间一久我都忘了当初为什么要对这个字段赋值?
  • A表依赖B表的数据,这种逻辑我到底放A逻辑层还是B逻辑层?再加一层?规则层?外观层?
  • 需求一来,想好实现方式,紧接着直接建表?
  • 100多张表了,这项目越来越乱了,是我表设计的不好?还是根本就没设计?
  • 为什么加一个需求,我改了几十处的代码,原来没问题的代码都被我改出BUG了。
  • 每次重构之后,我都觉得自己特牛逼。过个1年,又来重构,美约1.0、2.0、3.0?

如果您跟我一样有这些共鸣,那么希望这篇文章能给你带个启发。

网上已经有非常多关于领域驱动的一些概念讲解了,本篇的目的在于用来让小伙伴们快速认识领域驱动设计。

不管怎么说,我先把这些名词列出来,方便后面我们共同深入探讨。

7.png

这一层,狭义的可以理解为是三层的数据访问层,这么定义是方便大家理解与三层的对比。 随着我们的项目的复杂度越来越高,除了依赖数据库,还会依赖缓存,http,rpc,mq,es等等。

在,我们大部份会在。

但是你发现了吗,依赖的技术中间件只是为了解决海量数据、高并发、性能,跟业务逻辑其实一点关系都没有。

哪天mongodb换成es、ch,难道我们还要在逻辑层也跟着改吗?(哪怕是一个字符也是改)

所以,涉及到、,、,都应该放到基础设施层。

并且有一个很大的,逻辑层()是。(依赖倒置,)

我们项目中的、以及(动作)会产生时,都在。

领域层是充血模型,即带有行为的实体对象。

领域层通常会包含:、(聚合根、实体对象、值对象)、、、

是用来定义一些的。由。

本篇不是领域驱动设计的概念讲解,这里只是告诉大家有哪些概念,如果想深入学习的,网上有很多这方面的讲解。

可以理解为,在传统三层中的UI层与逻辑层之间多了一层。

的用途是为了将领域层做、与(如数据库事务),没有实战过的小伙伴,这里有可能会比较难理解。

很多时候无法区分应用层与领域层的职责。总之,业务逻辑、对象的属性实际赋值,放在领域层。而比如一个请求要做3个逻辑处理。那么这个1、2、3先后顺序的控制,由应用层组织编排。

像数据库事务也是由应用层来控制,为什么这么做?因为间接或直接的。

思考时间:通过这句话,大家是否有进一步的思考,为什么领域层不能间接或直接依赖基础设施层呢?欢迎评论区讨论。

这一层就可以理解为在三层里的UI层了。通常像我们做的API涉及到的MVC,就是放到了这一层。

由于框架的支持技术,是一种的技术实现。因此在后面的demo中,你会看到我们的接口层是没有代码的。

在api路由配置中,直接指向了应用层,这也是可以的。这样可以让我们少一层的转发,代码也更加容易维护。

在这个小章节,我给大家简略的讲述了领域驱动设计的分层概念,但我怕误导大家,因为领域驱动设计是分为两个部份的:、。

事实上涉及到编码部份(包括上面提到的分层)都是战术部份。。

那什么是战略设计呢?如果比喻成一个武功,、。

因此也可以知道(战略设计)是有多么的。可以让我们在一个项目中、,做整体的一个设计。

我们经常听到的微服务,你是否有想过一个问题:这个服务该怎么划分?微服务决非是RPC + 小接口,如果是这么理解,就大错特错了。

在这里突然说到微服务,是因为在划分服务与服务之间的界限时,正好的。

因此领域驱动是每一个做互联网、写服务API的开发人员都要学习的,哪怕你不用,起码也要懂其中的原理。

感谢你能坚持看到这,如果对你有用,请给个不要钱的小爱心,让作者的爱继续发扬光大。

有了前面的铺垫,接下来,我们马上进入代码部份。

使用到的框架:farseer-go 如果可以的话,请大家给farseer-go 1个star

项目开源代码:github

框架官方文档:文档

这是模拟了一个小型电商平台,用到的技术:

  • ddd:使用领域驱动设计
  • ioc:使用ioc/container,做解耦、注入、依赖倒置
  • webapi:api服务,并使用动态api技术
  • data:数据库操作
  • redis:redis操作
  • eventBus:事件驱动

shopping_1.png 按商品分类,浏览商品

shopping_3.png 商品详情,下单

shopping_2.png 下订单后,通过事件驱动来通知商品减库存、保存订单数据

  • ProductPO:商品,表名: (技术点:实现了数据库的插入、查找、分页技术)
  • ProCatePO:商品分类,表名: (技术点:与Redis缓存自动同步,redis没数据时自动从数据库读取并缓存到redis)
  • OrderPO:订单信息,表名: (技术点:利用事件驱动,解耦下单时,商品、订单的耦合)
  • 库存:key: (保存商品的库存,这里不考虑库存并发等问题,只演示如何使用Redis模块)
  • 商品分类:key: (数据库表与Redis同步)

shopping_4.png

文件位置:shopping/main.go

 

首先,我们通过函数引导farseer-go框架启动。

接着,我们注册了6个api接口

让我们的接口返回值时,自动包裹API的状态码相关字段。

shopping_5.png

让我们支持静态目录来支持前端静态页面(这里是前后端分离)

注意看路由部份,依赖了、、3个包。他们分别指:商品应用服务、产品分类应用服务、订单应用服务。

这些路由,是直接指向到应用层。因为webapi模块支持动态api模式,这样我们不再需要mvc模式的controller方式。

productApp.ToList、orderApp.ToList这两个路由指定了参数名称,这是因为当入参超过1个时,需要显示的申明第1个参数名称,第2个参数名称,以此类推。

接着我们看一下这个服务。这是获取商品详情的信息。

文件位置:shopping/application/productApp/app.go

 

有3个入参:、、。

通过路由注册,我们知道请求的URL是:GET http://localhost:8888/api/1.0/product/info?productId=1 ,1是商品的ID值

我们可以从URL中看出来,那么后面两个参数是怎么来的呢?

先看下productRepository的定义:

文件位置:shopping/domain/product/repository.go

 

从文件位置上,可以看的出来,这是放在domain(领域层)的,并且是属于product目录。

也就是说,这是放在产品领域的。而是仓储的意思,其类型是,从定义的方法可以看出来,是针对数据库的。

这与传统的三层模式不一样。在领域驱动设计里多出这么一个接口定义。

思考时间:为什么要这么定义?是不是多余的?欢迎评论区讨论。

既然是有接口定义,那么就一定有实现,接下来,我们先不继续探讨应用服务层,而是先把这个仓储接口的实现给搞明白。

文件位置:shopping/infrastructure/repository/model/productPO.go

 

这是大家都熟悉的配方:数据库表的实体类

文件位置:shopping/infrastructure/repository/productRepository.go

 

就是一个普通的数据库操作,其完全实现了接口。

大家还记得前面,第1个参数,我们知道是前端通过URL请求传过来的。那么第2个、第3个接口参数,是怎么赋值的呢?

shopping_6.png 图中可以看到,他们都是已经被赋值了。这其实就是ioc/container的功能了。

可以让我们少写很多代码,不需要手动到基础设施层的仓储实现来手动初始化了,是不是很方便。

这是怎么做到的呢?

首先,在基础设施层先将实个结构体注册到。

文件位置:shopping/infrastructure/module.go

 

在Module模块的PostInitialize方法中,执行了

 

接着,执行了,这是将实现类注册到。

这里的注册,意思是将当前初始化接口的方式交给ioc来管理其生命周期。

关于farseer-go的使用,可以看IOC、container注入

当webapi发现请求的路由handle发现参数是接口类型(上面提到的第二个参数、第三个参数)时,就会尝试到去解析是否有其实现。

如果找到了,则执行注入。现在我们就能理解,ToEntity的入参赋值是怎么回事了。

文件位置:shopping/application/productApp/app.go

 

我们继续看:,这是将一个类型转成另一个类型的工具,比如PO转DTO。(代码这里是聚合转成DTO)

这个方法,返回的是类型,并不是表实现类:

虽然他们长的很像,但还是有区别的。我们知道数据库的字段,通常是扁平化的,也就是说字段都是展开来的。除非我们用JSON来保存,但这样对where条件不友好,且失去语义。

先来看一下类型

文件位置:shopping/domain/product/domainObject.go

 

我们再对比一下数据库表实体:

文件位置:shopping/infrastructure/repository/model/productPO.go

 

发现没有,他们长的很像,区别在哪呢?是不是DomainObject(聚合)更加OOP?并且还多了个Stock字段。

小黑板:事实上,如果我们用领域驱动设计来设计项目代码时,一般是不会直接去建表的,而是先考虑这个聚合根长什么样。(当前会先划分限界上下文、领域事件等)

我们不要被数据库表的数据如何存储,而失去了项目中原本最重要的业务逻辑的思考。

如果改成这样的思考方式,我们是不是显的更加有设计感,而不再是表的CURD了?

由于这个项目,我演示的比较简单,也因此导致一个问题,所有的DomainObject没有行为产生。

如果想研究更复杂的,大家可以去看我的一个 开源项目。

如果可以,请给这个开源项目一个star。毕竟开源不易,作者都是用爱再发电,谢谢大家。

什么是领域事件?简单来说就是某一类的动作会产生不同的事件。

在这里用户下单就是一个领域事件,比如当用户下单时,需要做:

  • 减库存
  • 创建订单
  • 通知仓库人员准备发货

文件位置:shopping/application/productApp/app.go

 

通常,我们先减库存,因为我们要防止超卖,无法在异步环节处理。

在实际项目中,一般会提前生成OrderId,我在这里没做。

在最后两行,我们通过ioc/container容器,取出的实现。

然后调用事件发布:

先来看下这个的注册:

文件位置:shopping/application/module.go

 

是注册事件的意思,事件名称:,同时有两个事件订阅者:、

  • :创建订单(生成一条订单记录)
  • :通知仓库管理人员,需要准备打包货物(当然这里只是一个模拟)

这里的事件从技术角度来说,就是有一个发起方,有多个订阅者。(发布订阅模式)

随着业务的扩展,我们会不断的增加新的订阅者。保证新的需求过来时,不用修改原来的逻辑代码。

文件位置:shopping/application/job/createOrderEvent.go

 

文件位置:shopping/application/job/noticeWarehouseOrderEvent.go

 

到了这里,代码部份已经分析完了,我们再做一个闭环,给大家一个完整的数据流向:

farseer-go.png

  1. 在main.go中注册路由
  2. api请求时根据路由,会执行应用服务层的服务。
  3. 应用服务会请求领域层的仓储接口、或聚合根、领域事件。

通过分析也可以看出:

  • 接口层、应用层、领域层没有依赖基础设施层
  • 基础设施层是在main的启动模块(StartupModule.go)依赖的。
  • 当领域层不依赖基础设施层,那么使用哪种OLTP、OLAP、缓存、mq都不会导致逻辑改变。
  • 一个请求需要执行多个小逻辑时,由应用层控制(编排)

好了,本篇到这里就结束了。

感谢你能坚持看到这,如果对你有用,请给个不要钱的小爱心

如果遇到有不明白的地方,欢迎留言提问。

使用到的框架:farseer-go 如果可以的话,请大家给farseer-go 1个star

DEMO项目开源代码:github

框架官方文档:文档

github。

如果这个DEMO您看完后,能完全掌握了,不防看下

到此这篇领域驱动设计demo(领域驱动设计的定义)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 单片机设计课程(单片机 课程)2025-01-18 11:36:08
  • 配置中心设计(配置中心的工作流程)2025-01-18 11:36:08
  • 反激电路设计步骤(反激电路设计步骤包括)2025-01-18 11:36:08
  • 单片机设计制作(单片机自己设计)2025-01-18 11:36:08
  • 51单片机设计(51单片机设计计算器)2025-01-18 11:36:08
  • 51单片机设计篮球计时计分器(51单片机篮球计时计分器的原理)2025-01-18 11:36:08
  • 单片机设计原理图(单片机设计原理图解)2025-01-18 11:36:08
  • 单片机设计论文降重(单片机毕业论文怎么写降低重复率)2025-01-18 11:36:08
  • 单片机设计课程(单片机设计课程内容)2025-01-18 11:36:08
  • 单片机设计报告(单片机设计报告设计要求)2025-01-18 11:36:08
  • 全屏图片