领域驱动设计之我见-实现模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tidu2chengfo/article/details/81123744

领域驱动设计,这个名词从字面上来看分为三个部分,领域,设计,驱动。前面我针对领域做了一些叙述,总结起来就是一句话:技术专家和业务专家一起采用面向对象的思想来提取业务模型。那么接下来要看设计了,有了比较恰当的业务模型了,怎么将其设计为一个合理的软件系统呢?

在网上搜一下,随处可见各大博主对领域驱动设计的精彩描述,但是有读过埃文斯前辈的《领域驱动设计:软件核心复杂性应对之道》的读者会发现,各大博文基本上都是对该书中【第二部分 模型驱动设计的构造块】一章的概述。当然笔者也喜欢随大流,首先介绍一下模型驱动设计的构造块。埃文斯前辈设计中将整个系统分层为接口层(UserInterface),应用层(Application)、领域层(Domain)、基础设施层(Infrastructure),如下图所示

如上设计,最上层为用户接口层,主要负责系统入口协议等,该层不处理任何业务逻辑,只负责调用接入(入口协议),应用转发(调用Application层),如上图的设计中允许UserInface层跨层调用Infrastructure层,而现实的设计中我们不建议这样。

Application是应用层,和以往事务脚本的Service是截然不同的东西,该层中并不做详细的业务逻辑的封装,而是创建Domian并调用Domain中的相应方法完成业务逻辑处理,并调用Infrastructure完成Domain的持久化操作,该层需要负责事务的控制,保证数据的一致性。

Domain层的东西是核心,核心的业务逻辑应该以Domain为对象进行分类封装,Domain的划分以业务为基准(前面章节已经做了详解),Domain层在技术层面的建模通用技巧在下面会做详细介绍,该层只能向下调用Infrastructure完成自身模型的初始化和持久化。

Infrastructure层,该层类似于以往事务脚本的Dao层,然而在概念上,Infrastructure却是另外一个东西。以往的面向事务脚本的编程中,以数据表为主,所有的事务脚本直指目的就是完成表的CURD,而DDD中以模型为核心,Infrastructure层是为了重建已有的Domain,并在退出时持久化内存中的Domain对象。Infrastructure层不仅包含对关系数据库的处理,还包括对分布式缓存处理、对外系统的接入(integration)以及分布式消息队列的push操作。总而言之,就如infrastructure的直译“基础设施”一样,infrastructure层就像应用系统之下的基础设施一样,对应用提供数据的供给(模型重建/初始化模型)和数据的长久保持。

完成了DDD的总体架构设计之后,再来看看详细的domain模型设计。埃文斯前辈将模型分为值(ValueObject)、实体(Entity)、服务(Service)、模型工厂(Factory)、数据仓库(Repository),如下图所示

Entity/ValueObject

首先,我们来看一下什么叫做ValueObject,这个需要和Entity对照着来说,两者在代码中皆表示为一个类(对象),从业务上来讲也分别表示一个业务实体。那么什么样的实体应该定义为一个ValueObject或者一个Entity呢?从字面上来看,ValueObject就是一个值对象,而Entity是实体。实际上在DDD中也就是这意思,Entity是一个完整的具有生命周期的可以通过唯一标识来识别东西,而ValueObject则是Entity的一类属性。

例如,会员可以认为是一个Entity,一个会员就是具有生命周期的东西,具有唯一的标识可以将A用户和B用户分开(唯一标识:账户号),而这个用户的地址(例如:南京市/玄武区/徐庄软件园)就应该定义为一个ValueObject对象,当我们定义好一个地址,它既可以是A用户的地址也可以是B用户地址,当我需要更新A用户地址时,可以直接销毁A用关联的地址对象然后重新创建一个地址对象关联到A用户。

Service

既然我们已经将业务逻辑都封装在Entity和ValueObject中了,为什么还需要一个Service呢?事实上,当我们建模之后发现有些业务既不单单属于A领域对象,又不单单属于B领域对象,我们可以提取出一个单独的服务来完成此项业务。例如当我们把金融会员业务划分为UserBase、PaymentPassword、LoginPassword、Auth和Lable后发现用户注册的业务逻辑同时跨越UserBase和LoginPassword两个领域模块,此时把注册的业务分装在任何一个领域中都是不合适的,如果在各个领域中同时添加regist方法也不合适,那么我们可以抽象出一个register的Service,方法中处理用户基础信息的校验,并调用LoginPassword的加解密方法进行帐密处理。

Service和以往事务脚本的Service一样吗?只能说非常像,唯一不同的是,以往事务脚本的Service是自己全权负责业务逻辑,而此处的Service不仅编写业务逻辑,还会调用Entity和 ValueObject的方法来协调Entity和ValueObject,从而避免Entity和ValueObject的耦合。

Factory

领域工厂,这个没什么特殊的,在以往的编程经验中我们多处使用过工厂模式,并在工厂模式中获得过好处,让对象的创建更加简洁,当然工厂并非必须命名一个以Factory结尾的类,才叫工厂。对象的构造函数本身就是一个工厂方法,一个Entity对象内部能够构造出另外一个Entity对象的方法也是工厂方法,例如UserBase对象中的createDefaultUserHeadImg,用户注册时通过该方法可以给用户创建一个随机的系统头像UserHeadImg,该方法就是一个工厂方法。

Repository

数据仓库,主要完成领域对象的重建以及对象的持久化操作。对于一个从DDD着手设计良好的数据库系统,该Repository的设计主要就是调用infrastructure层来完成表的CURD、缓存的操作以及外系统接口的调用。然而如果我们希望将一个老旧的基于事务脚本开发的系统,重构为基于DDD设计的系统,那么Repository的设计会是一个挑战。

以上对领域驱动设计的各个构造快做了一个简速,这些只是软件设计中的一个通用的套路,如前章讲过,对于一个面向对象建模大师,不需要任何套路也能做好DDD开发设计,但是对于我等二流的工程师,最好还是乖乖的跟随埃文斯前辈的步伐,让系统整齐划一。

原文链接

猜你喜欢

转载自blog.csdn.net/tidu2chengfo/article/details/81123744