[转编] 企业架构模式之领域逻辑模式的实现(事务脚本、领域模型、表模块、活动记录)
分层架构与业务逻辑实现方式
一、分层架构
在当今软件系统中,常用的软件架构思想就是分层,分层思想是现代软件架构的主要思想。无论是企业级应用系统(如:CRM,ERP,OA,电子商务平台),专用软件(如:OS、SVN、IDE 等),还有协议之类(TCP/IP,OSI等) 绝大部分都采用分层架构思想进行设计的。
分层(Layer)不一定就是人们常说的二,三层,多层系统,因为这些说法都是分层架构的一些具体表现形式,分层是一种设计思想,也可以称之为一种软件架构模式(Pattern),这种思想的核心在于:划分系统的职责(Responsibility),如果这个系统的职责你分析清楚了,你的基于设计思路差不多就定下来了。你可以去看看,很多的现在代软件,不是一定是web方面。例如:SVN这样的源代码管理软件、
.NET Framework也是分层,Eclipse也是,TCP/IP更加是,还有像操作系统(OS)、编译器(Compiler),很多流行框架(Framework)也是分层。其实,MVC不也是分层,也就是把模型(Model)、视图(View)、控制器(Controller)三个不同职责分开。
那我们看看今天的企业级应用系统(很多说是web项目,其他我不认为是这样,因为web只是一种外在表现形式,我们可以用desktop程序,flash等作为表现形式),企业级应用系统很多人一说就是三层架构,其实确实也是这样的。即:表示层,业务层,数据层。当然还有其他的分层,如:表示层,服务层(服务外观层),业务逻辑层,数据映射层,数据层。也有分成:表现层,中间层,数据访问层等等。(注意这些都是逻辑上分层结构一般用Layer,物理上的分层结构,一般讲的是部署结构一般用tier)总体上都可以看成是三层:表现层,业务逻辑层(也可以说是领域层或领域逻辑层),数据层。像Spring,Structs、ORM等一些框架,他们都是在不同的层上的相关实现技术。
二、业务逻辑几种实现方式
现在我们再看看,企业级系统中最核心是哪一层?肯定是业务层,因为企业级系统主要是与业务打交道(其实几乎所有软件都是实现业务,企业级系统业务逻辑主要偏向于商业逻辑,其他系统,像游戏,自动化控制、支撑系统等把业务看成是算法),而且业务是每个系统都不尽相同的。“业务逻辑是最没有逻辑的东西” [Fowler PoEAA,2003]。而且企业级系统的变化与改变大多都在业务层上。那么,做好企业级系统,首先主要分析好业务系统。你可以看看,现今所有的框架在整体结构(spring,structs,等要求系统按MVC结构来开发),表示层(jquery,extjs等),与数据层(ORM之类)做得最多,有没有业务的框架?(有,但是很少,而且只能是业务比较有规律的地方,像一些财务系统,有些权限系统,当然还有工作流系统)因为业务逻辑每个系统都很可能不一样,没办法通用。那么有什么办法以比较好的方式实现业务逻辑呢。现在终于说到主要问题 上来了:也就是业务逻辑(Business Logic)的实现方式,也叫做领域逻辑(Domain Logic)的实现方式。一般来说,有以下几种:
1.事务脚本(Transaction scripts)
2.领域模型(Domain Model)
3.表模块(Table Module)
4.活动记录(Active Record)
我们用得最多就是事务脚本方式(像微软的Petshop4.0,很多国内的三层结构代码生成工具生成的系统,而且这种方式很多公司,很多企业级的项目都在用,还有一些框架引导你用这种方式实现)。这种方式最大的特点就是:简单实用。
为什么叫事务脚本(Transaction scripts)呢?也就是实现一个功能,就直接写一个过程(方法),系统的业务就是分散在一个个这样的过程里,像早期用ASP做的大部分系统,一些使用存储过程系统等,尤其是使用存储过程系统,所有业务逻辑都写在存储过程里,很明显的事务脚本实现方式。想一想,我们在业务层实现一个业务时,一般就是这么做的,要实现一个什么功能,在脑子里想一想,然后找到那个对应的类,然后再定义一个方法,加上一堆参数。就开始写这个方法的实现代码,要是逻辑复杂点,这方法里一堆ifesle,是不是?如果逻辑不复杂,这种实现到没什么问题,也很方便的。而且,有时候,发现一个类里好多方法,而且大部分是public的。有时候仔细看看,这个类已经不再是按面向对象方式来实现,虽然你用的是OO语言(java,C#,Ruby等),也用了类,接口,继承、多态等技术手段,但是你是否发现系统中对象之间的协作是多么难,甚至你都觉得系统都不存或很少有几个对象协作完成一件事情的,有时你会迷惑很多业务层的类是否应该直接定义成静态类就可以了,根本不需要实例化成一个对象。你还发现,有些方法(功能职责)根本不属于这个类或对象的。这样一来一去,类的职责乱了,方法多了,代码也没重构,越来越乱,最后头都大了。所以这就是事务脚本的特点,业务不复杂的系统用这样方式很方便,对技术人员要求也不是很高,因为它的实现思维还是按过程方式,大部分程序员都习惯性这样,但是一旦业务变化复杂,系统日益不断的变化,这种方式就变得不堪重负了。
领域模型(Domain Model),你也可称它为业务模型,这种方式是现今在国内外很多大师级人物提倡的实现方式。这种方式最大的好处,它采用是面向对象方式来分析与设计业务逻辑,很多经验不足的人就会反问,难道我用事务脚本方式就没有用面向对象的方式还分析与设计,我系统里面可全是类、继承,接口等,那我请问你,你每个类职责单一(SRP)么?或者说你把每个类的职责分配好了没有,就像你会用C#、java、Ruby了,那为何还会有《设计模式》呢?我想很多人都会沉默一下,其实要把职责分清楚是一件不容易的事,但也不是不可能,这需要丰富面向对象分析与设计及程序设计的经验(很多人觉得不需太多编码经验,我个人是很反对的,因为只有对程序设计语言也很熟悉,才直正设计出优秀系统,特别是每种语言在不同平台、框架还是有细微的差别的),还要准确理解与把握系统的业务需求,再经过不断迭代,精化才最终得出一个比稳定的业务模型。引申《重构》的一句话:“任何傻瓜都能写出计算机可以理解的代码,唯有写出人能容易理解的代码才是优秀的程序员” [Fowler 2002]。那是不是:任何了解OO语言的人都会使用类、接口、继承等特性写代码,唯有能写出职责分明,结构清晰的代码才是优秀的面向对象程序员呢?
“领域模型实现方式,不再是由一个过程来控制用户某一动作的逻辑,而是由每一个对象都承担一部分相关逻辑”[Fowler PoEAA,2003]。也就是说实现一个业务,是相关领域对象的一系列协作与交互的结果,不再像事务脚本那样直接在一个过程中。这好比一个人要完成一个动作都是人体各个器官相互合作协调来完成的,什么时候你见过一个动作,如吃饭,我只要口张开就可以了。
因此,领域模型实现方式,它一般会先进行业务建模,有时候把业务模型也称之为概念模型,我们在关系数据库理论里有E-R模型,所以有些人也称之为实体模型。其实,业务,概念模型与实体模型还是有区别的业务模型其实是现实问题域进行分析或抽象,这与实体差不多。但是业务模型最终要是用OO方式去试,实体模型要映射成关系模型。因此,业务模型在后期迭代与精化时,不得不采用一些OO手段,如对象职责(Responsibility),角色(Role),协作(Collaboration)等。而且实体模型则要考虑关系映射,查询优化,数据冗余的问题。如果你用面向对象方式来实现系统,业务层实现方式采用领域模型方式来实现是最好的,因为他直接与OO语言映射,思维方式与实现方式统一,所以他可以解决很复杂的业务系统,而且还可以得到很好扩展性,维护性与复用性。
我可以具体说明:例如interactiong项目,那个消息发送策略,
之前的实现方式是写了三个方法(SendMail,SendMSM,SendSMS)在一个类里,这一看,显然的事务脚本实现方式,根本没有去抽象与分析当前的领域逻辑,没有用OO方式去分析与设计当前问题,如果那天还要实现一个发送彩信或者去掉发送手机短信的功能,那还修改原来的类,这显示违背对扩展开放对修改关闭的原则(OCP)。这地方明显就是一个策略模式。还有,像发送者,与接收者,消息这些对象都是可以具有行为,因为领域模型是合并了数据与行为的对象模型。像Petshop的model层,就是一个贫血模型,一个实体对象没有任何行为(方法,操作)。现实中确实可以有一个对象没有行为,但是那肯定是少数。一般来说,一个对象肯定有数据与行为。
只有行为没有数据类,就叫无状态类(stateless class)
只有数据没有行为,一般用于DTO(数据传输对象[Data Transfer Object],这在远程调用与分布式调用中常见的一种设计模式)
因此,像这些发送者,接收者,消息这些类是应该具有行为的,像消息解析器,发送器,这些应该是服务类。所以基于领域模型的系统一般系统分成:表现层(Presentation,应用程序层(Application),领域层(Domain),基础结构层(Infrastructure)。应用程序层其实比较弱,有时候也可以叫做服务外观(Service Facade),有时候可以不需要这一层;领域层,就是业务逻辑层,它包括领域对象与领域业务的一些实现,还资源库(Repository)对象,资源库相当于数据访问对象(DAO),主要用于数据访问,增、删、改、查(CRUD)等;基础结构层,可以包括在很多,数据持久化(Data Persistence)、ORM、安全机制(Security)、数据验证(Data Validation)、异常处理(Exception Handle)、日志跟踪(Tracing),缓存机制(Caching)、IoC、AOP等,很多模切(Cross Cutting)组件都可以在这层提供。这种划分,是一种经典的领域驱动设计划分,不一定严格按此方式。
表模块(Table Module),我个人认为表模块应该不算是一种业务逻辑实现方式,但是根据Martin Fowler 《PoEAA》一书,把其归类到领域逻辑模式中,它是处理某一数据库(其实只能是关系型数据库)中表与视图所有行的业务逻辑的一个实例。因为表模块其实就一个数据集合(如:ado的RecordSet,ado.net的DataSet中的DataTable或类型化DataSet等之类),它可以看成是一个数据容器,因为他用一个类(如Product)表示数据库中对应表所有数据及行为,我们知道面向对象模型与关系模型存在差异,通常一个类与一个实体在概念上相对应,也就是一个类对应一个表,一个类的实例,即对象对应表中某一行记录。类与表都是抽象的,集合的概念,像关系数据库中表就一个二维(行、列)的集合,而表模块用一个类直接表示表中所有数据及行为,所以这个类可以不需要实例化,它就相当于一个表(如.net 的DataTable),这样所有业务操作都直接用表模块方式进行,从这一概念上来说,它也可以看成是业务逻辑的一种实现方式,其实大家肯定可以得出,这种方式在本质上还是采用事务脚本方式来实现业务逻辑,只是事务脚本方式,经常要求处理一个业务逻辑(如:查找指定ID的Product)就需要用SQL语句从数据库中获取数据,而这种方式先把数据库的所有行加载到表模块(如:DataTable)中,之后处理所有业务都直接与表模块有关(如:查找指定ID的行,CRUD之类的操作),这正是表模块与事务脚本的细微区别之后。
活动记录(Active Record),它本质上是一种领域模型。它表示一个对象,其包装数据库表或视图中某一行且封装数据库访问,并在这些数据上加上了部分领域逻辑。
活动记录对象中封装了数据访问(Insert、Update、Delete)与相关的领域逻辑操作,同时活动记录还有一个特点:活动记录的数据结构与数据库中的结构完全匹配,也就每个类的属性与表的列一一对应,因此活动记录可能要考虑对象与关系映射,像一对多,多对多的外键映射。如果关系太复杂,活动记录处理起来也比较困难,这些时候需要采用领域模型,因此在Martin Fowler 的《PoEAA》中把活动记录不归类为领域逻辑模式中,而归类为数据源架构模式。
以上几种业务逻辑实现方式在现在企业级应用系统中都比较常见,其实归根到底也就是两种方式:过程化(事务脚本、表模块)与面向对象(领域模型、活动记录),这以正好体现两种分析与设计问题方法,面向过程方法论与面向对象方法论。至于用那种方式比较好,这没有绝对的答案,还是那话老话:软件开发中不变是变化;没有最好的,只有最合适的。但是如果你是一个面向对象的忠实粉丝,那么在很多情况,你的第一选择肯定是领域模型,即便是业务逻辑简单的系统,你也会选择领域模型。我们通过下表简单的总结一下几个实现方式的特点:
实现方式 |
优点 |
缺点 |
备注 |
事务脚本 |
|
|
像Petshop4.0、国内很多三层架构代码生成工具生成的系统以及现今国内绝大部分企业级系统(本人估计)都采用这种方式 |
领域模型 |
1. 采面向对象方法直接对系统的问题域进行分析或建模,领域模型直观地反映现实世界,能够更好用面向对象语言模型转换,是一种纯OO模型 2. 能够解决很复杂的业务逻辑 3. 可以有效地利用面向对象的特性(抽象,封装,继承、多态等)与相关技术(如设计模式,重构等),以提高系统的可扩展性与复用性。 |
|
现在很多企业级应用项目开始采用此方式,这主要得益于ORM框架不断成熟,以及模式运动,同时一些设计原则也比较好解决相应问题。 请参看参考书目的: 11、13、16、6、2、5
|
表模块 |
1. 适合于系统业务较直观,CRUD操作比较集中 2. 如果支持平台中有比较好工具集支持(如:Ado.Net,数据控件之类),开发速度快,效率高 |
1.当业务并非CRUD集中型操作,特别是领域模型和数据库表模型差异较大时,难度非常大,因此不适合业务复杂的系统 |
在Microsoft .Net平台有很多工具集支持,有时候你甚至不需写一行代码就可以实现CRUD操作 |
活动记录 |
1. 使用OO的方式进行设计与实现,能在一定程度上避免冗余代码问题) 2. 使用活动记录后,与某个实体相关的数据和业务全部集中于活动记录业务对象中,模块内聚性好,便于维护 3. 活动记录适用于不太复杂的业务逻辑,如CRUD等。在此结构下,基于单个活动记录上的派生和测验证很有效。 |
|
你可以使用Castle Project 的Active Record框架,或者采用Ruby on Rails 框架 |
表一:几种业务逻辑实现方式的特点