业务逻辑层的设计(一)——逻辑是谁的职责

你或许也和我一样:

一谈到业务逻辑层,脑海中定会呈现三个字母,那就是“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切面编程)。

实际使用当中,也会有一个服务类来服务于客户端的调用,在这里验证,也在这里调用仓储。


小结:由于时间关系,我只能暂时写到这里了,看来写技术博客真的很累,但是写的结果就是对自己和读者都有帮助。

需要注意的是,这篇随笔提到的设计思路,有点偏领域驱动设计的。

其实业务逻辑层也是非常有学问的,这篇随笔也只能点到一个方面,就是哪些逻辑不应该把责任推给数据访问层,也提到了一些处理业务规则和验证的经验。

 

 

你可能感兴趣的:(设计)