在五彩石我的项目启动伊始,就决定采纳畛域驱动模型来设计业务框架,然而因为过后好多人对该模型的不相熟,以及对一些设定的难以了解,导致初版的代码还是存在不少 mvc 的影子的。随着大家对该模型的逐步理解,统一认为须要对业务代码做一些优化,于是组外在 8 月份启动了二次 DDD 革新的外部迭代,通过一直的灰度放量,近期曾经全量放开了新版业务逻辑。
对于对 DDD 的了解,每个人都能够是不同的,并且根据不同的了解所写的业务框架也是不同的,只有在肯定的范畴内可能逻辑自洽,那是没问题的。在启动 DDD 革新前,组内也做过一些分享,制订了一些标准,本文就是对组内这些标准的一个总结。
interfaces (用户接口层)
流量申请入口。
用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户(H5/APP)、程序(API)、音讯队列(MQ)、超时核心回调,自动化测试和批处理脚本等等。
在最后的设计中,只有用户的调用是放在这一层的,其余调用是放在了 application,这样导致了两个后果,分层不明确和入口扩散。
所以本次将所有的流量入口全副放在了 interfaces 做收口。
application(应用服务层)
应用层连贯用户接口层和畛域服务层,它是很薄的一层,次要职能是协调畛域层多个聚合实现服务的组合和编排。
应用服务层是很薄的一层,实践上不应该有业务规定或逻辑,次要面向用例和流程相干的操作。但应用层又位于畛域层之上,因为畛域层蕴含多个聚合,所以它能够协调多个聚合的服务和畛域对象实现服务编排和组合,合作实现业务操作。
应用层也是微服务之间交互的通道,它能够调用其它微服务的应用服务,实现微服务之间的服务组合和编排。
在设计和开发时,不要将本该放在畛域层的业务逻辑放到应用层中实现。因为宏大的应用层会使畛域模型失焦,工夫一长你的微服务就会演变为传统的三层架构,业务逻辑会变得凌乱。
举个精简的例子,假如出价分四步,
1、调用商品接口校验商品是否上架,
2、出价规定校验,
3、数据落地,
4、调用库存接口生成库存;
那么应用层的次要性能就是编排这四步,不做业务解决。其中第 1 步和第 4 步放到根底层解决(根底层封装内部 RPC),第 2 步在规定域执行,第 3 步在出价域执行。
在初版的设计探讨中,有过一个定义是查问业务能够穿畛域层,中转根底层,理由是纯查问的业务没有简单的逻辑,仅仅是拿数据而已。但这也只是一个想法而已,通过理论的业务实际可知查问还是有很多的业务逻辑组装,而根底层是不解决业务逻辑的,拿到数据后只能在应用层做封装,导致了应用层过于厚重。同时也思考到一个正当的规范性,约定同样的流程当前,会更好的推动 DDD。
domain(畛域服务层)
畛域服务层是由多个业务职责繁多的聚合形成,实现外围的畛域逻辑。
畛域层的作用是实现畛域外围业务逻辑,通过各种校验伎俩保障业务的正确性。畛域层次要体现畛域模型的业务能力,它用来表白业务概念、业务状态和业务规定。
畛域层蕴含聚合根、实体、值对象、畛域服务等畛域模型中的畛域对象。畛域模型的业务逻辑次要是由实体和畛域服务来实现的,其中实体会采纳充血模型来实现所有与之相干的业务性能。
什么是充血模型
与之对应的是贫血模型,咱们常常应用的 MVC 模型中,实体类只定义属性,并没有定义实体行为。这种将数据与业务逻辑拆散,其实是违反了 OOP 的封装个性,实际上是一种面向过程的编程格调。任意代码都能够批改实体的属性,那么实体的属性值就不受限制了。这其实是自下而上的设计思维,是 SQL 驱动(SQL-Driven)的开发模式。
而充血模型则是将该实体所有行为也进行了定义(比方 save,modify,remove 等操作)。任何想要批改实体属性的操作,必须要通过实体自身来实现。这是一种自上而下的设计思维,由具体的业务驱动开发,不须要关怀底层的 SQL 实现。
实体和畛域服务在实现业务逻辑上不是同级的,当畛域中的某些性能,繁多实体(或者值对象)不能实现时,畛域服务就会出马,它能够组合聚合内的多个实体(或者值对象),实现简单的业务逻辑。
聚合根
聚合根是一种更大范畴的封装,把一组在业务上不可分隔的实体和值对象聚合在一起,通过根实体的惟一标识对外提供能力。
实体
实体是畛域中须要惟一标识的畛域概念。雷同的两个实体,如果惟一标识不一样,那么即使实体的其余所有属性都一样,咱们也认为它们两个是不同的实体,比方同一个用户对同一个 sku 同价格的两个库存实体,除了主键 ID 外,其余均雷同,但这依然是两个实体。
同时,不应该给实体定义太多的属性或行为,而应该寻找关联,将一些属性或行为转移到其余关联的实体或值对象上。比方 Inventory 实体,会存储一些商品信息(sku、spu 等),因为商品信息是一个残缺的有业务含意的概念,所以,咱们能够定义一个 Commodity 对象,而后把 Inventory 实体中商品相干的信息转移到 Commodity 对象上。如果没有 Commodity 对象,而把这些商品信息间接放在 Inventory 对象上,并且如果对于一些其余的比方费用信息、仓库惟一码等信息也放到进去,会导致 Inventory 对象很凌乱,构造不清晰,最终导致它难以保护和了解。
值对象
值对象就是下面所说的 Commodity 对象,并不是每一个值对象都必须有一个惟一标识,也就是说咱们不关怀对象是哪个,而只关怀对象是什么。以 Commodity 对象为例,如果有两个 Commodity 的 spuId 是一样的,咱们就会认为这两个 Commodity 是同一个。也就是说只有 spuId 一样,咱们就认为是同一个商品。
infrastructure(基础设施层)
基础设施层是数据封装层,在这里获取各类数据,比方数据库,缓存,内部畛域,内部接口等。
根底层是贯通除畛域层外所有层的,比拟常见的性能还是提供数据库长久化和内部畛域服务调用的。
根底层蕴含根底服务,它采纳依赖倒置设计,封装根底资源服务,实现应用层、畛域层与根底层的解耦,升高内部资源变动对利用的影响。
比如说,在传统架构设计中,因为下层利用对数据库的强耦合,很多的架构演进中最担心的可能就是换数据库了,因为一旦更换数据库,就可能须要重写大部分的代码,这对利用来说是致命的。那采纳依赖倒置的设计当前,应用层就能够通过解耦来放弃独立的外围业务逻辑。当数据库变更时,咱们只须要更换数据库根底服务就能够了,这样就将资源变更对利用的影响降到了最低。
依赖倒置
Domain 层不再间接依赖 Infrastructure 层,而是引入了一个适配器模式(Port/Adapter),应用 DIP(Dependency Inversion Principle,依赖倒置)反转了 Domain 层和 Infrastructure 层的依赖关系,其关系如上图所示 Domain 层以接口的形式凋谢端口,让 Infrastructure 层去实现,这样设计的有点是 Domain 层的演变和进化齐全是独立的,向上不受 Application 层影响,向下不受 Infrastructure 层影响。
举个例子,畛域层是通过仓储接口 (repository) 获取根底资源的数据对象,仓储接口会调用仓储实现,具体的根底资源的数据处理过程是在仓储实现中实现的。这样做的益处是,防止将仓储实现的代码混入下层业务逻辑中。如果当前替换数据库,因为做了根底资源的共性的代码隔离,所以实现了应用逻辑与根底资源的解耦。在更换数据库时只须要更换仓储相干的代码就能够了,利用的逻辑不会受太大的影响。
拆散畛域(调用关系)
分层是为了各层独立演进的,下层应用上层定义的服务,而上层对下层无所不知,另外每一层对下层暗藏细节实现,依赖契约交互,独立层在技术计划调整时只有恪守契约则能够做到下层无感知迁徙,这样也不便各层的保护和标准化工作。DDD 有两种架构,严格分层架构和涣散分层架构。优化后的 DDD 分层架构模型就属于严格分层架构,任何层只能对位于其间接下方的层产生依赖。而传统的 DDD 分层架构则属于涣散分层架构,它容许某层与其任意下方的层产生依赖,倡议应用严格分层架构。
对象流转
interfaces 层:request、response
application 层:DTO(data transfer object)
domain 层:entity、VO(value object)
infrastructure 层:PO(persist object)
首先用户接口层通过 request/response 对象来进行跨过程间的交互数据;应用服务层应用(DTO)来进行数据交互;在畛域外部,咱们通过畛域对象(entity/VO)作为畛域外部的数据和行为载体;在基础设施层,咱们应用长久化对象(PO)进行数据库资源的交互。
防腐层
也被称适配层或者转换层
在一个上下文中,有时须要对外部上下文进行拜访,通常会引入防腐层的概念来对外部上下文的拜访进行一次本义。
有以下几种状况会思考引入防腐层:
- 须要将下层的模型翻译成以后层能够了解的模型。比方下层定义了两个值 id 和 type,不同的 type 所对应的 id 含意不同,那么翻译的时候,能够间接依据 type 进行转换。
- 不同上下文之间的团队协作关系,如果有依赖关系,倡议引入防腐层,防止上层变动造成下层的变动,即防止参数的援用传递。
- 防止将下层过多的参数传递到上层。
畛域事件
畛域事件是畛域模型中十分重要的一环,畛域事件将会导致进一步的业务操作,有助于实现业务的解耦,并实现业务闭环。
举例来说,畛域事件是业务流程的一个两头步骤,比方出价畛域出价胜利后将告诉库存域增加库存动作;也可能是批处理过程产生的事件,比方求购域批处理程序扫描求购业务表判断是否要的群体触发根底服务域的 push 服务告知求购用户最新的求购停顿。
畛域事件能够切断畛域模型之间的强依赖关系,事件公布实现后,公布方不用关怀后续订阅方事件处理是否胜利,这样能够实现畛域模型的解耦,保护畛域模型的独立性和数据的一致性。通过畛域事件 + 弥补机制来达到最终一致性,进步零碎的稳定性和性能;
在一致性要求不高时,能够通过畛域事件订阅器间接向音讯队列发送事件。
对一致性要求高时,须要先将事件存储,而后通过后盾线程加载并散发到音讯队列。
以上就是出价组在二次 DDD 革新中的一些总结,上面以一个例子做结尾吧。
案例
销售库存占用
应用服务层次要是做流程编排等轻量逻辑,畛域服务层实现畛域内理论逻辑并长久化数据。
文|CJ BOYS
CJ 是出价的拼音首字母缩写,因为组内都是 Boy,于是团队起名 CJ BOYS。
关注得物技术,携手走向技术的云端