业务层——跨越边界传输数据

跨越边界传输数据

物理层意味着需要跨越的物理边界,不管是进程边界还是机器边界。跨越边界是一个昂贵的操作。触及远程物理机器的代价比触及同一台机器的另一个进程的更高。一个可以参考的经验法则是跨越进程边界的调用比对应的进程内调用要慢100倍。如果要通过网络传输才能到达端点的话就更慢了。
一个调用是如何通过网线跨越边界的?轻装传输?还是背负一切传输?选择最适合的方式跨越边界(逻辑的或物理的)传输数据是应用程序的业务部分要解决的另一个设计问题。

分层架构里的数据流

在这里插入图片描述
上图(跨越分层架构各层的数据流的示意图)展示了分层架构里的一个相对抽象的数据流。

  1. 数据从用户界面以InputModel的形式穿过应用程序层。
  2. 根据被请求的操作,应用程序层可能需要使用InputModel的内容创建领域实体(比如说,从提供的信息创建一个新的订单)。
  3. 在分层的领域模型系统里,持久化通常意味着把领域实体转换成物理数据模型(通常是关系型数据模型)。
  4. 在回来的路上,在请求查询时,从数据模型读到的内容首先会被转换成领域实体图,然后适配进视图模型给用户界面渲染。

这4种模型在逻辑上都是不同的,但有时候它们可能是重合的。领域模型可以直接持久化到基础设施层,在这种意义上,领域模型和数据模型通常是相同的。在ASP .NET MVC应用程序里,输入模型和视图模型在控制器操作的GET和POST实现里通常是重合的。在CRUD系统里,所有模型都可能重合,也就是只有一个一一一它将会是MVC模式里的“模型”。

早在20世纪80年代刚被设计出来时,MVC是一个应用程序模式,可以用来架构整个应用程序。那是一体化系统的年代,被端到端创建成单一事务脚本。多逻辑层和多物理层系统的出现改变了MVC的角色,但没有否定它的重要性。MVC仍是一个强大的模式,但单一模型的理念不再有效。MVC里的模型被定义为“在视图里使用的数据"。这意味着今天的MVC基本上是一个表现模式。

共享领域模型实体

在遵循领域模型模式的分层架构里,领域实体是最合适的输入容器。领域实体是否适合向上传
给用户界面,并在需要时序列化它们穿越物理层?
此外,在数据驱动的应用程序里,如果你要把领域模型传给表现层(某种自我跟踪的实体),那
么贫血领域模型更加适合。
当你在类里使用带有方法的富模型(领域模型)时,把领域实体传给表现层没有意义,因为这会
让表现层得以实体里的那些方法。在DDD应用程序里,表现层应该有一个不同的表现模型,基于DTO
模型(在很多情况下是一个视图模型),DTO是根据屏幕/页面的需求而不是基于领域模型来设计的。

在各层里的领域实体

我们认为在应用程序层编排的组件和模块之中传递领域模型的类是没有问题的。只要数据传输
135

发生在逻辑层之间,就应该没有问题,不管是技术上的还是设计上的。如果数据传输发生在物理层
之间,你可能会遭遇序列化问题,尤其是领域实体构成了一个错综复杂的图,里面还有循环引用。
在这种情况下,引入一些专门的数据传输对象针对一两个场景进行处理可能更加容易。
湖注意:确切地说,当实体具有延迟加载属性时,在不同的逻辑层之间传输领域实体也可能面临
问题。在这种情况下,当接收实体的层试图通过延迟加载属性读取数据时,就会引发异常,因为数
据还没加载,而存储环境已经不再可用。

领域模型(Domain Model)实体包含的内容与用户界面(View Model)一致,可以在用户界面使用Domain Model,省下一堆其他的数据传输类。原则上,在各个逻辑层和物理层上使用领域模型实体不但可以接受,在某些情况下更是合适的,但是,有一些可能的副作用需要考虑。

为命令和查询使用单一模型的危险-补充

从下一章开始我们会探讨这个问题,在第10章“CQRS导论”和第11章“实现CQRS”里会有 更深入的讨论。

就目前而言,从用户界面引用领域实体可能需要表现层代码充分利用领域实体内建的行为。这个行为本质上就是领域逻辑,而领域逻辑必须一直与业务规则保持一致,我们认为表现层代码有打破这个一致性的潜在风险。

将来扩展的可能约束

由于现代应用程序会扩展新的前端。这可能需要为用户界面提供数据的不同聚合,而这些聚合并不存在于领域模型里。如果不想为了满足特定前端的需要而修改领域模型。可以添加专门的数据传输对象(DTO)。

使用数据传输对象

有时候,使用领域实体可能很方便;有时候,使用数据传输对象(DTO)会更好。没有哪个解决方案总比其他的好。需要具体问题具体分析。

数据传输对象概论

数据传输对象专门用来在不同的物理层之间携带数据。DTO没有行为,只是一个简单的get和set的容器,创建起来也相对不昂贵(比如说,它不需要单元测试)。作为一个简单容器,使用DTO的原因是它允许你打包多块数据,在单次往返里传输所有数据。DTO与生俱来就是可序列化对象。在涉及远程组件时通常都推荐用它。但是,我们希望从一个
更广泛的角度来看待DTO,考虑在不同的逻辑层之间使用。

2.DTO与领域实体
DTO的通常用法是,举个例子,当你需要同时显示或处理订单和客户信息,但实际处理所需的信息只用到订单和客户实体上的一部分属性。DTO可以把复杂的层次结构简化成简单的数据容器,只包含必要的数据。

在这里插入图片描述

图7.4在跨越不同的物理层和逻辑层传输数据时的DTO与领域实体

在不同的逻辑层共享领域实体通常是没问题的,而且可以最大限度减少牵涉的类的总量。从单纯的设计角度来看,使用DTO是“完美的”解决方案,它保证了接口组件之间最大程度的解耦,也保证了可扩展性。

但是,完整的DTO解决方案毫无疑问会导致各个VisualStudio项目出现大量的小类。一方面,你需要通过文件夹和命名仝间来管理和组织那些数量可观的类。另一方面,你也不得不面对把数据加载到DTO以及从DTO提取数据的代价。

AutoMapper和适配器

使用DTO真正的代价就是数据填充以及读取数据。AutoMapper可以把属性从领域实体复制到DTO,反之亦然。使用示例

// 源类型和目标类型之间创建一个映射,启动映射过程,用源类型的实例里的数据填充目标类型的实例
mapper.CreateMap<YourSourceType,YourDtOType>();
// 通过Map方法调用它
var dto=Mapper.Map<YourDtOType>(sourceObject);

AutoMapper这类自动化工具的缺点:
当用它从实体创建DTO时,无可避免地要遍历整个实体图,为此必须把整个实体图从存储读到内存里。或许,通过领域服务返回现成的DTO更加便捷。

注意:另一个传输数据的方案是使用IQueryabIe对象。一个极具争议但正在普及的做法是从数据仓储返回IQueryable。
IQueryable是LINQ的核心接口,它所做的是针对支持LINQ的数据源提供计算查询的功能。
从仓储返回IQueryable的一个原因是让上层可以轻易创建不同类型的查询。这样做可以保持仓储接口轻薄,同时减少使用DTO的需要,因为某些DTO可以是匿名类型。虽然DTO是从查询创建的,但它们属于特定的层,隔离在层的上下文里,并且易于管理。

总结

经典的二元论:做正确的事与正确地做事。它们没有谁比谁更优先,二者都是重要的观点。

正确地做事是表现层应该关心的事。正确地做事的核心理念是效率:以优化的方式实现任务,快速且流畅。做正确的事则是业务层应该关心的事。做正确的事的核心理念是效益和达成目标。软件系统的终极目标是满足需求和忠实再现领域空间。

为了使得领域建模更加高效,领域模型等模式和IDDD等方法学是必不可少的。这些方法学对多年以来被称为业务层的东西进行了改造。我们引入了应用程序层和领域层,同时把数据访问和其他基础设施组件(如邮件服务器、文件系统和外部服务)都隔离到基础设施层里。

我们在这里提及的领域模型模式都是比较通用的内容,在下一章里,我们将会着重探讨领域驱
动设计。

笑到最后

一个成功的业务层需要敏锐的观察和建模。它还需要通过尽可能简单的方式做事的能力。“简约而不简单”是我们最喜欢的真言之一。此外,我们为本章列出的第一条墨菲定律是:如果你有任何复杂的东西可以工作,那么你可以肯定过去也有某些简单的东西可以工作。
一个可以工作的复杂系统总是从一个可以工作的简单系统进化而来的。
在软件可靠性上的投入会持续增加,直到超出错误的可能代价。
理论和实践在理论上没有区别,但在实践上有。

猜你喜欢

转载自blog.csdn.net/Star_Inori/article/details/85054190