分层架构中的可追溯性:路线图






4.61/5 (18投票s)
分层架构中的可追溯性使软件架构师能够以更结构化的方式定义、利用、重用和重新设计现有、当前和未来的应用程序架构。本文提供了一个路线图,用于轻松跟踪多层架构。
概述
分层架构中的可追溯性使软件架构师能够以更结构化的方式定义、利用、重用和重新设计现有、当前和未来的应用程序架构。本文提供了一个路线图,用于轻松跟踪多层架构。
引言
多层架构是当前软件开发趋势中最流行和最经过验证的模型之一。它使软件设计师和开发人员能够轻松地分离用户界面、业务逻辑和数据访问逻辑。
在过去的几年里,我参与了许多由其他开发人员完成的项目,以改进和/或增强它们。此外,我还看到了许多入门套件——网络上提供的示例项目,新手到专家都可以从中改进他们关于分层架构最新最佳实践的想法。
然而,在审查其他开发人员完成的项目源代码时,我总是会错过一个简单的文档,通过该文档我可以轻松掌握分层的核心思想,并可以根据自己的观点更新任何部分。无论应用程序多么庞大或架构多么复杂,拥有一个可追溯性结构都能帮助给定项目的未来开发人员快速理解现有的分层模型,以及/或在考虑适当的设计和需求上下文的情况下利用/更新现有结构。
可追溯性模型,考虑到逻辑层,是一个非常基础的文档,它解决了当前或未来应用程序架构的几个常见问题、答案和观点。
本文探讨了一些常见问题和解释点,这些将有助于我们形成自己的可追溯性模型。
分层架构设计因素:业务实体
业务实体是在逻辑层之间传递的逻辑数据容器。它可以是一个简单的类,其中包含通常从逻辑和/或物理数据表中映射的数据列,也可以是具有高开销的复杂类。
业务实体是什么类型?
对于 .NET 应用程序,业务实体可以分为自定义实体、类型化数据集和通用数据集。自定义实体通常比数据集简单且速度更快,而它们包含更多的开发者参与,而不是数据集,数据集包含大量内置功能,以及性能方面的权衡。选择适合应用程序需求和开发上下文的正确业务实体是软件架构中的基本操作之一。
业务实体类映射到哪个上下文?
对于简单的应用程序,直接从物理表映射到逻辑业务实体可以节省大量编程成本,尤其是在使用代码生成器时。然而,在简单和复杂的应用程序中,除了映射物理表之外,还有一些区域可能不适合直接从数据库表映射。
为了将数据库的一对多关系映射到逻辑应用程序模型,一些设计者会为业务实体创建一个基容器类,该类包含父业务实体和子业务实体作为数据元素。它促进了复杂数据容器的简单传递。
在哪里以及如何将连接操作(从多个数据表中获取)放在业务实体中?
当我们通过连接操作需要从多个数据库表中检索结果集时,一种常见的做法是使用通用数据集来存储结果。我们可以为连接操作在软件设计时确定的情况考虑自定义业务实体,这比数据集提供了更快的执行时间。
此外,在某些情况下,需要在应用程序端而不是数据库端实现连接操作,特别是当需要从数据库和/或 Web 服务源组合多个实体时。
如何将面向对象的概念映射到关系数据库设计?
让我们考虑一个简单的示例
- 联系人数据库表:联系人 ID(主键)、联系人姓名、联系人地址
- 员工数据库表:员工 ID(主键)、员工入职日期
- 客户数据库表:客户 ID(主键)、客户出生日期
现在,为了在关系数据库模型中利用面向对象的继承概念,我们可以从联系人表与员工表和客户表创建一对一关系,其中除了主键之外,员工 ID 和客户 ID 将作为联系人表的外键,其概念是所有员工和客户都被视为“联系人”对象。
现在,如果我们希望将这个想法映射到实际的 OOP 代码,我们将创建一个名为“Contact”的业务实体,并将“Employee”和“Customer”业务实体作为继承的子类。与“Contact”的 BLL(业务逻辑层)和/或 DAL(数据访问层)类对应的 BLL 和/或 DAL 类也可以继承,以面向对象的方式执行所需的操作。
对于“类型化”实体是否有特殊考虑?
有些实体的数据变化不频繁,但其中一两个项会在几个月内添加。例如,用户类型、产品类别、订单类型和付款方式。为了更好地进行数据库操作,这些字段的值保存在数据库中,并作为外键引用到其他实体。但有趣的是,“类型”实体的这些值在编码层中经常使用,开发人员必须使用硬编码的主键值或此类型的字符串值来执行任何业务逻辑或 UI(用户界面)逻辑,这会降低代码的“可读性”或“可维护性”(如果我们更改类型名称怎么办;在这种情况下,我们需要在整个项目中搜索字符串或主键值!)。
由于 .NET 框架支持枚举类型,因此,如果可能,可以在这些业务实体上应用枚举类型。当需要添加或更改其任何值时,开发人员只需专注于设置枚举值的代码段。
分层架构设计因素:逻辑层
将考虑多少层?为什么?
在这里,我们将讨论一些主要的逻辑层设计考虑。不一定非要将每个系统都构建成流行的“三层”模型。可能存在一个设计上下文,其中单层或双层方法是一个好的设计选择。在这里,我们需要明确分层的划分及其职责。我们还可以解决一些现在和将来可能存在的考虑因素,以支持当前的分层划分,例如,关于多个 UI(Windows Forms、Web Forms)、数据源(SQL Server、XML、Oracle 等)、平台等。
您使用缓存层吗?
为了提高应用程序性能,“缓存”是当前最佳实践趋势中的一种流行技术。一些设计支持在 UI 界面级别缓存数据,其中有内置的缓存机制。有时,将缓存机制放在一个单独的层中可以提供更好的隔离和对应用程序空间的控制。如果是这样,应该正确处理缓存隔离、过期以及与其他层的耦合策略。
层如何与下一级层通信?
对于三层应用程序模型,在某些情况下,UI 界面层可以直接使用 DAL,而不是通过 BLL(即“直接下一层”)使用它。这种应用程序模型通常包含一小组类和方法在业务逻辑层中,忽略了所有功能从 DAL 到 BLL 的映射。这种设计问题通过减少中间层的参与来提供一定性能。
另一方面,我们可以有一个模型,其中完整的函数集需要在 BLL 中存在,并且是“紧密耦合”的。在此上下文中,UI 层不能直接使用 DAL 的功能。
此时,需要软件架构师明确解决此设计问题。
DAL 类和 CRUD 方法的映射标准是什么?
DAL 类可以根据物理或逻辑业务实体、BLL 类来定义,反之亦然。此映射过程可以包括一对一、一对多或多对多的技术。
例如,对于电子商务应用程序,我们有两个业务实体(物理或逻辑),名为“订单摘要”和“订单项”。与 DAL 的一对一映射关系将包含两个单独的 DAL 类,“OrderSummeryDAL
”和“OrderItemsDAL
”。
BLL 类和 CRUD 方法的映射标准是什么?
BLL 类可以根据物理或逻辑业务实体、DAL 类来定义,反之亦然。此映射过程可以包括一对一、一对多或多对多的技术。
例如,对于电子商务应用程序,我们有两个业务实体(物理或逻辑),名为“订单摘要”和“订单项”。在类级别上,与 BLL 和 DAL 的一对多映射关系可以包含一个单独的 BLL 类,“OrderBLL
”,以及两个单独的 DAL 类,“OrderSummeryDAL
”和“OrderItemsDAL
”。“OrderBLL
”类的“CreateOrder
”方法可以分别调用“OrderSummeryDAL
”和“OrderItemsDAL
”的“CreateOrderSummery
”和“CreateOrderItem
”方法,以完成整个订单的数据库创建操作,如下所示。
即使 BLL 和 DAL 类之间存在一对一关系,也可以在方法级别包含一对多关系。例如,我们有一个 BLL 类“OrderBLL
”和一个 DAL 类“OrderDAL
”。在方法级别包含一对多关系将包括OrderBLL
中的“Create
”方法,该方法基本上调用“CreateOrderSummery
”和“CreateOrderItem
”方法,如下所示。
此时需要定义适当的设计问题。
对于 BLL 中的 CRUD 操作,何时使用业务实体,何时使用参数?
业务实体可以作为方法参数传递到 BLL,或者所有数据元素都可以作为方法参数单独传递到 BLL 方法中。某些设计也可能支持业务实体类包含相应的 CRUD 方法,这基本上不需要将任何数据元素传递到 CRUD 方法中。
对于 DAL 中的 CRUD 操作,何时使用业务实体,何时使用参数?
业务实体可以作为方法参数传递到 DAL,或者所有数据元素都可以作为方法参数单独传递到 DAL 方法中。某些设计也支持业务实体类包含相应的 CRUD 方法,这基本上不需要将任何数据元素传递到 CRUD 方法中。
在逻辑层中,我们通常如何处理一对多(数据库)关系?
一对多关系是关系数据库设计上下文中最核心的考虑因素之一。然而,在将数据库表映射到逻辑层时,需要清楚地定义它是如何处理的。例如,设计可以包含一个包含父类和子类作为属性的容器类,或者包含所有数据实体的单独类,而不管父/子关系。
在逻辑层中,我们通常如何处理多对多(数据库)关系?
通常,两个数据表之间的多对多关系会形成一个包含这两个表主键的第三个表。例如,如果存在“Person”和“Address”两个表或数据实体,并且这些实体之间存在“多对多”关系,那么将存在另一个表来保存此关系,该表可以命名为“Person-Address”。一些逻辑模型支持在应用程序分层组件(DAL、BLL、BE 等)中为多对多表创建一个单独的逻辑实体,而一些应用程序则将操作嵌入到两个主表中的任何一个逻辑实体中。
我们需要考虑哪些特殊情况?
可以利用代码生成器工具实现适用于大多数业务实体的逻辑层常见模型,这可以节省大量开发人员时间。除了通用模型之外,可能还有一些需要特殊考虑的情况。特定和复杂的业务逻辑可以作为此类特殊情况的示例。
如何处理空值?
空值是基本考虑因素之一,如果不加以处理,可能会在应用程序实时运行时引起许多混淆和错误。一些应用程序架构支持一种模型,当用户从用户界面提供空数据时,将空值放入数据库。对于字符串/文本类型,数据有时保存为空字符串(即,String.Empty
)。但当空字符串本身被视为一个值时,这会引起混淆。此外,除了字符串/文本类型数据外,对于其他类型的数据(数字、小数、日期时间等),在保存和检索到物理数据库表时,都需要特别注意空值。需要解决这些考虑因素。
在哪里抛出无效值的异常?
在某些情况下,通过使用验证控件,可以在用户界面级别处理无效值。但是,我们需要解决在何处以及如何处理未从用户界面层验证或需要在代码级别验证的无效值。此时需要解决这些考虑因素。
数据库实用程序类的哪些部分是特定于供应商的?
为了执行应用程序的数据库操作,DAL 通常使用一些数据库实用程序,这些实用程序有助于编写最少的代码行来执行数据操作。在数据实用程序类中,某些类和方法可能特定于供应商(例如,SQLHelper
类仅适用于 MS SQL Server 操作),而某些类和方法则可以不考虑数据库供应商而使用。这些问题以及界限应在此处解决。
分层架构设计因素:物理层
逻辑层是如何连接的?
BLL、DAL、CL 等逻辑层可以托管在同一台机器上,也可以分布在单独的机器上,尤其是在需要高伸缩性的地方。需要定义和解释逻辑层和物理层之间的关系和隔离,并附带适当的设计目标。
在分布式环境中考虑逻辑层时,有多种可用技术。可以考虑基于 SOAP/HTTP 的 Web 服务或 .NET Remoting,它们各有优缺点。需要在此时解决这些考虑因素。
分层架构设计因素:一些基本设计问题
我们使用事务吗?如果使用,是在 DAL 还是 SP 层?为什么?
事务是一种机制,其中一批操作可以在整个过程的中间点发生任何故障时回滚,或者在所有操作成功完成后提交。事务可以用于数据库级别和/或应用程序级别。
我们在 CRUD 操作上使用级联吗?如果使用,是在 DAL 还是 DB 层?为什么?
级联是一种存在于两个数据库表之间关系的技术。它允许根据父表记录相应地删除/更新子表记录。除了数据库级别,还可以考虑在中间逻辑层中执行此操作。软件设计者需要对此考虑因素给出适当的解释。
在哪里以及为什么我们使用内联 SQL、LINQ 查询、CLR 存储过程或内置存储过程来操作数据?
为了操作数据库对象,开发人员有几种可用方式,例如内联 SQL、LINQ 查询、CLR 存储过程或内置存储过程。它们各自都有好处和权衡。这些问题需要在此解决。
当我们需要高性能时,有哪些标准实践?
开发者社区中存在一些由顶尖专家定义的最佳实践。然而,在考虑有关性能的最佳实践时,当然会存在一些权衡(例如,安全性、可用性等)。根据应用程序的上下文,应妥善调整这些要点。
Create/Update/Delete 方法(BLL/DAL 中的)向调用者返回什么值,以通知数据操作的成功?
Create 方法可能会将新记录的生成主键返回给调用者,以便在需要时使用。返回默认值可能被视为数据操作失败。此外,由于 Update 或 Delete 操作通常不需要返回值,因此这些类型的方法只能返回布尔值来通知成功/失败状态。
在编写新的业务实体、BLL 和 DAL 时,我们如何考虑命名约定?
遵循数据库对象、类名和方法的命名约定可以使编码和未来的修改更加容易。例如:给定业务实体的 BLL CRUD 方法可以这样编写
GetEmployeeById
:返回单个员工业务实体实例。CreateEmployee
:创建一个新的员工业务实体。
当现有关系、实体和属性发生添加或修改时,应该执行什么操作?
这是软件架构可追溯性中非常重要的问题之一。需要非常清晰地提供一些简单的示例演练。例如,在向系统中添加新实体或在现有实体中添加数据元素时需要执行哪些步骤等。
结论
本文探讨了一些在开发多层应用程序架构时需要考虑的常见设计注意事项。但是,您可以通过添加/删除此处提供的问题来创建自己的可追溯模型。
请继续关注最新更新以及空白模板,请访问 @ code.msdn。
附录
- DAL: 数据访问层
- BLL: 业务逻辑层
- CRUD: 创建、读取、更新、删除