`
高成锋
  • 浏览: 51264 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

三层架构,MVC浅谈【二】

阅读更多

1. 如果理解业务逻辑呢

狭义的认知分解过程 

我们先接触了分层架构,然后对每个层产生了初步的认识。其中,由于表示层和数据访问层的代码职责清晰明确,基本能正确认识。但是,由于我们接触的分层架构的Demo大多业务极其简单,又基本是CRUD操作集中型的业务。所以,我们脑子中就产生了疑问:这个所谓的业务逻辑层是干什么的?怎么就简单封装了一下数据访问层的操作?这有存在的必要吗?由于有了这种先入为主的误导,使得很多朋友脑中将业务逻辑业务逻辑层两个概念混淆了,始终想不明白这东西到底是什么,做什么用的。再加上很多朋友所看的、所做的系统都是CRUD操作集中型的,就形成了业务逻辑貌似就是对数据访问操作的简单封装这一片面概念。

到底这一概念有没有错呢?其实没错,因为在简单的、CRUD操作集中型软件中,业务逻辑基本就是对数据访问简单的封装。但是,无错不代表全面,这是一种狭义的业务逻辑理解,而且是狭义中的狭义。为什么这么说呢?因为我们不但是在业务逻辑层这么一个狭义范围内去理解业务逻辑,而且还是CRUD集中型操作这种非常瘦的业务逻辑层范围内去理解,所以,可谓是在狭义的基础上的狭义。

当我们把这么一个狭义中的狭义业务逻辑业务逻辑等同起来时,误会、迷茫、困惑、不屑就出现了。这就如同,给你一只温顺的哈巴狗,还是病怏怏的、无精打采的小哈巴狗,而你把这只病怏怏的小哈巴狗的概念等同起来了。那么你一定就会为有人养狗看家和警察养狗当警犬抓坏人而困惑:这东西这么弱小,我一脚就踩死了,怎么弄用来看家和抓坏人呢?进而可能会产生狗狗无用论狗狗废品等观念。当然,在现实中,很少有人只见过小哈巴狗而没见过狼狗等其它狗类,所以,故事中的误会对 一般是不存在的。但在现实中,确实有很多人只见过业务逻辑中的小哈巴狗,却没有见过业务逻辑中的狼狗藏獒,所以,这种误会在对业务逻辑的理解上广泛存在。 

广义的认知分解过程

(注意!凡是不特别说明,下文中所有数据一词都指需要持久化的数据,而不包括内存中的临时数据。请各位留心。)

广义的认知分解应该是这样的:软件产品都是在某个领域内实现某些特定业务,所以,软件产品天生应该分解为界面交互部分和业务逻辑部分,其中业务逻辑部分是软件产品的核心,它客观存在于软件产品内部,但是无法对使用者产生直观刺激,因此业务逻辑不能与使用者直接交互。而界面交互部分是业务逻辑与使用者进行交流的接口,使用者通过界面交互部分,与业务进行交流,从而使得软件产品发挥其作用。 

而在具体实现系统时,界面交互部分演化成表示层,业务逻辑部分演化成业务逻辑层。所以,可以认为,数据访问层不是软件产品自然演化的直接产物,之所以出现数据访问层,是因为某些产品的业务属于数据操作集中型业务,为了实现隔离、复用等目的,架构师从业务逻辑中分离出了频繁使用的数据访问业务,形成了单独的数据访问层。从广义来说,可以认为数据访问隶属于业务逻辑,因为,数据访问操作实际上也是业务逻辑的一部分。

总结一下几个要点:(这几个要中的业务逻辑均指广义业务逻辑)

1)软件产品自然的可分为界面交互部分和业务逻辑部分。 

2)从空间结构上看,业务逻辑和数据访问不是并列关系,而是隶属关系——数据访问隶属于业务逻辑。虽然在具体系统实现层面,数据访问层和业务逻辑层是并列存在,但从概念本质层面上分析,两者是隶属关系。 

3)从时间结构上看,应该是先有业务逻辑的概念,才有数据访问的概念。业务逻辑衍生自软件本身,数据访问衍生自业务逻辑。 

4)因为业务逻辑是软件产品自然的一部分,所以拥有业务逻辑是软件产品的必要条件(读者可以试着举出一个不包含业务逻辑的软件)。但是一个软件可以没有数据访问,如计算器不带存档的小游戏等。 

利用以上论述要点和认知分解,朋友们可以试试在脑中重新构筑狭义和广义业务逻辑的概念。看能不能把我们丢掉的业务逻辑概念找回来。关于业务逻辑更多的细节,将在下文中讨论。

这里我仅给出两个定义。

广义上的义务逻辑——软件本身固有的一种品性,自然存在于软件产品内部,是软件具有的在某个业务领域内的逻辑,是软件的核心和灵魂。软件产品除界面和交互外的一切都可看作是广义业务逻辑。

狭义上的业务逻辑等同于分层架构中业务逻辑层的职责,是软件中处理与业务相关任务的部分,一般狭义上的业务逻辑不包含数据持久化,而只关注领域内的相关业务。

对于以上两种定义,希望朋友们不要割裂开来看,而 要辩证统一的去看,这样,才能构建一个完整而辩证统一的业务逻辑概念。在下文中,将不再明确区分狭义和广义,业务逻辑一词将代表两者的辩证统一体。

2.2、业务逻辑的组成结构 

业务逻辑作为一个高层次概念,其内在结构也是非常丰富的,下面我们深入其里,去探寻一下业务逻辑都是由哪些更底层的部分构成的。

2.2.1、领域实体(Domain Entity) 

通俗的说,领域实体就是这个领域内有哪些东西。例如,银行业领域内有账户、支票、前台营业员等实体;B2C电子商务领域有商品、订单、交易等实体;魔兽世界游戏的领域内有角色、种族、道具、魔法等实体;高等代数领域有矩阵、行列式等实体。

领域实体是某个领域内各种对象的抽象,可以用名词表示(可以是具体名词或抽象名词,甚至动名词,只要其具有名词性),构成了整个业务逻辑的骨骼和静态模型。一般每个领域实体有自己的一些属性和行为。 顺便说一句,领域实体的存在时OOA&D的基础。 

在具体的软件系统中,领域实体往往会根据架构的不同有不同的映射存在形式。

其中一种叫做Business ObjectBO),即业务对象,某些文献称其为充血实体类,这种对象完整抽象了领域内的某个实体,封装了此实体相关属性和行为。在面向对象的设计和架构中,这种实体类很常见。

另一种叫做Data Transfer ObjectDTO),某些文献称其为贫血实体类,其特点是仅有属性,不存在行为。这种实体类主要负责整体性传递数据。另外,与BO不同的是,DTO可以不抽象领域实体的全部属性,而只根据需要抽象一部分。例如,某个“User”实体存在很多属性,但如果某个方法仅需要其联系方式,可以设计一个DTO,仅有idemailaddressphone等就够了。在面向过程的设计和架构中,这种实体设计比较常见。

2.2.2、业务规则(Business Rules) 

业务规则就是某个领域内运作的规则,构成了整个业务逻辑的灵魂和动态模型。业务规则作用于领域实体,领域实体遵从业务规则进行运作。 

如:在银行领域内,转账时从A账户扣除相应款项,在B账户添加相应款项,并从A账户扣除相应手续费,并通过某些途径通知AB账户的户主就是一条规则。需要注意的是,业务规则比较抽象,并不是需求,需求需要具体且无二义性,而业务规则只是抽象的一种描述,例如,通知户主的途径是什么?电子邮件?电话?短信?并没有具体描述,但在规则中有通知这一项,因此不能将业务规则等同于需求。

2.2.3、完整性约束(Validation) 

领域实体和业务规则构建了业务逻辑的主体,但在这主体之上,还存在着一个限制,这就是完整性约束。 

完整性约束是对业务领域中的数据、规则的强制性规定与约束。这种约束是系统正常运转的保证。

账户密码不能为空身份证号必须符合具体格式规定转账流程必须具有原子性,A账户扣钱、B账户存钱、A账户扣除手续费、通知户主四项操作必须要么都做,要么都不做,都是完整性约束。

2.2.4、业务流程及工作流(Business Processes and Workflows) 

有了上述三项,业务逻辑还不能正常工作,因为还没有启动器过程托管器。设想我们有了各种实体类,它们有各自的属性和行为,也有定义好的业务规则和完整性约束。现在实体类仅仅具有实现业务规则的能力,但它们如何启动并交互协调完成业务规则呢?因此我们需要有东西去触发和协调实体。

业务流程或工作流是启动及托管协调领域实体完成既定规则的过程。例如,在线订购是一个业务流程,它包括用户登录-选择商品-结算-下订单-付款-确认收货这一系列流程。各个实体如会员、订单、商品等已经包含了完成在线订购必要的行为,但仍需一个流程,才能真正完成业务。

具体到程序中,业务流程也许通过一个方法来实现,这个方法负责启动并协调各个实体类,完成一个流程。

2.3.1、数据的格式化 

关于数据的格式化应该放在业务层进行还是表示层进行一直存在争议。我个人的意见是这样的:

业务层送给表示层的数据应该具备以下要求。1)返回的数据应该完成了所有必要的业务处理和业务计算。 例如,若返回订单信息让表示层展示,会有个必要的数据——订单总额。这个数据需要首先用各个订单项的单价乘以数量,然后加和。那么,这个数据应该在业务层完成计算直接返回,总之不应让表示层进行任何业务处理和计算操作。2)一次性返回所有需要的数据,避免表示层再一个Action里调用多次业务。 打个比方,例如订单中有个客户姓名,这个数据不保存在订单表中,而是通过外键关联的,那么,业务层应该将客户姓名一并取出返回给表示层。总之,避免表示层在一个Action里多次调用业务层。3)不携带任何格式信息,仅仅是结构良好的纯净数据,如DTO形式。 因为,数据如何展示,是表示层的职责,如何在业务层中返回了过多格式信息,就会造成表示层的修改困难。例如,我曾听说过所里承接的一个实际项目,开始是使用B/S,当时他们的业务层返回的数据全都附带了html代码。后来,客户嫌B/S响应不够迅速(可能是客户公司的网络条件不好),要求改成C/S,当时全傻眼了,貌似几乎修改了整个业务层。那个项目相当庞大,7个子系统,投入200人开发了1年多,想想修改的难度吧。

2.3.2、数据合法性及完整性验证 

一般做系统,都避免不了数据验证。上文曾经提到,完整性约束是业务逻辑的一部分。如此看来,数据验证一般应该放在业务层。但是,实际情况并不尽然。个人认为数据验证的方式,目前没有统一标准,可以根据需要放在表示层或业务层。但是,我个人不提倡在表示层的服务端放置过多完整性验证。因为,表示层的职责应该仅仅是接收数据并传递给业务层,不应对数据是否合法负责。过多的数据验证,不但令表示层代码臃肿,而且使得表示层职责变得不明确。

可以在表示层的服务端放置一些简单的验证,如空值验证,两次输入密码是否一致等,但业务关系紧密的验证,最好放在业务层。甚至有些验证只能在业务层验证,如当前用户名不能与已有用户名重复,这种验证需要访问持久化数据,需要由业务层完成。

这里之所以强调表示层的服务端,是因为一般在B/S系统中,都会在JavaScript里加入一些基本的数据验证,如空值检查,格式正则匹配等。这主要是为了减轻服务器负担,将大多数显然包含不合法数据的请求拒绝掉,而不发给服务端验证。当然,因为可能会出现JS被屏蔽或黑客恶意攻击行为,所以,所有验证不论JS中是否验证过,服务端(可能是表示层的服务端部分或业务层)一定要再进行验证。

2.3.3CRUD 

CRUD,即常说的增删改查操作。关于CRUD是否是业务层的职责,一直也是争议不断。因为目前并没有权威的定义,所以这里我斗胆说一下我对这个问题的看法。还请大家批判性阅读。

一说到增删改查,大家一定会觉得这理所当然是数据访问层的职责。我认为这个理解是对的,但是只对了一半!之所以这么说,是因为增删改查有两个层次含义。

第一个层次,是数据访问层次上的。在这个层次上,增删改查只是单纯的数据库操作,增删改查可以理解为插入一条记录,删除一条记录,更新一条记录的信息,获取一条或多条记录四个操作,其意义和着眼点完全是数据访问层面上的,不带有任何业务成分和业务知觉。这个层面上的CRUD应该属于数据访问层的职责。

第二个层次,是业务逻辑层次上的。在这个层次上,增删改查是业务领域内实体的变化以及一系列相关反应,增删改查可以理解为领域内新增一个业务实体,领域内去掉一个业务实体,领域内一个业务实体更新了信息,得到领域内一个或多个业务实体的信息

两者最大的不同,是业务层面上的增删改查往往不是单纯的增加减少,还包括实体变化后相关的业务流程。下面举个例子:

添加一个新的订单”——这是一条典型的操作。在数据访问层面上,它的意义是在表示订单的数据表里增加一条记录;而在业务逻辑层面上,它的意义除了领域内多了一个订单实体外,还可能包括根据业务规则判断是否是重复下单,根据金额对下订单客户的等级做相应提升、发送Email和短信通知客户等。可以看到,业务层面上的可能不仅是简单封装一个简单的插入记录,可能还要去做其他数据访问——提升用户等级,以及做一些非CRUD的业务操作 ——发送短信通知。

在许多稍微复杂的系统中,业务往往不仅仅是封装了一条数据访问操作,而是还有很多如计算等业务处理,一个业务操作期间可能要多次使用数据访问操作。退一步说,即使某个业务仅仅封装了一条数据访问操作,其意义和层面也是不同的,在数据访问层面,仅仅是多了一条记录,而业务逻辑层面,是领域内多了一个业务实体。也许其本质上都是往数据库插入一条记录,但人类的抽象思维可以将之在不同层面上区分,这也是人类思维层面的一种抽象能力的表现。例如,我们知道太阳升起不过是地球自转使得从背阴面转到了向阳面,但当人们看日出时,很少有人会说看!我们从背阴面转到向阳面了!,我们会说看!日出!,这就是同一事物的不同层次表现。

2.3.4、存储过程 

也许是性能上的诱惑,许多人喜欢在数据库系统中写很复杂的存储过程。这样,许多业务操作就被写到存储过程中去了。我个人建议,除非对性能要求极高,否则最好还是不要用存储过程实现业务。例如,在一般的系统中,某个业务操作可能需要1秒,而是用了存储过程只用0.1秒,看上去存储过程将效率提高了10倍。但对大多数用户来说,1秒和0.1秒的差别并不大,但是这样做的话,业务会变得十分不容易维护。所以,我个人觉得,除非十分必要,还是不要用存储过程实现业务。 

3、业务逻辑的架构模式及实现 

Martin Fowler在《Patterns of Enterprise Application Architecture》一书中,总结了四种企业应用中业务逻辑的组织方式 :Transcation ScriptDomain ModelTable ModuleService Layer,另外,本书的第十章“Data Source Architecture Patterns”中包含一种模式——Active Record。结合软件体系结构的近期发展及个人的理解,我更倾向将Active Record归入业务逻辑的组织模式,而Service Layer应该不算做业务逻辑特有的模式,所以,在本文中,将介绍四种模式:Transcation ScriptTable ModuleActive RecordDomain Model

3.1Transction Script 

3.1.1、概述 

Transction Script(以下简称TS)是一种面向过程的业务逻辑组织方式。这里首先要强调一点,这里的Transction一词与数据库系统中表示事务的 Transction没有任何联系。TS是将领域中的业务分解为一个个业务过程,每个过程实现一项业务功能,具体到程序中,一个业务过程往往映射到一个方法。TS是完全面向过程的业务组织模式,适合应用于业务逻辑较简单的场合。 

应该说,我们见到的绝大多数系统都是以TS组织业务的。例如PetShopOxite等经典示例。有时为了方便维护,开发者会将同一领域实体相关的业务方法集中到一个类中,这里虽然用到了领域实体和类的概念,但和面向对象没有任何关系,完全是面向过程的。

当使用TS时,可以不需要数据访问层,而是将数据操作执行代码(如执行SQL或存储过程的代码)直接嵌入在业务方法中,有时为了复用性和维护性可以编写一个helper类封装数据库的操作。当然这并不是说TS不能配合数据访问层使用,但由于应用TS的场合一般业务非常简单,如果配合Repository或 ORM使用,业务逻辑层往往就会变得非常,看起来仅仅是对数据访问层的封装。一般在需要支持多数据库的场合,要配合Repository和 Abstract Factory使用。

<!--EndFragment-->
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics