DDD 领域驱动设计-商品建模之路

最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念:

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

关于改版的业务设计,还是想尝试 DDD 领域驱动设计,之前写的一些相关文章,都是直接进行战术设计,而非在战略设计基础上进行,所以最后可能会出现一些问题,所以这次的过程是:边了解业务、边了解 IDDD 书中关于战略设计的部分,然后尝试使用战略设计的方式进行业务分析,最后再细分出具体的战术设计,没有正确的设计方案,只有合适的设计方案,排除技术之外的业务分析过程,还是蛮有意思的。

DDD 战略建模(包含概念):领域(Domain)、核心域、子域、界限上下文(Bounded Context)、上下文映射图(Context Mapping)。

相关文章:

1. 业务流程

业务场景:发布商品

业务场景就上面四个字,看起来很简单,但其实具体分析起来,所包含的东西还是蛮多的,整个发布商品过程,就像一个商品诞生的生命周期一样,需要经历各个阶段和过程,直到商品正式发布出来,并且在这个过程中,有一系列的其他概念由商品衍生出来,比如库存、分类、品牌等等,还会有一些用户的行为参与,比如小二的后台审核等。

在业务分析过程中,我还是比较喜欢画一张简单的业务流程图,并不一定很规范,你也可以直接手绘出来,从业务流程图中,我们可以看到整个的业务方向,有利于我们从中找出关键的业务点,并进行具体分析设计。

发布商品业务流程图:

DDD 领域驱动设计-商品建模之路

图比较简单,我们需要从添加商品到发布商品完成的过程中,抽离出关键的业务点,并且这些业务点事需要在业务系统中进行设计的,发布商品流程大概分为两个部分:

  1. 商户发布商品:这部分内容比较多,先选择分类(分类需要进行设计),然后填写基本信息(根据实际的业务,有很多不同的设计,是发布商品的核心,需要重点考虑),填写完成之后(两种选择:保存草稿和发布),在小二审核之前,需要填写入库单,用来更新商品库存,然后进入小二审核阶段,如果审核成功,并且商户选择商品上架,则代表着整个商品发布的业务流程跑完了。
  2. 小二审核商品:小二根据一些规定进行审核商户发布的商品,这个部分人工因素很大,但发布商品的规定一般是确定,因为是人工进行操作,所以这部分内容在业务系统设计方面体现不大。

其实,选择分类可以归纳到填写基本信息中,重要的是基本信息具体是什么?这部分包含的业务是什么?该如何设计呢?后来分析了下,除了一些商品的基本信息之外(比如标题、价格、商品详情、商品图片),还包含了品牌、分类、属性(一般指的是 sku)等,像标题和价格之类的属性一般是具体的值,后面我们在战术设计的时候,直接把它们设计成值对象即可,但对于品牌、分类之类的对象,需要进行单独进行设计,因为它们不是一个值所能代表的,需要进行独立维护。

除了商品信息之外,后面就是填写采购单用来更新商品库存了,这部分业务内容有点像外部服务一样,通过外部服务的一些操作,最后的结果导向商品模型,这部分类似的业务以后可能会很多,比如小二审核商品信息,也像一个外部服务一样,不过是人为进行操作的,审核最后的结果导向商品状态,我们可以归纳出,可以改变商品状态的一些行为,都是需要进行考虑的业务,并且这部分业务在后面建模的时候,需要重点设计。

画业务流程图的目的,在于熟悉整个业务的大致流程,以及对商品生命周期的了解,但只是大致的表述,当你对业务理解越深的时候,业务流程图也就会越复杂,但基本的框架是不变的,所以,在画的业务流程图的时候,要找出业务的不变规则,比如发布商品肯定要填写信息、然后小二审核等,变的业务都是在这些不变的规则之上丰富起来的,最后形成整个健全的业务系统。

2. 限界上下文

关于领域、核心域和子域的概念,相对比较容易理解,领域就是业务系统的全部,核心域就是业务系统最重要的部分,比如商品业务系统,核心域就是商品,其他相对不重要的业务部分就是子域,子域又分为支撑子域和通用子域,支撑子域用来支撑核心域,在整个领域中,可以被公用的子域,称为通用子域。

限界上下文是一个显式的边界,领域模型存在这个边界职位,领域模型把通用语言表达成软件模型,一般在设计的时候,会把领域和限界上下文一一对应(但也不是相对的),有时候限界上下文很大,但有时候限界上下文也很小,比如一段业务描述也可以称之为限界上下文,不管概念是怎么定义的,只需要知道限界上下文的核心是边界,边界的目的就是内聚合隔离。

一张简单的商品限界上下文图(虚线表示领域的边界):

DDD 领域驱动设计-商品建模之路

首先,在商品领域中,商品是核心域,并对应一个商品上下文,库存被设计为一个通用子域,因为以后交易的业务场景会被用到,并对应一个库存上下文,品牌通用子域也一样,业务场景可能会对品牌的单独处理(比如品牌街,这是和商品不想关的),所以设计成通用子域会相对好些,分类支撑子域和属性子域相对复杂点,其实这里的分类和属性都是相对于商品而言的,你可以成为商品分类和商品属性,独立于商品之外,分类和属性是没有任何存在的业务意义的,所以,把它们设计为商品领域的支撑子域会比较好些。

另外,关于分类上下文和属性上下文之间的关系,从上面图中就可以看到,在业务场景中属性依附于分类,比如在发布商品页面,填写商品属性之前需要先确定商品分类,因为不同的分类有对应不同的属性,比如表带材质属性,只有手表分类下才会有,其实它们也可以直接合二为一,叫做分类属性支撑子域,对应分类属性上下文,上面说过添加属性之前,必须先确定分类,属性就像是分类中的一个子域,属性其实和商品没有直接的关系,它和商品的所有关系,必须都通过分类,并且属性的数据维护也是如此,这个后面会有调整,再详细说明。

分类在具体的实现中,会相对比较简单,有点像品牌的实现,顶多和商品有一些关联,但属性实现相对比较复杂些,因为属性项和属性值都是动态的,并且属性的展现形式也是动态的,比如一个属性项可能对应多个属性值,并且展现可能是组合形式的(文本+单选+下拉列表),这方便在也体现在数据存储的时候,不过可以按照一定的格式用 json 进行存储,展现方式也是一样。

限界上下文就像一个手术刀,将领域一点一点的进行解剖,解剖出来的部位独立进行实现,限界上下文的具体实现就是战术设计,并且各个限界上下文的实现之间是相互不影响。

3. 数据模型(聚合和实体)

限界上下文让我们明白,我们到底需要做什么东西,接下来就是针对这些东西的具体设计和实现了,怎么实现?就是战术设计。

战术建模(包含概念):聚合(Aggregate)、实体(Entity)、值对象(Value Objects)、资源库(Repository)、领域服务(Domain Services)、领域事件(Domain Events)、模块(Modules)。

关于战术设计的首要前提是聚合,后面实体和值对象等概念,都是在聚合的基础上衍生出来的,关于聚合的概念就不多说了,但需要说明下聚合设计的注意点:

  • 尽量小聚合设计:有助于减少事务的提交冲突,也有利于系统性能和可伸缩性,但不能过小,比如一个聚合只包含唯一标识和单个属性,这种设计不合理。
  • 根实体可以作为聚合根。
  • 聚合边界和真实的业务约束是一致的。
  • 通过唯一标识引用其他聚合。
  • 事务一致性:在一个事务中,只能修改一个聚合实例。
  • 在聚合之外使用最终一致性:如果可以不在意延迟,一般用领域事件进行实现。

先简单看下商品所包含的东西:

DDD 领域驱动设计-商品建模之路

图中主要说明的是商品大致包含的内容:分类、属性和基本信息,属性又有具体的分类,但都基于分类确定的情况下。

一张简单的商品数据模型图:

DDD 领域驱动设计-商品建模之路

简单归纳下:

  • 商品(聚合根):商品(根实体)、商品图片(实体)、商品 sku(实体)、商品描述(实体)、商品调整纪录(实体)
  • 库存(聚合根):库存(根实体)、入库详情(实体)
  • 品牌(聚合根):品牌(根实体)
  • 分类(聚合根):分类(根实体)、分类属性(实体)、分类属性值(实体)

实体中的属性只是一些示例,并不详细,值对象并没有在图中体现,因为实体的属性都可以被设计为值对象,这部分在具体实现的时候,再详细进行考虑,聚合根和实体、聚合根和聚合根之间的关系用箭头进行表示了。

关于分类和属性,在限界上下文设计的时候,被分开设计了,但后来想了一下,还是设计成一个比较好,分类作为聚合根,分类属性和分类属性值作为衍生出来的实体。

关于数据模型图,就不详细说明了,内容都在上面的图中,况且现在还不是很完善,后面可能还会进行调整。

大概就纪录这些。

上一篇:DDD领域驱动设计:CQRS


下一篇:15、NFC技术:使用Android Beam技术传输文件