技术开发 频道

业务逻辑,架构的第几层?

【IT168 专稿】

    近年来,我们从桌面逐步转向客户服务器、3级、n级,直至面向服务。虽然在此过程中许多东西发生了变化,但是也有许多习惯保留下来。这是因为人们通常对改变有一种抵触心理,而程序上的问题也经常导致这种结果。本文将对这些错误的做法进行讨论并给出可行的解决方案。

    本文从设计与架构的角度来构建一个n级系统,其重点不是代码。实现n级系统有许多方式,这只是其中的一种。希望本文能为那些正在构建系统的人提供帮助。

    问题的提出

    当你问任何一名开发人员:业务逻辑应该放在哪里,他都会回答:“当然是业务层。”

    当你再问他:你们公司的业务逻辑在哪里呀,他一定还会回答:“当然是业务层。”

    业务逻辑当然是在业务层。不仅是业务逻辑的部分,而是全部的业务逻辑都应该在业务层。阅读本文后,许多开发人员会认识到,他们曾经以为正确的这一观点,实际上却是错误的。

    术语

    有些术语的定义比较复杂,为了叙述方便,在本文中我先定义如下:

    级:当我使用级(Tier)这个词的时候,我指的是物理服务器,或者用于提高处理能力的一组物理服务器这样物理意义上的级。

    层:层(Layer)指的是系统中的过程或部署单元中的一个区域。同一级上可能存在多个层,但是通过远程调用技术可以很容易地将层从一个级移动到另一个级。

    问题的发展

    桌面

    在桌面应用中,业务逻辑与其它层共同存在于同一级中。由于没有区别层的需要,所以这些层通常都混合在一起,没有明显的界限。

    客户服务器

    在客户服务器系统中有两个级,因此至少需要实现两个层。在早期,通常把服务器视为远程数据库,也就是区分为应用(客户端)与存储(服务器)。通常所有的业务逻辑都保存在客户端,与用户界面等层混合在一起。

    但是,不久人们便发现这种方式会消耗太多的网络带宽,并且为了减少客户需要经常进行重新部署所带来的麻烦要把大部分业务逻辑从客户端剥离。由于这里只有两个层,因此业务逻辑的唯一去处就是服务器。从架构上来说,在客户服务器系统中服务器是一个理想的场所,但是使用数据库作为平台却不是个好主意。数据库是用于存取的,它们的扩展性能也是以此为基础。数据库里存储的流程语言是用来对SQL所无法完成的数据转换进行补充的。它们需要非常快的执行速度,并不是用于复杂的业务逻辑的。

    虽然如此,这也是没有办法的办法。因此,出于实用主义,业务逻辑就像是硬被塞进了小鞋里。在一个两级的环境中,这虽然不完美,但也可算是一种改进。

    3级

    随着客户服务器系统的问题越来越突出,3级的设计模式开始流行起来。当时最急切需要解决的问题是连接数的问题。虽然现在的数据库可以处理数以千计的同时连接,但在20世纪90年代,500左右的连接就足够让大多数据库不堪重负了。

    于是连接池技术开始受到欢迎,但是要在一个有许多不同的客户端的系统中实现连接池,就需要在客户端与服务器之间插入一个第三级。这个中间级就是后来的"中间级"。虽然在大部分情况下,当时只是用中间级处理连接池,但是由于各种开发语言(C++、VB等)的作用业务逻辑涌入中间级,很快被证实中间级是存放业务逻辑的非常好的位置。

    中间级还能为低带宽的客户提供更好的连接,因为数据库的直接连接通常需要一个低延迟的网络环境。

    什么是业务逻辑?

    在更深入以前,我们先来看一下业务逻辑的定义。我发现各人对业务逻辑都有不同的认识,有些人甚至对此还没有认真地思考过。

    数据库服务器是一个存储层。数据库是用来对数据进行尽可能高效率、高速度的存取和更新的。其功能可以简称为CRUD(创建、提取、更新、删除)。但是我们今天的重点不是这个。

    数据库是用来实现高效、高速的CRUD操作的。而不是用于调整电话号码的格式等繁琐的操作的。但是我却在存储程序中看到了所有这些琐碎的东西。

    业务逻辑例子—删除客户

    现在,我们来看一个简单到甚至不能称为业务逻辑的例子——删除客户。几乎在所有的系统中,我发现这个过程都是用存储过程实现的。但是要执行这个操作,还需要考虑许多业务逻辑决定。这个客户可以删除吗?删除前后还需要什么操作?需要什么检查程序?等等。

    数据库不应该用来存储客户相关的东西,它只应该存储客户的构成元素。也不应该让数据库决定使用哪个表格存储客户对象。数据库的工作是存储表中代表客户的一行。除了那些用于提高数据提取性能的元素之外,不能用数据库存储应在业务层进行处理的那些客户数据。

    存储过程还应该尽可能地只对一张表进行操作。在这种情况下存储过程是作为一种视图存在的。虽然视图与存储过程可以用于基本的价值列表,但是只能以提高业务层的数据提取和数据更新的效率为基础。

    即使是那些为自己的新技术而自豪的企业也难免会在存储过程中发现许多像删除客户、添加客户等对业务对象进行操作的行为。

    比如我经常看到执行如下功能的存储过程:

    sp_DeleteCustomer(x)
    Select row in customer table, is Locked field
    If true then throw error
    Sum total of customer billing table
    If balance > 0 then throw error
    Delete rows in customer billing table (A detail table)
    if Customer table Created field older than one year then
        Insert row in survey table
    Delete row in customer table

    有时候也有部分业务逻辑被移到了业务层。

Business Layer (C#, etc)
Select row in customer table, is Locked field
If true then throw error.
Sum total of customer billing table
If balance > 0 then throw error.
if Customer table Created field older than one year then
    Insert row in survey table
Call sp_DeleteCustomer
sp_DeleteCustomer(x)
Delete rows in customer billing table (A detail table)
Delete row in customer table

    但是这样虽然部分业务逻辑被转移了,却不是全部。因为"影响到哪一张表"也是业务逻辑。数据库不需要了解在业务层次上哪些表构成一个客户。这种活动在业务层能得到更好的实现。我们要将所有业务逻辑转移到业务层。

    我们可以这样:

Business Layer (C#, etc)
Select row in customer table, is Locked field
If true then throw error.
Sum total of customer billing table
If balance > 0 then throw error.
if Customer table Created field older than one year then
    Insert row in survey table
Call sp_DeleteCustomer
Delete rows in customer billing table (A detail table)
Delete row in customer table

    如果只删除一个表中的一行可以使用一个存储过程。但是现在的数据库都使用了查询计划缓存,这种做法就没有提升性能的意义了。此外,由于这种系统所发出的SQL只对一个单独的表进行操作,因此几乎没有对操作进行优化的需求。实际上与执行简单的SQL相比,许多数据库在存储过程的加载量上的问题更为严重。

    把像表格修改这种操作也转移到业务层上,可以获得以下优势:

    · 可以方便地对系统的数据库进行移植。

    · 可以方便地进行修改。

    · 可以方便地进行调试。

    · 不会让其它业务逻辑"溜"进存储过程中。

    由于这需要对数据库进行三次连续调用,所以你的业务级要在一条单独的高速线上和数据库进行连接。发送300个字节与发送100个字节不会对有形资产产生影响。大部分数据库支持SQL的批处理,因此这三条语句可以在一个批处理中发送到数据库,从而减少网络调用。为了不把SQL嵌入到代码中,还要建立一个用于执行SQL的数据访问层。

    部分DBA(甚至开发人员)可能无法接受这种程序的集成而坚持使用批处理。你需要依据数据库和优先级而做出决定。因为几乎所有的数据库都会对基于查询计划缓存的SQL进行优化,所以大部分情况下性能提升的空间很小。但是即使如此,由于一定的技术原因,我们不能将逻辑加入到存储过程中。如果你坚持把这种批处理更新放在存储过程中,那么你就应该小心别让其它的业务逻辑溜进存储过程。

    业务逻辑例子—格式

    我们再来看另一个在开发人员当中很有分歧的问题——non-simplistic格式。我将阐述我把它看作是一种业务逻辑,而非用户接口或者存储的原因。这里以电话号码为例。

    各个国家都有自己的电话号码显示格式。在许多国家里甚至有多种方式。比如下面的几种:

赛普路斯:
+357 (25) 66 00 34
+357 (25) 660 034
+357 25 660 034
+357 2566 0034
德国:
+49 211 123456
+49 211 1234-0
北美(美、加等):
+1 (423) 235-2423
+1-423-235-2423
俄国:
+7 (812) 438-46-02
+7 (812) 438-4602

    德国甚至还有国家标准。

    我们假设系统是国际性的,所以我们必须存储并显示国家代码。对于每个国家我们采用一种显示格式。

    基于此有几下考虑:

    1.输入可能有多种格式。

    2.各国有自己的显示方式。

    3.某些国家的格式可能比较复杂。

    4.前几位数字通常不固定。比如俄国。所以要根据区域决定号码的长度。

    5.由于新的移动法规、移动公司等因素导出不穷,号码也频繁发生变化。比如赛普路斯在近几年里移动用户几乎翻倍。我们可以认为变化会定期地产生。

    通常在输入号码的时候不会输入非数字的部分,比如:

电话:35725660034

    有时候国家代码分储存在一个不同的域中,比如:

国家代码:357
本地电话:25660034

    这看起来很简单,但是却会带来另一个业务逻辑上的问题。并不是所有国家代码的位数都相同——通常有1到3位。

    对输入进行解析的工作一般是在客户端上进行的。而客户端需要大量的数据来决定国家代码的长度。

    有时候这个格式操作是由存储过程完成的。可是存储过程通常不适用于这种逻辑,因此经常导致出错。

    这样号码可能会被重复存储。先是一次用于查询的存储,然后是用于显示的。此外还有随之产生的其它问题。

    在一些极端的情况下,输入什么格式号码就会以什么格式进行存储,这使得号码的定位、查询等操作变得极不方便。

    虽然这是一种格式问题,但是它不是用户界面,并且虽然最终的处理都集中在数据库,它仍然是一个业务逻辑。在业务层上进行格式操作可以消除重复数据,并可以使用开发语言。

    在存储过程中执行某些批处理更新可以获得更快的速度。其中大部分可以直接使用SQL,但是一小部分更新需要循环操作,这样如果实现在业务层中就会产生数千的SQL语句。这种情况下,即使要将部分业务逻辑实现到存储过程中,也应该使用存储过程进行处理。当然这也需要格的外谨慎。

    我会在后面再讨论这个问题。

    现在的系统

    客户服务器

    在客户服务器系统中业务逻辑通常会被分成客户端与服务器两部分。

    当然实际比例会根据应用和企业有所不同。为了使业务逻辑集中,许多业务逻辑是在存储过程和视图中实现的。然而许多业务规则由于与用户界面有关,因此无法简单地通过SQL或存储过程实现,或者说在客户端上执行的速度更快。由于这些原因,业务规则被分到了客户与服务器两端。

    N级

    N级系统的情况更为糟糕(更多具体原因我会在下面讲述)。它不但没有让系统更稳固,反而使业务逻辑变得更为分散。

    当然由于业务逻辑的分布不同,各个系统也不相同,但是有一点是一样的。业务逻辑分布在三层上,而不是两层。下面是几个实例。

    案例1

    典型的N级系统通常是这样的:

    这种情况下,业务层没有包含任何业务规则。这不是一个真正的业务层,它只是一个数据库结果的XML格式化程序和匹配程序。虽然这也有一定的优势,但它毕竟不是一个真正的逻辑层。把它看作一个没有业务层的物理层更好一些。

    案例2

    另一种情况如下:

    通常会有一部分应用规则被转移到了业务层,但是原来在数据库中的那部分仍然留在了那里。

    在这种设计中,当对业务层进行重用的时候,必须对应用中的业务逻辑进行复制。这违背了实现业务层的最基本原则。

    这种应用可能会违背或者忽略业务规则。真正的业务层决不会这样。

    集中

    取而代之的是业务层应该包含所有的业务规则。

 

    这种设计有以下优势:

    · 所有业务逻辑都在一个位置,可以方便地进行验证、修改、调试。

    · 可以使用真正的开发语言实现业务规则。这种语言比SQL和存储过程更灵活且更适合于业务规则。

    · 数据库变成一个存储层,可以有效地进行存取数据操作。

    这种情况才是我们的目标。但是由于某些原因(特别是验证)我们需要在客户端作一些复本。这些规则应该获得业务层的支持。还有,在某些系统中,为了实现一些较极端的性能优势可能会把一些重要的规则放在数据库中。因此,一个更为现实的方式应该是下面的样子。请注意业务层仍然包含100%的业务规则,其它的只是为了实现特殊目的的复本。

    转向中间层

    过渡

    在转向中间层的时候总会出现"把这个放到存储过程中吧"这种情况。并且一而再再而三地出现。然后你会发现一切都回到了从前。

    存储过程应该用于处理SQL并返回可以优化存储过程的结果集。但是存储过程在返回数据的时候不能做多余的操作。对于数据更新来说它应该做的只有这点。

    虽然在某些例外的情况,但是这些情况实际上很少,并且应该看作是例外,而非规则。应该对各个例外进行审查。

    成本更低

    购买更多的硬件却节省成本,这听起来可能比较诡异。但是当你添加额外的中间级的时候,除了操作系统几乎不再需要其它的软件。

    有两个原因使得提高数据库服务器性能的成本很高:

    1.数据库服务器硬件通常比中间级更重要,因此也更昂贵。

    2.数据库通常以CPU为单位进行注册,因此添加CPU意味着更多的注册费用。每个CPU的注册费用从5000美元到40000美元不等。

    把逻辑移到中间级,你可以减少数据库服务器的负载,并避免数据库的提前换代。

    更简单

    除了成本因素,对中间级进行更新通常比对数据库进行更新更为简单。

    数据库有其固有的扩展限制。这可能需要一些其它技术,而这些技术又都非常复杂,还需要在硬件等方面做出大量投资并且会对当前系统产生显著的影响。

    但是中间级就很简单。只要安装好负载分配器,剩下的就只是添加一个能提高性能的新服务器了。

    拓扑结构

    我们用下面的图来分析刚刚讨论的内容。各色条的褪色趋势显示了其对上面对应级的重要性。每单位的成本(cost per unit)是从客户端向中间级、数据库逐渐提高的。单位指的是处理器或服务器。

    如果使用相对值表就更容易对比:

    我没有在图上标出数据,因为这取决于许多因素。各种计算手段也不相同。我只是在这里描述它们的基本关系。很显然中间级具有扩展性,并且比数据库更为简单、而且成本更低。

    发展中间层

    如果有大量的业务逻辑在数据库中实现,那么你就需要一个更大的数据库。比如这个样子:

    通过把逻辑移到中间级,你可以很大程度地减少数据库服务器的负载。虽然具体数据可能根据情况不所不同,但它能帮助你更好地了解其原理。下图的实现虽然需要更多的硬件,但是整体成本却相对较低,而且更易于部署。发展中间层是一种更简单、成本更低的做法。

    瓶颈

    我们来看前面图中的一项。

    这个系统性遇上的唯一的瓶颈是什么?出现在哪一部分?很显然是数据库。所有的漏斗出口都在数据库。

    这样通过把处理过程移到中间层,我们就可以更好地摆脱数据库的限制。

    实现该设计的障碍

    在转向这种统一的设计的过程中,除了编码方式的改变,还有几种困难。

    习惯

    俗话说:"江山易改,本性难移。"在团队中也是如此。在一个团队里你不仅要说服你自己,还要说服你的队员。

    流程

    许多公司的安全策略规定必须在数据库中对安全进行控制,而使用存储过程无法提供足够的控制。让公司的安全策略转向中间级是一件相当困难的事。

    虽然随着.NET等的发展展人们越来越关注中间级的安全性,但是许多企业仍然把重点放在数据库上。

    数据库管理员

    这可能是一个敏感话题。但是不管如何,我们必须提到这一点。不管是你是一名DBA或是一名开发人员,请别把我说的话当作真理应用到所有的DBA身上。如果你是一个没有这种困惑的DBA,那么恭喜你!你是数据库的领导者而不是守旧派。

    通常DBA不愿意做出任何实质性的改变,因为这会打断他们现有的系统。许多企业都有一名DBA和许多DA(数据库助理,Database Assistants)。DBA就是这个领域的头儿。只是管理部门才能影响到DBA,而管理部门在DB方面上的失误通常会归咎于DBA。

    大部分DBA对n级系统了解甚少,甚至根本不关心。对于他们来说,任何级都只是一个客户端,所有的东西都只是一种客户服务器模型。他们只关心他们的服务器,只要不会造成什么严重后果,他们拒绝向开发人员做出任何妥协。

    DBA不像开发人员流动性强,他们通常都在同一公司里呆了10年甚至20年。数据库就是他们最重要的东西,他们不想做出任何改变。他们有自己的王国,并拒绝交出任何控制权。让这样的DBA放手一部分安全和实现通常需要极大的努力以及管理部门的参与。

    也有部分DBA没有这么固执,他们会接受任何合理的东西。但是在许多企业里,特别是大型企业里,DBA通常都坐在企业指挥系统的最上层。

    工具

    当前的大部分工具都无助于业务逻辑的实现。它们只是用于提高扩展性、连接池等并且无法满足业务逻辑实现的需求。

    实现设计的解决方案

    架构监控

    让系统架构设计人员经常性地对业务逻辑进行审查并标记不妥之处是一种很有效的作法。发现得越早,执行起来就越容易,也更节省修复的成本。如果你不想指定一个专门的架构负责人,那么开发人员可以互相进行监控。如果有人发现不妥,他就会通知团队。

    培养DA

    DA的培养非常重要。他们长期进行着业务逻辑的实现,因此可能很难区分业务逻辑与存储。DA通常只是根据DBA的命令做自己的任务。

    DA的职位仍然存在。他们不仅要继续做自己的工作,还要监控来自中间级的SQL和数据库性能。他们还要继续做数据库设计。

    管理层

    通常管理部门对此会比较抵触,但是相对来说这还是一个比较简单的障碍。虽然他们不关心你的工作是否会变得简单,但是他们关心成本。

    与此相关的主要障碍应该是来自DBA。所以要说服管理层,让他们处理DBA的问题。

    其他

    本文提到的方式我已经应用近十年。当然在此过程中我也做了许多修改。

    总结

    改变企业的方向是一项冒险的决定。开发人员应该采取低姿态,让其他人做主要工作。许多开发人员会彻底改变他们习以为常的习惯。我希望本文能帮你改善当前的过程,或者至少能帮你做出更有价值的决定。

    本文所描述的方法最适应于新系统、或者重建的完整系统、或者重建的部分系统。对于既有系统,最好还是先保持现状,直到产生其它需要对其进行重新设计的更为重要的原因。
 

0
相关文章