吴昌雨,王善勤,李云松,刘青,邹军国
(滁州职业技术学院,安徽滁州239000)
传统的软件开发经常是分析与设计割裂的,一个典型的例子就是在我国系统分析师、系统设计师就是两种不同的职称,分析与设计分离导致的后果就是分析的结果往往不能直接用于设计编程,设计者需要从分析文档中给出数据设计逆推出系统的行为,最终造成设计出的软件并不能真正的体现需求,而领域驱动设计(Domain-Driven Design,DDD)是Eric Evans在其著作《Domain-Driven Design–Tackling Complexity in the Heart of Software》[1]中首次提出的一种用于指导复杂软件设计与开发的一整套基于领域模型的系统分析和设计的方法.它将软件分析与设计的关注点从数据引导到业务上来,打破了分析与设计的隔阂,提出了领域模型概念,使得软件能够适应更灵活的需求变更.
本文从教学资源共享平台的分析与设计入手,阐述了领域驱动设计的相关理论及其应用,通过应用六边形架构实现了系统原型,为类似软件开发过程中领域驱动设计方法的应用提供借鉴.
领域驱动设计是面向对象的分析与设计(Object Oriented Analysis Design,OOAD)的扩展和延伸,它既是面向对象设计的补充,又完成了对面向对象设计的超越,相对OOAD而言,它的主要变化在于能够使用领域模型准确反应业务语言,也正因为此,它几乎成了目前开发大中型复杂软件系统的主流方法.国内外研究学者对领域驱动设计的核心构成要素如分层架构、实体、值对象服务等概念展开了大量研究.例如Vaughn Vernon的《Implementing Domain-Driven Design》(实现领域驱动设计)从战略设计高度研究了包括领域、实体、值对象、受限上下文等概念如何设计,并从战术设计的角度研究了其如何实施[2];Mat Wall等人从Guardian.co.uk网站,采用领域驱动设计后其架构演进的角度着手分析了如何在既有项目上应用领域驱动设计并不断演进[3];Jimmy Nilsson在其著作《Applying domain-driven design and patterns》(领域驱动设计与模式实战)中,展示了如何应用测试驱动开发(TDD)不断改进领域驱动设计以及应用领域驱动设计创建高质量企业级应用架构的过程[4].
和传统软件开发一样,软件开发首先是从软件专家与项目领域专家的交流开始,但这种交流通常会存在障碍,原因是双方思维方式及问题的侧重点是不一致的,所以领域驱动设计的一个核心的原则就是使用一种基于模型的通用语言(Ubiquitous Language)实现相互的交流.图1展示了通用语言是介于开发者与领域专家组成的开发团队所使用的用于统一其行动及帮助创建统一模型的语言.
图1 构建通用语言
以教学资源共享平台开发为例,首先由领域专家对其需求进行定义:教师可以创建并管理课程;课程由章节构成,每个章节包括一定学时的教学内容;章节中的教学内容由文档、视频、音频等教学资源构成;学生可以浏览并收藏课程内容.
如果是使用传统的面向对象程序设计方法,根据其需求可以由动名词法得到一些实体类,类之间包含一些属性及其get/set方法,这些实体类的作用很单一,仅仅用于描述实体却没有任何与其业务逻辑相关的东西,业务逻辑将会被放到一个单独的service类中处理,这是一种典型的失血模型.而采用领域驱动设计方法则要求将业务逻辑集中于领域对象中,同样是上面的例子,课程领域模型可以被视为一个聚合,课程作为聚合根包含了章节与课程资源,聚合根内部包含有状态,并且这种状态不能直接暴露出去,另外聚合内部的对象通过聚合根实体与外界交互.因此从逻辑上得出这样一个结论:教师、学生作为用户与课程发生业务逻辑,而课程作为聚合根其内部包含了章节及教学资源,即形成了图2所示的领域模型.
图2 教学资源共享平台领域模型
这种领域模型准确的反应了业务,业务逻辑不是放在单独的业务逻辑类中处理,而是包含在领域对象中,每个领域对象都是包含了属性与业务逻辑相对完整的独立体,并与现实领域中的业务对象一一映射.领域模型则是由这些领域对象组成.这种设计方法,即保证了系统的可维护性、扩展性和复用性,同时也在处理复杂业务逻辑方面具有先天优势.
Eric Evans在《领域驱动设计》中给出了一个典型的四层参考架构,分别是用于展示信息,并解释用户命令的表现层;起到协调、调度作用的应用层;核心的领域层,包括业务领域的信息,以及业务对象的状态变更;提供业务对象的持久化等支撑的基础设施层.
这种分层架构很好的遵循了关注点分离的原则,对领域对象进行了明确的策略和职责划分,让领域对象和现实世界中的业务形成良好的映射关系,相比于传统的软件架构分层有如下特点:
1)应用层不包含业务逻辑,由领域层处理具体的业务操作
传统三层架构软件设计中,有专门用于处理业务逻辑的业务逻辑层(BLL),在这样的架构中,随着需求的变更业务逻辑处理类开始积聚越来越多的业务逻辑,而领域对象则成为单纯的数据载体造成了“肥的服务层”和“贫血的领域模型”.而在DDD方法指导下,领域模型应该侧重于具体的业务操作领域.领域对象由实体和值对象构成,实体类具备自己的属性和行为、状态,可以聚合,实体类之间可以有聚合关联等关系,可以借由基础设施层进行持久化.
2)领域层不依赖于实现的细节,层与层之间松耦合
在软件分层结构中,层通常是职责划分为独立且紧密结合的单元,比如传统三层架构中BLL层负责业务逻辑,它依赖于底层数据访问层的支持同时也为其上级表示层提供依赖,这种层与层之间的依赖关系看起来很自然,但在具体面对需求变化时,每一个层次的变更都有可能影响到其他层,并对系统的伸缩性产生负面影响.而在DDD中,领域层虽然负责处理整个系统的业务逻辑,但其设计是与其他层松耦合,即与其上下层之间没有依赖关系,领域模型业务逻辑的实现应该独立于持久化实现的细节.
事实上,DDD的具体实现并不依赖于特定架构,包括其参考架构的层次概念在实践中都是可以忽略的,本文针对教学资源共享平台采用了图3所示的六边形架构(Hexagonal architecture),图中左边是六边形架构,右边是资源共享平台实现过程中针对六边形架构的一些具体实现.
图3 六边形架构
这种六边形架构也可以称之为端口和适配器架构(Ports and Adapters architecture)[5],该架构设计目标是实现层次之间的解耦,在其核心的领域模型中包含了所有的业务逻辑与规则(但并不直接实现,由基础设施层通过DI注入);包围领域模型的是应用程序端口层,它负责接收请求,并交由领域层处理,这一层很薄,主要起到协调作用;最外层的是适配器层,负责以某种格式接受输入并产生输出,比如通过HTTP接受客户端请求并封装为端口能够理解的方式交给端口,再将处理结果转换为HTTP相应反馈给客户端.该架构的特点是组件与组件之间是相互平等的,模糊了层次概念,因为各层次之间的交互并不依赖于各自于实现的细节,都是通过接口实现,这一特性的实现取决与软件抽象及一些新技术手段的运用.具体来说,该架构的实现需要以下三种技术手段的配合:
1)OOP(Object Oriented Programming,面向对象编程),OOP仍然是领域实现中的重要原则,应充分利用其封装、继承、接口等特性设计领域对象;
2)DI((Dependency Injection,依赖注入),DDD要求领域对象既要具有业务逻辑但又不能依赖于具体实现细节,则只能通过DI的方式将数据持久化等业务逻辑注入到领域对象中;
3)AOP(Aspect Oriented Programming,面向方面编程),AOP可以很好的实现关注点横切,比如可以使用AOP将领域对象的业务规则检查、状态变化跟踪、数据缓存、事务管理等某个方面的问题从领域对象中移除出来,让领域对象更好的关注业务.
领域对象由实体及值对象构成,实体(Entities)类具有唯一的ID,能够实现持久化等业务逻辑,对应于现实世界中的业务对象,在系统中设计了Course等实体类;值对象无ID,由对象属性描述,可用于传递数据或对实体进行补充描述.
基于领域模型,教学资源共享平台的通过设计与分析教学资源共享平台领域层设计图如图4所示:
图4 教学资源共享平台领域建模
图中领域对象和现实业务的对应关系为:Teacher——教 师、Student——学 生、Course——课程、Lesson——教学章节、Resource——教学资源.这5个领域对象按照功能划分为两个模块,分别是用户模块和课程模块,将这些高关联度的类划分到一个模块,可以提供尽可能大的内聚性,从图3中可以看出每个模块通过一个定义好的接口被其他的模块访问,比如用户模块通过IUserService接口的实现类UserService服务与外部交互,其目的是降低系统耦合度.UserService以及CourseService都属于DDD中的领域服务,它为外部提供操作接口,负责对领域对象进行调度与封装并提供各种形式的服务,服务执行的操作代表了一个领域概念,被执行的操作通常会涉及到领域中的其他对象,以删除课程为例,该业务逻辑不仅仅需要删除课程还需考虑如何处理与其相关联的章节及教学资源,此时将业务逻辑放到服务中是一种更合理的做法.
在领域对象设计过程中还应该处理好对象之间的关系,通常领域对象之间会相互产生各种联系,甚至形成一个复杂的关系网,比如在教学资源共享平台中一门课程拥有多个教学章节,这是一个典型的一对多关系;一位老师可以创建多门课程应该也是一个一对多关系,但同样的一对多关系在设计过程中还应区别对待,对DDD中的领域模型而言,其设计目标并非让其具备完整的关联关系,而是尽量的简化关系.复杂的关联关系只会让管理对象生命周期变得困难,简化关系可以采取删除非基本关联关系、添加约束减少多重性、双向关联转为单项关联等手段实现.教学资源共享平台的开发采用了Groovy语言,以Course领域对象设计为例,其代码如下:
代码中展示了Course与Lesson之间的一对多关系,因为Course与Lesson之间业务上紧密相连,其关系应在模型中体现.但在Course与Teacher之间的关系的处理上,考虑到逻辑上他们分属两个不同的模块,Course领域对象中维护其关系将导致额外的复杂性,因而并未在Course领域对象中直接体现两者之间的关系,而是通过teacherID维护其关联关系,另外在代码中通过设置约束来维护其关联关系的完整性,比如定义一个约束用来保证只有课程的创建者才可以执行课程的维护,代码如下:
def isCourseOwner(Teacher teacherInstance,Course courseInstance){
return courseInstance.teacher.id.equals(teacher-Instance.id)
}
在进行删除、修改等操作之前需要先调用该方法确认当前操作者与课程中TeacherID一致才可以继续进行.
包围领域模型的是应用程序端口层它负责接收请求,并交由领域层处理,这一层很薄,主要起到协调作用.
适配器层是最外部的一层,它包含了与各种外部设备对接的适配器,比如针对Web浏览器用户的适配器、针对数据库交互的适配器、针对外部服务的适配器、甚至包括针对自身内部操作的适配器等,这些适配器有些需要自行开发也有些可以利用基础实施层的一些中间件来实现其功能.
教学资源共享平台的基础设施层主要利用了一些JavaEE开源组件来构建,其中包括Hibernate实现数据持久化;Spring MVC框架实现IOC及AOP;JDBC实现数据库驱动等.
采用领域驱动设计的最大优势是直接将核心业务逻辑与领域模型结合起来,而不用向传统软件设计那样分割为数据与行为,这种优势使其在复杂软件设计中已成为主流思想,基于其设计教学资源共享平台充分应用了领域驱动设计方法的相关理论,在其四层参考架构的基础上研究了基于六边形架构的具体实现,模糊了分层概念,更为充分的体现了软件设计中高内聚、低耦合的要求.
[1]Eric Evans.Domain-Driven Design[M].Boston:Addison-Wesley Professional,2003.
[2]Vaughn Vernon.Implementing Domain-Driven Design[M].Boston:Addison-Wesley Professional,2013.
[3]Mat Wall,Nik Silver.演进架构中的领域驱动设计.[EB/OL].王丽娟译.http://www.infoq.com/cn/articles/ddd-evolving-architecture.2009.
[4]Jimmy Nilsson.领域驱动设计与模式实战[M].赵俐,马燕新,译.北京:人民邮电出版社,2009.
[5]Alistair Cockburn.Hexagonal architecture.[EB/OL].http://alistair.cockburn.us/Hexagonal+architecture.2010.