业务逻辑层的设计
你或许也和我一样:
一谈到业务逻辑层,脑海中定会呈现三个字母,那就是“BLL”,我曾经写过的第一个类就非常简单,里面几乎什么也没有,后来就发现它就是个坑,为了填补这个坑,来后一个项目我根本就没有打算要分这层给它了。
直到有一天,辗转反侧睡不着,半夜起来敲代码,我终于领悟了这个BLL的真谛。
我还是用以前的话,“软件设计本身已经够理性了,我们为什么不能用感性一点的文字?”,写者随意,看者轻松。
声明:我会讲一些术语讲的比较白,但可能缺少严谨,但是有些词语不明白可能还是需要搜索一下了。
哪些逻辑应该划分到数据访问层(DAL)
有很长一段时间,我的业务逻辑代码遍布在整个项目代码的任何角落,也同时在操作ADO.NET调用代码的中见缝插针,但我相信这是从一个程序员走向设计师必定要踩过的坑。
之后开始使用O/RM工具,是的,用了它之后我很少使用ADO.NET,因为都被它分装好了,当然也就很少有机会见缝插针了。
在O/RM以及领域驱动设计的思想引领下,我也逐渐在避免存储过程,同时这个习惯也让公司许多业务逻辑从存储过程中移除。
但是O/RM工具在执行事务的时候有两种方式,原理如下:
1、大概是将要增删改的东西全部丢到一个类似于IList<T>的集合之后,当你调用提交事务时,其实是遍历这个IList<T>的集合一次性操作数据库,他又成功地将业务逻辑与操作数据库分离,当你要它回滚的时候,它只需要将集合清空即可。专业点的名词讲,这个叫工作单元(uow)。
2、有时候你会发现,新增了一个实体(或者insert)了以后,你需要马上用到它的ID(标识)继续执行一些相关的操作,这些操作都包含在一个事务中。而且经常在事务中也会穿插那么一点点的逻辑,但通常这些逻辑都比较细粒度,或者说应该将它设计成模块化(讲白点,就是设计时考虑内聚性)。
例如,有个简单的需求是这样的。你需要新增一张订单,订单中包含几个项,所以你在新增订单的同时连同它包含的所有项都保存进数据库。
Order、OrderItem显然是对象模型,存在于BLL。
public class Order { private IList<OrderItem> _orderItems = new List<OrderItem>(); public virtual IList<OrderItem> OrderItems { get { return _orderItems; } set { _orderItems = value; } } public virtual string Name { get; set; } } public class OrderItem { public virtual string ItemName { get; set; } }
你将会发现,这个类中仅包含操作数据库的逻辑。验证、业务规则都不是它要关注的。在领域驱动设计中,会称Repository为仓储。OrderRepository就是一个特定的仓储,它不需要知道订单中具体有多少个订单项,也不关心它们的名称是否符合规范,这些都应该是BLL的责任,而仓储属于DAL。
public class OrderRepository { public void AddOrder(Order order) { //新增一个Order,并持久化 foreach (OrderItem item in order.OrderItems) { //遍历所有项,添加到这个订单。 } } }
当我平时在设计的时候,耳边老是有DAL的一个声音,“嘿,这不是我的责任,不要给我做。”
DAL开始踢皮球了。听久了,我逐渐听到了DAL的心声,我开始将许多IF交给BLL来做。
那么BLL应该做些什么?
上面已经提到了验证和业务规则。
让对象模型生动起来,让BLL充实起来,修改Order类
public class Ordere { private string _name; private IList<OrderItem> _orderItems = new List<OrderItem>(); public virtual IList<OrderItem> OrderItems { get { return _orderItems; } set { _orderItems = value; } } public virtual string Name { get { return _name; } set { if (value.Length <= 0 && value.Length > 25) { throw new IndexOutOfRangeException("订单名称必须在0-25个字符以内"); } _name = value; } } public bool IsValid { get { if (Name.Length <= 0 || Name.Length > 25) { return false; } return true; } } public string Vali() { StringBuilder builder = new StringBuilder(); if (!this.IsValid) { if (Name.Length <= 0 || Name.Length > 25) { builder.AppendLine("订单名称必须在0-25个字符以内"); } } return builder.ToString(); } }
下面来模拟一下实际调用验证
[Test] public void TestVali() { Ordere order = new Ordere(); order.Name = ""; if (!order.IsValid) { //Name长度为0显然不合法,这里果断没有通过验证。 Console.WriteLine(order.Vali()); //Vali方法让我们知道了验证没有通过的原因 //订单名称必须在0-25个字符以内 //Expected: True //But was: False } Assert.AreEqual(false, order.IsValid); }
实际使用当中,其实完全可以引入微软企业库5.0的验证模块,并且发现IsValid和Vali()都可以在该框架的帮助下完美提取成一个方面(AOP切面编程)。
实际使用当中,也会有一个服务类来服务于客户端的调用,在这里验证,也在这里调用仓储。
小结:由于时间关系,我只能暂时写到这里了,看来写技术博客真的很累,但是写的结果就是对自己和读者都有帮助。
需要注意的是,这篇随笔提到的设计思路,有点偏领域驱动设计的。
其实业务逻辑层也是非常有学问的,这篇随笔也只能点到一个方面,就是哪些逻辑不应该把责任推给数据访问层,也提到了一些处理业务规则和验证的经验。