黄光芳, 金义富
(湛江师范学院 a.信息与教育技术中心;b.科技处,广东湛江524048)
现代信息化高速发展的今天,越来越多的应用系统都被构建在Web之上,关于它的开发也经历了面向过程、面向对象、面向服务架构(SOA)等开发过程,其复杂性也越来越高,使用的技术平台有Java、.NET、Ruby等,目前基于Web的多层架构体系(如 J2EE、ASP.NET)已经成为解决企业级应用的主要途径。因此,如何在软件中更好地处理业务逻辑,且高质量高效率地完成软件的开发便成为人们日益重视的问题。但是长期以来,传统的Web平台开发工作趋向于一种以技术为先导的开发方式,开发的过程即先从业务方面分析企业需求,然后把需求传达给开发团队,开发人员再依据需求的描述创造出最有可能的设想进行开发[1]。这些软件开发的指导原则依然是基于数据库设计而非面向对象设计,即开发人员一开始便根据需求建立数据库模型,系统中的业务对象被机械化的数据库CRUD操作代替,忽略面向对象的开发思想,缺少领域模型的开发,业务逻辑设计混乱,不能及时有效地反映用户需求,开发的系统缺少通用性和科学性等。针对以上开发方法的不足,文中将领域驱动设计的开发思想融入到业务逻辑复杂的Web平台的构建中,力求寻找一种更佳的企业级Web平台的开发方案。
领域驱动设计(Domain-Driven Design,DDD)是领域驱动设计大师Eric Evans在2004年发表的文献[1]中提出的软件开发概念,是一种基于模型驱动开发(MDD)思想的崭新的开发方式,目的是让软件系统在实现时准确的基于对真实业务过程的建模并根据真实业务过程的调整而调整。
领域驱动设计很好地遵循了关注点分离的原则,提出了成熟、清晰的分层架构,对领域对象进行了明确的策略和职责划分,让领域对象和现实世界中的业务形成良好的映射关系,为领域专家与开发人员搭建了沟通的桥梁。领域模型分为用户界面层、应用层、领域层和基础结构层四层,如图1所示。
图1 领域驱动设计的分层架构
用户界面层。主要负责向用户呈现信息、接受并解释用户命令,并把用户的请求发送到应用层或领域层。
应用层。定义了系统要完成的工作,不包含业务逻辑的实现,只保留任务的进度状态。
领域层。系统的核心,负责系统业务逻辑的实现工作,包含领域行为和模型。
基础结构层。为上层提供通用的技术能力,持久化业务对象以及实现应用层的管理等。
领域驱动设计除了对系统架构进行了分层描述外,还对对象(Object)做了明确的职责和策略划分,划分的对象有实体、值对象、工厂、仓储、服务、聚合等。
实体(Entities)。具备唯一ID,能够被持久化,具备业务逻辑,对应现实世界业务对象。
值对象(Value objects)。不具有唯一ID,由对象的属性描述,一般为内存中的临时对象,可以用来传递参数或对实体进行补充描述。
工厂(Factories)。主要用来创建实体,目前架构实践中一般采用IOC容器来实现工厂的功能。
仓储(Repositories)。用来管理实体的集合,封装持久化框架。
服务(Services)。为上层建筑提供可操作的接口,负责对领域对象进行调度和封装,同时可以对外提供各种形式的服务。
聚合(Aggregate)。主要将复杂领域中关系密切的多个实体的合并在一起,以降低领域的复杂性。聚合内实体可以相互引用,两个聚合之间的实体必须通过聚合根才能引用。
领域驱动设计的专注点在于领域模型的研究,因为在领域驱动设计中,它是以模型驱动设计为根基,以软件领域为着眼点,专注于领域模型的构建与代码匹配,并将模型作为领域专家和软件开发人员交流的一种开发方式[3]。相对于以往的数据库驱动的设计方式,这种新开发方式具有以下优势:
(1)复用。在领域驱动设计中,领域对象是核心,每个领域对象都是一个相对完整的内聚的业务对象描述,所以可以形成直接的复用。同时设计过程是基于领域对象而不是基于数据库的Schema,所以整个设计也是可以复用。
(2)注重实践。专注于具体场景的应用,领域专家、开发人员及用户使用模型元素之间的交互来理清系统中的业务逻辑,且按模型允许方式将各种概念结合在一起,然后将这些应用到图和代码中,消除了开发中思想的隔膜,保证领域模型与系统业务相对应。
(3)重构。领域驱动设计采用面对对象的设计,使领域模型在设计中随时响应用户提出新的软件需求,根据业务逻辑向更深层次重构。
这里以一个业务逻辑稍为简单的网上书店的电子商务平台来阐述领域驱动设计在Web平台中应用情况。该系统实现网上书店的常用功能:包括浏览书籍、挑选书籍、提交订单、查看订单、自动折扣、处理订单、取消订单等。未登录用户可以浏览和挑选书籍;已登录用户可以提交和查看自己相关的订单;管理员可以处理订单。结合书店的业务场景,抽象出以下一些领域对象,如订单、账户、书籍、购物车、购物项、折扣等,现实业务和领域对象的对应关系为:订单—Order,账户—Account,书籍—Book,购物车—Cart,购物项—Item,折扣—Discount。通过对场景及业务逻辑的分析和设计,得到的领域模型如图2所示。
图2 网上书店业务逻辑图
在图2中,首先BookStoreAction负责处理表示层的请求,并把请求转发给业务服务IBookStoreBS,业务服务负责调度上图中显示的领域对象,处理该场景的所有业务。从图中我们还可以清晰地看到各个领域对象之间的关系。Order和Cart都聚合了Item,都是聚合根,对应都是1…n,Item聚合了Book,Item是一个聚合根,Book是一个实体,对应关系1…n,Order分别与折扣、账户发生关联和调用等,整个网上书店的场景就这样描述出来了。
与事务脚本的编程模式不同,领域驱动设计不是把业务逻辑放在业务服务(Business Service)层中,而是由具备属性、行为和状态的领域对象处理。例如Order类,如果是贫血的POJO,那它内部只有与数据表字段对应的属性以及getter和setter方法,而在领域驱动设计中,则是一个相对独立的、能够处理自身关联业务的领域对象。如在本系统中,订单类中除了联系方式、邮寄地址等基本属性外,还有以下领域相关的行为:
(1)init(·),结算时调用方法,根据当前用户与购物车中的Items初始化订单,供用户修改。
(2)submit(·),提交订单时调用的方法,保存订单。
(3)cancel(·),取消订单,把订单和相关item的状态设置为“已取消”,然后委托基础结构层进行持久化。
(4)dispose(·),处理订单,首先更新订单项的状态,然后委托基础结构层持久化订单数据。
……
通过以上的描述,我们可以看到,Order类基本上覆盖了现实世界中订单这个业务的所有行为和状态,是相对内聚的,这样的特性使其复用性大大增加,即使未来开发新的模块,涉及到订单业务的,可以直接复用Order类,同时在后期维护中,如果想了解订单的业务,直接读Order的代码就可以了。
在项目开发中,良好的框架设计可以有效地提高工作效率,缩短开发时间、降低开发成本,增强程序的可维护性和可扩展性。根据领域模型的特点,在具体的项目开发中,运用分层架构和.NET提供的实体框架[4],对一些相关的类和框架进行抽象设计,设计了一些通用模块,如层超类、接口、仓储框架、仓储工厂、仓储基类和工作单元等,为将来构建一个大型的网上书店的设计与实现打下基础,而且,项目的一些通用模块与具体的实现细节无关,即可以直接应用到其他系统的设计中,提高开发效率。
当某一层中所有的对象都具有某些方法,同时为了避免这些方法在系统内被多次复制而产生冗余时,便将这些行为移到一个通用的类中,这个类就是层超类型(Layer Supertype),然后其他接口都被重构[6]到这一超类中。在网上书店的系统框架中,设计一个EntityBase的抽象类作为基础结构层的层超类型,领域模型中的所有实体类都继承它的标识Key。
层超类的分别包含一个缺省的构造器和一个重载构造器,重载构造器允许传入一个只读属性的Key,考虑到不同的实体标识类型不一致,所以这里的Key的类型是System.Object,体现了超类设计的灵活性。
仓储是领域层与基础结构层的一个衔接组件,领域层通过仓储访问外部存储机制,这样就使得领域层无需关心任何技术架构上的实现细节。因此,仓储这个角色的职责不仅仅是读取、保存、查询、删除,它还解耦了领域层与基础结构层,将繁杂的数据库操作从领域层中解放出来,使开发人员更好地专注于领域层的设计。在实践中,可以使用依赖注入[7]的方式,将仓储实例注入到领域层,从而获得灵活的体系结构,如图3所示。
在本系统中,IRepository(仓储接口)是一个泛型接 口[8],泛 型 类 型 被 where 子 句 限 定 为EntityFramework中的EntityBase,该接口使用泛型,方便被其他特定的聚合类型的仓储继承后使用,定义方式见如下代码所示:
为了消除大量重复的代码,仓储框架将在仓储接口的基础上添加了抽象的仓储基类RepositoryBase,该基类实现了IRepository<T>,从而方便系统中具体仓储类的重构。SqlRepositoryBase<T>基类实现了RepositoryBase<T>类,是专门针对SQL Server数据库读取和写入数据的通用的基类,它可以减少大量具体仓储中的重复代码。具体如图4所示。
图3 基础结构层将仓储实现注入领域层
图4 仓储框架的构建模式
在具体业务的实现中,仓储作为领域模型的一部分,领域模型依赖于仓储的抽象,仓储提供一套方法将聚合从持久层中提取出来,包括一系列的查询,保存根的方法,以及从根开始访问聚合内其他领域模型的方法。如在Order(订单)聚合根中,具体实现仓储为Order,该仓储继承了 IOrder接口和 SqlRepositoryBase抽象类,SqlRepositoryBase继承了仓储框架中的RepositoryBase类。为了方便仓储类重构,系统在初始的框架RepositoryBase基类中添加了一些常用的方法如 Add、Edit、Delete、FindBy、FindAll 等,然 在SqlRepositoryBase基类中覆盖该方法,于是在具体的仓储实现类Order中便可以直接调用这些方法而不需要再次实现,当然,如果实现的功能稍有差导,也可以重写该方法。同理,其他实现类如Account(账户),Book(书籍),Cart(购物车)都可以像Order类那样创建仓储并继承仓储基类和层超类,这样整个系统便提高了代码利用率,同时也方便系统向更深层次模型重构,以获取更合理的实现方式,确保系统在开发中有更好的可维护性和可扩展性。
工厂模式[9](Factory)实际就是在处理复杂的对象创建时,将对象创建的职责交给第三方来完成,从而降低对象创建的复杂度,增加系统的可靠性。这里本系统使用分离接口模式(Separated Interface)创建仓储工厂实例。在基础结构层中共设计了四个配置类来创建仓储的映射。通过读取配置节设置,并复制到一个良好的对象模型中,仓储工厂类便可以利用这个对象模型创建仓储。工厂类首先使用反射[10]来取得接口类型的名称,然后查找基于映射配置中的将要创建的仓储类型,如没有,则使用Activator对象的反射能力创建一个正确的仓储工厂实例,并将其放到静态词典中以供下次检索,从而避免系统频繁构建仓储,节省系统开销。
Unit of Work(UoW)模式在企业应用架构中被广泛使用,它能够将领域模型中对象状态的变化收集起来,并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据库中,减少与数据库交互次数,提高系统性能[11]。在系统基础结构层中设计了一个工作单元接口IUoW,用于标识已经添加、更改或移除的实体,UoW类实现IUoW接口,UoW类分别遍历所有删除、添加和更改的注册信息,整个操作被包装进一个事务,仅调用接口的Commit方法就可以将所有的更改提交到数据库中,保证了数据的一致性。UoW类引用了IUnitOfWorkRespository接口,这个接口被仓储中的RepositoryBase基类实现,于是工作单元的实现便回调到仓储里实现。
应用服务层位于分层架构中的第二层,它不负责处理任何业务逻辑,主要是协调其他层的数据传输、工厂调用或对象发送等,为业务逻辑的正确执行提供适当的运行环境。在应用服务层,采用了微软的窗口通信基础[12](Windows Communication Foundation,WCF)技术为系统的上下层提供数据通信。WCF使用SOAP通信机制[13],保证了系统之间的互操作性,即使是运行不同开发语言,也可以跨进程、跨机器甚至于跨平台的通信,同时可以提供高效且安全性的访问。WCF在数据传输中使用数据合约(Data Contract)来订定双方沟通时的数据格式,如涉及到订单处理的部分,应用层仅仅是协调仓储操作和事务处理,业务逻辑由Order的仓储方法实现。对于部分不属于单独的对象、不能轻易地合并到某个实体和值对象的操作或者涉及到几个场景实体的业务逻辑,这种行为一般放在服务层,声明为服务,设计好后供表示层直接调用,简化了对领域模型的设计,使领域模型更纯净。如在管理员处理订单这个场景中,首先需要根据订单信息获取账户,根据账户信息确定折扣率,同时进行余额校验,如果校验通过,就会调用订单对象的dispose方法处理订单,这个场景会涉及到 Order、Account、Discount等对象,这样的业务逻辑,则声明为一种服务,在应用层实现。
对于领域驱动设计,最核心的就是如何解决复杂业务的设计问题,如何抓住业务逻辑的本质,并转换成业务逻辑模型。领域驱动设计良好的支撑框架和富有弹性的需求分析过程,已经得到许多企业的认可,并且它不依附于哪一个特定的平台,这就为广大开发者提供更有弹性的开发空间,更有利领域驱动设计的思想融入各个领域Web平台中,加快这方面的研究和应用。目前已经有许多开发人员尝试着应用到石油、航海[14]、物流[15]及信息系统等项目中,随着软件市场的进一步成熟和客户需求的不断提高,相信这是一个切实可行且具有很好应用前景的开发方法。
[1] 宋 波,赵永翼,张 悦,等.一种规范Web开发框架的研究与实现[J].微电子学与计算机,2007(7):201-208.
[2] Eric Evan.领域驱动设计-软件核心复杂性应对之道[M].陈大峰,张泽鑫译.北京:清华大学出版社,2006.
[3] 严欣品.领域驱动设计方法的研究及其应用[D].南昌:南昌大学,2010.
[4] 雷 蕾,陆新泉,李 睿,等.应用_NET框架命名空间技术实现Web测试自动化[J].计算机应用研究,2010,27(6):.
[5] Tim Macarthy.领域驱动设计C#2008实现[M].UMLChina译.北京:清华大学出版社,2010.
[6] 科瑞夫斯盖.重构与模式[M].杨 光,刘基诚译.北京:人民邮电出版社,2006.
[7] 张 浩.利用反向控制原则和依赖注入的可复用框架设计解耦方法[J].计算机应用,2010,30(12):227-229.
[8] 陈叶旺,余金山.泛型编程与设计模式[J].计算机科学,2006,33(4):253-257.
[9] 彭世康,周逢权.新的设计模式——数组工厂和数组原型模式[J].计算机应用,2012,32(S2):107-112.
[10] 吴东庆,胡小健,杨逢建.反射机制下类工厂模式的实现与研究[J].计算机应用,2006,26(3):705-707.
[11] Martin Fowler.企业应用架构模式[M].王怀民,周斌译.北京:机械工作出版社,2004.
[12] 刘黎志,吴云韬.应用WCF分布式框架实现移动数据同步[J].计算机应用,2011,12(31):3281-3284.
[13] 刘嘉俊.基于SOA架构的ERP与电子商务系统研究[J].企业经济,2011(5):88-90.
[14] 张金松.领域驱动设计在航务海事系统中的应用研究[D].大连:大连海事大学,2010.
[15] 丁 涛.基于领域驱动设计的物流平台系统实现[D].成都:电子科技大学,2010.