技术开发 频道

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

    什么是业务逻辑?

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

    数据库服务器是一个存储层。数据库是用来对数据进行尽可能高效率、高速度的存取和更新的。其功能可以简称为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语句。这种情况下,即使要将部分业务逻辑实现到存储过程中,也应该使用存储过程进行处理。当然这也需要格的外谨慎。

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

0
相关文章