DDD项目实践之领域、限界上下文、问题子域

开发 项目管理
DDD为什么难推行?因为我们习惯了舒适,并不是我们不想接受新事物,而是因为我们懒思考,习惯了以往一贯的流程开发、面向数据库CRUD开发,很难转换思维。

[[398256]]

 本文转载自微信公众号「Java艺术」,作者wujiuye。转载本文请联系Java艺术公众号。

DDD为什么难推行?因为我们习惯了舒适,并不是我们不想接受新事物,而是因为我们懒思考,习惯了以往一贯的流程开发、面向数据库CRUD开发,很难转换思维。

DDD要求我们根据产品原型建模,识别领域、限界上下文、子域,这些需要时间思考的问题就像一座座大山,让我们望而却步。且由于项目前期看不到DDD带来的高效,反而没那么敏捷了,并且前期的建模还可能要推倒重来,这让更多人一开始就想放弃,而只有随着需求的不断迭代,DDD才会显示出它的优势。

本篇笔者就以近期的一个项目跟大家分享笔者为该项目识别领域、限界上下文、子域,以及建模的过程。当然,分享的只是最初的一个版本。

领域通常指的就是业务范围,每个公司都有自己明确的业务范围。通常每个公司内部都有很多个系统,如一家电商公司可能会有物流系统、电商系统、直播系统等等,每个系统做的事情则是更细分的领域。

茉莉红交所项目是笔者入职茉莉数科集团后做的第一个项目,也是一个新的项目,由于没有太多历史包袱,笔者选择从零开始搭建整个项目。并且由于初期业务简单,所以才选择在该项目试行DDD,寄希望于随着业务的不断迭代,能够看到DDD发挥出的优势。

最初从产品那里了解到该项目要做的业务就是OTO(线上到线下)探店,那么OTO探店就是我们要了解的领域。

探店其实是达人帮助商家做推广的一种有偿活动,商家通过免费让达人品尝美食或是免门票游玩景点,达人最终通过短视频、直播、图文内容等方式为商家做推广。无论是探美食店、探游乐园,探店都是这个领域的核心。

在探店这个领域中,核心的业务名词有:商家、达人、店铺、订单、任务。而行为有:商家发布探店订单,订单关联店铺,商家认证店铺,达人接单(任务),商家发布订单时通知达人,达人完成任务时通知商家。

现在,我们需要为业务划分限界上下文。

限界上下文是业务概念的边界,是业务问题最小粒度的划分。在OTO探店业务领域中会包含多个限界上下文,我们通过找出这些确定的限界上下文对系统进行解耦,要求每一个限界上下文其内部必须是紧密组织的、职责明确的、具有较高的内聚性。

我们划分出的限界上下文如下图所示。

为什么将任务和订单拆分为不同限界上下文(任务不是作为订单聚合根的实体,而是作为一个独立聚合的聚合根)?这是因为商家发布的一个订单允许有不同的多个达人接单,一个达人也可以接不同商家的订单,这并不是简单的一对多关系。这更像是商品与订单的关系,而不是订单与订单item的关系。

在划分出限界上下文后,还需要根据限界上下文识别出问题子域。问题子域是对业务问题的划分,相对限界上下文来说,是对业务问题更大粒度的划分。

  • 核心(子)域:产品的核心竞争力、盈利来源;
  • 通用子域:常见的,不同领域都可共用的,可通过购买就能使用的;
  • 支撑子域:非核心域、又非通用域,具有个性化需求,用于支撑核心域运作;

根据限界上下文,我们划分出的子域如下图所示。

OTO探店核心域:商家创建订单、平台对订单审核,达人接单后生成任务、平台对任务审核,达人完成任务回填内容链接;

商家支撑子域:商家注册、商家审核;

达人支撑子域:达人注册、达人档案管理、达人粉丝数据提取;

店铺支撑子域:商家注册店铺、店铺审核;(根据下版本的需求,将把店铺当作商家聚合根的实体)

消息通知通用子域:短信通知、应用内通知、小程序消息推送。

在划分出子域后,我们就可以为领域建模了。

领域建模是通过将业务抽象为聚合、实体、聚合根、值对象模型的方式,封装和承载全部的业务逻辑,保持业务的高内聚和低耦合。

聚合:负责封装业务逻辑,内聚决策命令和领域事件,容纳实体、聚合根、值对象。

  • 聚合根:也是一种实体,是聚合的根节点,如订单;
  • 实体:聚合的主干,具有唯一标识和生命周期,如订单Item;
  • 值对象:实体的附加业务概念,用于描述实体所包含的业务信息,如订单收件地址。

在技术实现上,一个聚合就是一个包,里面存放领域服务、工厂、资源库、聚合根、实体、值对象。

领域层包的划分规则通常为:

  1. --domain 
  2. ----限界上下文 
  3. ------聚合 
  4. -------- (聚合根、值对象、实体、领域服务、资源库、领域事件) 
  5. ------聚合 
  6. -------- (聚合根、值对象、实体、领域服务、资源库、领域事件) 

特别的,一个限界上下文可能包含多个聚合,但一个聚合只能存在于一个限界上下文。 如果一个限界上下文只有一个聚合,这种情况下我们通常省略限界上下文这一层。

以订单限界上下文、任务限界上下文为例:

  1. --domain 
  2. ----ordercontext 
  3. ------orderType(订单类型聚合(特殊的聚合,用于管理订单分类):美食(早餐/午餐/晚餐/下午茶)、...) 
  4. ------order 
  5. ----task 

由于订单存在两个聚合,因此我们没有省略订单限界上下文这一层,而任务只有任务聚合,所以省略了任务限界上下文这一层。

以上包的划分只是领域层的划分,要求聚合根、值对象、实体、领域服务、资源库、领域事件等类存放在聚合包下,无论是使用DDD经典四层架构,还是六边形架构。

我们并非采用DDD经典四层架构,也非六边形架构,我们实际对项目包的划分如下。

当我们需要按限界上下文拆分订单和任务为两个微服务时,只需要copy一份项目代码,一个项目中去掉ordercontext包,一个项目中去掉task包,并且将两个限界上下文应用层之间的依赖调用改为通过远程RPC调用。上图中的xxxGateway类就是用于封装远程调用的,UserApplicationServiceGateway、RabbitmqConfiguration之所以放在最外层,因为两个限界上下文都会用到。

以任务聚合为例,展开后的包结构如下。

以上全部就是我们最初对OTO探店业务识别限界上下文、拆分子域、领域建模的过程,根据目前需求排期来看,这个模型我们即将要推倒重来一次,但对代码的改动应该不大。

因为DDD缺少权威性的实践指导和代码约束,我们只能是通过实践慢慢积累经验。

 

责任编辑:武晓燕 来源: Java艺术
相关推荐

2017-12-17 17:01:23

限界上下文系统模型

2017-05-11 14:00:02

Flask请求上下文应用上下文

2015-07-08 10:25:05

Javascript上下文作用域

2012-12-31 10:01:34

SELinuxSELinux安全

2011-06-28 10:55:02

QT QMainWindo 内存泄露

2022-09-14 13:13:51

JavaScript上下文

2023-05-05 07:41:42

执行上下文JavaScript

2023-01-14 22:49:44

Flask线程请求

2023-07-11 10:02:23

2022-09-15 08:01:14

继承基础设施基础服务

2022-10-28 16:24:33

Context上下文鸿蒙

2022-07-05 08:09:26

领域驱动设计

2021-07-26 07:47:36

Cpu上下文进程

2020-07-24 10:00:00

JavaScript执行上下文前端

2022-10-31 15:34:30

python装饰器内存泄漏

2010-02-25 17:04:54

WCF实例上下文

2019-05-06 14:36:48

CPULinux寄存器

2012-07-30 16:29:40

架构架构模式.NET

2022-04-24 15:37:26

LinuxCPU

2023-08-28 07:28:41

项目领域层充血模型
点赞
收藏

51CTO技术栈公众号