专栏名称: 逸言
文学与软件,诗意地想念。
目录
相关文章推荐
程序员的那些事  ·  阿里云机房火灾,字节等服务瘫痪,网友:吃瓜吃 ... ·  4 天前  
51CTO官微  ·  全面评估:OpenAI模型的软件开发基准测试 ·  3 天前  
码农翻身  ·  月薪45-60K!程序员从业人员新出路,工资 ... ·  4 天前  
程序员的那些事  ·  趣图:你会嫁给我么? ·  1 周前  
51好读  ›  专栏  ›  逸言

第 27章 业务逻辑层

逸言  · 公众号  · 程序员  · 2024-09-07 12:19

正文

业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也就是说,它与系统所应对的领域(Domain)逻辑有关。很多时候,我们也将业务逻辑层称为领域层。例如,Martin Fowler在《企业应用架构模式》一书中,将整个架构分为三个主要的层:表现层、领域层和数据源层。作为领域驱动设计的先驱Eric Evans,对业务逻辑层做了更细致的划分,分别包括了应用层与领域层,通过分层进一步将领域逻辑与领域逻辑的解决方案分离。

业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表现层中间,起到了数据交换中承上启下的作用。由于层是一种弱耦合结构,层与层之间的依赖是向下的,底层对于上层而言是“无知”的,改变上层的设计对于调用的底层而言没有任何影响。如果在分层设计时,遵循了面向接口设计的思想,那么这种向下的依赖应该是一种弱依赖关系。在不改变接口定义的前提下,理想的分层式架构,应该是一个支持可抽取、可替换的“抽屉”式架构。正因为如此,业务逻辑层的设计对于一个支持可扩展的架构尤为关键,因为它扮演了两个不同的角色。对于数据访问层而言,它是调用者;对于表现层而言,它是被调用者。依赖与被依赖的关系都纠结在业务逻辑层上,如何实现依赖关系的解耦,是除了实现业务逻辑之外留给设计师的任务。

27.1  与领域专家合作

设计业务逻辑层最大的障碍不在于技术,而在于对领域业务的分析与理解。很难想象一个不熟悉该领域业务规则和流程的架构设计师能够设计出合乎客户需求的系统架构。几乎可以下定结论的是,业务逻辑层的设计过程必须有领域专家的参与。在我曾经参与开发的项目中,所涉及的领域就涵盖了电力、半导体、汽车等诸多行业,如果缺乏这些领域的专家,软件架构的设计尤其是业务逻辑层的设计就无从谈起。这个结论唯一的例外是,架构师同时又是该领域的专家。然而,正所谓“千军易得,一将难求”,我们很难寻觅到这样卓越出众的人才。

领域专家在团队中扮演的角色通常被称为Business Consulter(业务咨询师),负责提供与领域业务有关的咨询,与架构师一起参与架构与数据库的设计,撰写需求文档和设计用例(或者用户故事User Story)。如果在测试阶段,还应该包括撰写测试用例。理想的状态是,领域专家应该参与到整个项目的开发过程中,而不仅仅是需求阶段。

领域专家可以是专门聘请的对该领域具有较深造诣的咨询师,也可以是作为需求提供方的客户。极限编程强调现场客户原则,提倡将客户作为领域专家引入到整个开发团队中。现场客户需要参与到计划游戏、开发迭代、编码测试等项目开发的各个阶段。领域专家与架构师以及开发人员组成一个团队,贯穿开发过程的始终,就可以避免需求理解错误的情况出现。即使项目的开发与实际需求不符,也可以在项目早期及时修正,从而避免了项目不必要的延期,加强了对项目过程和成本的控制。正如Steve McConnell在构建活动的前期准备中提及的一个原则:发现错误的时间要尽可能接近引入该错误的时间。需求的缺陷在系统中潜伏的时间越长,代价就越昂贵。如果在项目开发中能够与领域专家充分地合作,就可以最大程度规避这样一种恶性的链式反应。

传统的软件开发模型同样重视与领域专家的合作,但这种合作主要集中在需求分析阶段。例如瀑布模型,就非常强调早期计划与需求调研。然而这种未雨绸缪的早期计划方式,对架构师与需求调研人员的技能要求非常高,它强调需求文档的精确性。一旦分析出现偏差,或者需求发生变更,在项目开发进入设计阶段后,由于缺乏与领域专家沟通与合作的机制,开发人员估量不到这些错误与误差,因而难以及时做出修正。一旦这些问题像毒瘤一般在系统中蔓延,逐渐暴露在开发人员面前时,已经成了一座难以逾越的高山。我们需要消耗更多的人力、物力,才能够修正这些错误,从而导致开发成本成数量级地增加,甚至于导致项目延期。当然还有一个选择,就是放弃整个项目。这样的例子不胜枚举,事实上,项目开发遭遇“滑铁卢”,究其原因,大部分都是因为业务逻辑分析出现了问题。

迭代式模型较之瀑布模型有很大的改进,因为它允许变更以优化系统需求。整个迭代过程实际上就是与领域专家的合作过程,通过向客户演示迭代产生的系统功能,及时获取反馈,并逐一解决迭代演示中出现的问题,保证系统向着合乎客户需求的方向演化。因而,迭代式模型往往能够解决早期计划不足的问题,它允许在发现缺陷以及需求变更时重新设计,重新编码并重新测试。

无论采用何种开发模型,与领域专家的合作都将成为项目成败与否的关键。这基于一个软件开发的普遍真理,那就是世界上没有不变的需求。一句经典名言是:“没有不变的需求,世上的软件都改动过3次以上,唯一一个只改动过两次的软件的拥有者已经死了,死在去修改需求的路上。”一语道尽了软件开发的残酷与艰辛!

那么应该如何加强与领域专家的合作呢?James Carey和Brent Carlson根据他们在参与的IBM SanFrancisco项目中获得的经验,提出了Innocent Questions模式,其意义即“改进领域专家和技术专家的沟通质量”。在一个项目团队中,如果我们没有一位既能担任首席架构师,同时又是领域专家的人选,那么加强领域专家与技术专家的合作就显得尤为重要了。毕竟,作为一个领域专家而言,可能并不熟悉软件设计方法学,也不具备面向对象开发和架构的能力。同样,大部分技术专家很有可能对该项目所涉及的业务领域仅停留在一知半解的地步。如果领域专家与技术专家不能有效沟通,整个项目的前途就岌岌可危了。

Innocent Questions模式提出的解决方案包括:

  • 选用可以与人和谐相处的人员组建开发团队;

  • 清楚地定义角色和职权;

  • 明确定义需要的交互点;

  • 保持团队紧密;

  • 雇佣优秀的人。

事实上,这已经从技术的角度上升到对团队的管理层次了。就好比篮球运动一样,即使你的球队集合了5名世界上最顶尖最有天赋的球员;如果没有整体的配合,各自为战,要想取得比赛的胜利依旧是非常困难的。团队精神与权责分明才是取得胜利的保障,软件开发同样如此。

与领域专家合作的基础是保证开发团队中永远保留至少一名领域专家。他可以是系统的客户、第三方公司的咨询师,最理想的是自己公司雇佣的专家。如果项目中缺乏这样的一个人,那么我的建议是赶快雇佣他。

我们需要确定领域专家的角色任务与职责。必须要让团队中的每一个人明确领域专家在整个团队中究竟扮演什么样的角色,他的职责是什么。一个合格的领域专家必须对业务领域有足够深入的理解,他应该是一个能够俯瞰整个系统需求、总揽全局的人物。在项目开发过程中,将由他负责业务规则和流程的制定,负责与客户的沟通,需求的调研与讨论,并与设计师一起参与系统架构。编档是领域专家必须参与的工作,无论是需求文档还是设计文档,以及用例的编写,领域专家或者提出意见,或者作为撰写的作者,或者至少他也应该是评审委员会的重要成员。

我们需要规范业务领域的术语和技术术语。领域专家和技术专家必须在保证不产生二义性的语义环境下进行沟通与交流。如果出现理解上的分歧,必须及时解决,通过讨论确立术语标准。很难想象两个语言不通的人能够相互合作愉快,解决的办法是加入一位翻译人员。在领域专家与技术专家之间搭建一座语义上的桥梁,使其能够相互理解、相互认同。还有一个办法是在团队内部开展培训活动。尤其对于开发人员而言,或多或少地了解一些业务领域知识,对于项目的开发有很大的帮助。在我参与过的半导体领域的项目开发中,团队就专门邀请了半导体行业的专家就生产过程的业务逻辑进行了全方位的介绍与培训。正所谓“磨刀不误砍柴工”,虽然我们消费了培训的时间,但掌握了业务规则与流程的开发人员,却能够提升项目开发进度,总体上节约了开发成本。

我们必须加强与客户的沟通。客户同时也可以作为团队的领域专家,极限编程的现场客户原则是最好的示例。但现实并不都如此完美,在无法要求客户成为开发团队中的固定一员时,聘请或者安排一个专门的领域专家,加强与客户的沟通,就显得尤为重要。项目可以通过领域专家获得客户的及时反馈,通过领域专家去了解变更了的需求,会在最大程度上减少需求误差的可能。

27.2  业务逻辑层的模式应用

Martin Fowler在《企业应用架构模式》一书中对领域层(即业务逻辑层)的架构模式做了整体概括,他将业务逻辑设计分为三种主要的模式:事务脚本(Transaction Script)、领域模型(Domain Model)和表模块(Table Module)。

事务脚本模式将业务逻辑看作是一个个过程,属于比较典型的面向过程开发模式。应用事务脚本模式可以不需要数据访问层,而是利用SQL语句直接访问数据库。为了有效地管理SQL语句,可以将与数据库访问有关的行为放到一个专门的入口(Gateway)类中。应用事务脚本模式不需要太多面向对象知识,简单直接的特性是该模式全部价值之所在。因而,在许多业务逻辑相对简单的项目中,应用事务脚本模式较多。

领域模型模式是典型的面向对象设计思想的体现。它充分考虑了业务逻辑的复杂多变,引入了策略模式等设计模式思想,并通过建立领域对象及抽象接口,实现模式的可扩展性,并利用面向对象思想与生俱来的特性,如继承、封装与多态,处理复杂多变的业务逻辑。唯一制约该模式应用的是对象与关系数据库的映射。我们可以引入ORM工具,或者利用数据映射器模式和活动记录(Active Record)模式来完成关系向对象的映射。

与领域模型模式相似的是表模块模式,它同样具有面向对象设计思想,唯一不同的是它获得的对象并非是单纯的领域对象,而是DataSet对象(注:在Java平台下,则是RecordSet对象。)。如果要为关系数据表与对象建立一个简单的映射关系,领域模型模式是为数据表中的每一条记录建立一个领域对象,而表模块模式则是将整个数据表看作是一个完整的对象。虽然利用DataSet对象会丢失面向对象的基本特性,但它在为表现层提供数据源支持方面有着得天独厚的优势。尤其是在.NET平台下,ADO.NET与Web控件都为表模块模式提供了生长的肥沃土壤。

27.3  PetShop的业务逻辑层

PetShop在业务逻辑层中引入了领域模型模式,这与数据访问层对数据对象的支持是分不开的。由于PetShop并没有对宠物网上商店的业务逻辑进行深入,也省略了许多复杂细节的商务逻辑,因而在领域模型模式的应用上并不明显。最典型的应该是对Order领域对象的处理方式,通过引入策略模式完成对插入订单行为的封装。关于这一点,我已在第25章有了详尽的描述,这里不再赘述。

本应是系统架构中最核心的业务逻辑层,由于简化了业务流程的缘故,使得PetShop在这一层的设计有些乏善可陈。虽然在业务逻辑层中,针对B2C业务定义了相关的领域对象,但这些领域对象仅仅完成了对数据访问层中数据访问对象的简单封装,目的仅在于分离层次,以支持对各种数据库的扩展,同时将SQL语句排除在业务逻辑层外,避免了SQL语句的四处蔓延。

最能体现PetShop业务逻辑的除了对订单的管理之外,还包括购物车(Shopping Cart)与期望列表(Wish List)的管理。在PetShop的BLL模块中,定义了Cart类来负责相关的业务逻辑。定义如下:

[Serializable]public class Cart {    private Dictionary<string, CartItemInfo> cartItems =         new Dictionary<string, CartItemInfo>();    public decimal Total    {        get         {            decimal total = 0;            foreach (CartItemInfo item in cartItems.Values)                total += item.Price * item.Quantity;            return total;        }    }    public void SetQuantity(string itemId, int qty)    {        cartItems[itemId].Quantity = qty;    }    public int Count    {        get { return cartItems.Count; }    }    public void Add(string itemId)     {        CartItemInfo cartItem;        if (!cartItems.TryGetValue(itemId, out cartItem))         {            Item item = new Item();            ItemInfo data = item.GetItem(itemId);            if (data != null)            {                CartItemInfo newItem = new CartItemInfo(                    itemId, data.ProductName, 1,                    (decimal)data.Price, data.Name,                    data.CategoryId, data.ProductId);                cartItems.Add(itemId, newItem);            }        }        else        {            cartItem.Quantity++;        }    }
//其他方法略}
Cart类通过一个Dictionary对象负责对购物车内容的存储,同时定义了Add()、Remove()、Clear()等方法,实现购物车内容的管理。Cart类是典型的领域对象,它是在领域建模过程中,根据真实世界领域中的概念识别出来的概念模型。