2016 年 2 月

第 31 卷,第 2 期

领先技术 - UXDD 体系结构衍生

作者 Dino Esposito | 2016 年 2 月

Dino Esposito多年以后的今天,能让我感到非常高兴的事情就是,我知道并能推荐一种或两种有效的方式来最大限度地提高软件工作效率。不要被软件营销强调的重点所欺骗。虽然软件在不断改善,但软件并不是现有大多数公司的核心业务。也就是说,公司更愿意接受的是,使用相同的软件很多年。根据 Tiobe.com 指数,在 2014 年夏天,Visual Basic 6 仍是前 10 大常用编程语言之一。不过,一年半之后,它的排名倒退了好几名。这似乎确认了一种趋势,即沉默的大多数公司往往会尽量推迟完全重写软件。实际上,沉默的大多数公司在软件技术方面至少有五年的积压工作 (backlog),但仍在经营自己的业务。然而,当这些公司是时候更新有积压工作 (backlog) 的系统时,架构师会寻求最新软件。

如今的软件体系结构

就软件体系结构而言,近来流行两个关键字。一个是“CQRS”,它是“命令查询职责分离”的缩写。另一个是“混合持久化”。 我在最近的一系列“领先技术”文章中提到过 CQRS。您不妨阅读 MSDN 杂志 2015 年 6 月刊中的“适合常用应用程序的 CQRS”(msdn.com/magazine/mt147237),作为入门初级文章。CQRS 是一种架构指南,仅建议您将用于更改系统状态的代码和用于读取和报告系统状态的代码区分开来。广而言之,混合持久化是指一种新出现的做法,即在同一数据访问层的上下文中使用多种持久化技术。术语“混合持久化”用两个英文单词概述了社交网络采用的复杂技术解决方案。社交网络采用这些解决方案是为了处理每天需要管理的海量数据。混合持久化的本质是仅使用非常适合您所拥有数据的类型和数量的持久化技术。

虽然 CQRS 和混合持久化的优点和潜在优势均很显著,但有时很难在复杂业务应用程序的上下文中发现它们具有相关性,因此也就无法让 Visual Basic 6、Windows 窗体或 Web 窗体这些大型基本代码死灰复燃。不过,相关性是存在的。如果缺少相关性,那么从长远来看可能会对项目和公司造成不利影响。同时,软件体系结构不是信念的实践。CQRS 可能看起来好像非常不错,但您必须找到正确的角度,才会发现它适用于您的方案。

如果您从 UXDD 的角度来看待软件体系结构,则可以很容易就发现它对 CQRS 和混合持久化都非常适合。

基于任务的设计

UXDD 工作流窗体已用于各种规模大小的公司。我亲自在不超过 10 人的小型团队以及价值数十亿美元的企业目睹过这些窗体的风采。在这种情况下,用户体验团队会通过与最终客户(无论是内部还是外部)密切合作,精心设计屏幕。用户体验团队会以 PDF 或 HTML 文件的形式,生成一些生动有趣的项目,并把这些项目传递给开发团队。问题是,大多数情况下,在整个后端堆叠设计并构建完成之前,开发团队会忽略此类项目。设计后端时,经常会很少考虑或不考虑在展示级别执行的实际任务。作为架构师,我们努力处理业务逻辑任务,并想办法优化实现,使之具有通用性。我们通常没有足够关注瓶颈和非最优数据展示和导航。当我们发现用户的实际应用程序使用体验不太理想时,通常都太晚了,这时已无法通过符合成本效益的方式实现更改(如图 1 所示)。

软件系统的由下而上设计与由上而下设计
图 1:软件系统的由下而上设计与由上而下设计

让我们来正视这个问题。除了具有无可争辩的数据存储有效性之外,关系模型的其他方面都会让人们对软件体系结构产生误解。或者至少在问世的头二十年后,它会开始让人们产生严重误解。在 20 世纪 90 年代中期,Java 领域中实际上就已经出现过关系数据模型和概念数据模型不匹配,这催生了 O/RM 工具的试用。在几代软件架构师(现称为经理)的成长过程中,都有一个观念,即最重要的就是有坚实的数据库基础。毫无疑问,可靠的数据库是所有可靠软件的基础。难办的是,域和应用程序逻辑很复杂,且数量多。在可以将零碎的此类逻辑添加到存储过程之前,这都没什么大不了。在突破这一门槛之后,软件架构师逐步向经典的三层体系结构(展示、业务和数据)发展,后来又向域驱动设计 (DDD) 分层体系结构(展示、应用程序、域和基础结构)发展。无论有多少层,基本上采用的还是由下而上设计。

如果以下两个条件中的至少一个得到确认,那么由下而上设计就能完美运行:

  1. 理想 UI 与关系模型十分接近。
  2. 用户沉默寡言且不主动。

如果您查看用于推广某项炫酷的新技术或框架的现成教程,则会发现软件设计似乎是一门精确的科学。当您离开“无菌实验室”来到现实世界时,就会发现软件设计充满着恐惧、疑虑和不确定性,这一真空地带只适用一条规则,就是: 这要视情况而定。

UXDD 只是建议您在没有清楚了解(读取:线框)信息理想体系结构以及用户与系统的理想交互之前,不要开始进行任何重要的开发工作。由上而下设计始于表示层的线框,可保证您尽可能真正满足用户需求。您听到“这不符合我们的要求”此类恶毒抱怨的风险大幅降低。请注意,如果线框被标记为错误,这绝不是偏好问题,更像是业务问题。忽略线框反馈就像是在生成的系统中刻意形成缺陷。

图 2 汇总了现今的体系结构阻抗不匹配。我们有能够描述理想表示层的线框,也有能够根据理解实现业务逻辑的代码。遗憾的是,这两者多半不匹配。如果软件项目几乎没有达到期望(尤其是从财务角度来看),您就确定这不是因为体系结构阻抗不匹配所致吗?

体系结构阻抗不匹配
图 2:体系结构阻抗不匹配

解决体系结构阻抗不匹配

委婉地讲,我们发现,覆盖全部域逻辑的全面域模型根本就毫无意义。域模型的概念和整套 DDD 方法的出现是为了解决软件核心位置的复杂性问题,但这是十多年前的事情了。后来发生了许多变化。虽然 DDD 的一些核心元素(尤其是策略设计概念和工具)如今比以往任何时候都具有价值,但全面覆盖了不同边界上下文的所有读写方面的面向对象的域模型构建起来十分痛苦,且不切实际。

同时,全面域模型的概念与软件的由下而上设计相一致。在此类软件中,您了解用户需求,然后需要精心构建模型,对模型进行编码,并在其上添加 UI。有趣的是,您可能会发现,成功软件是在一些魔力黑盒 (MBB) 之上带来完美有效的用户体验。

如果您从线框开始执行任何工程设计工作,就会确切了解 MBB 需要有效支持的内容。您还会知道,只要 MBB 可以正常发挥作用,系统就能真正尽可能满足用户的正式需求。几乎没有让开发者进行假设的空间,大多数客户迭代都是在项目的初始阶段进行分组。进行任何更改的费用都很低。一旦通过用户体验测试,剩余的大部分内容在实现详情方面都具有相关性。

UXDD 关注的是任务和事件。在由上而下设计中,起点是单击或选择之类的用户操作。每个用户操作都与应用程序层中的入口点绑定。例如,在 ASP.NET MVC 中,这意味着每个控制器方法(表示层)都与应用程序层类中的入口点绑定,如图 3 所示。

图 3:CQRS 控制器

public class SomeController
{  
  public ActionResult SomeQueryTask(InputModel input)
  {
    var service = new QueryStack.SomeService();
    var model = service.GetActionViewModel(input);
    return View(model);
  }
  public ActionResult SomeCommandTask(InputModel input)
  {
    var service = new CommandStack.SomeService();
    service.GetActionViewModel(input);
    RedirectToAction(...);
  }
}

两个 SomeService 类都属于应用程序层,应用程序层的可见范围是 SomeController 类呈现的表示层内。两个 SomeService 类可能属于与表示层相同的物理项目和程序集,也可能属于与表示层不同的程序集和项目。从体系结构角度来看,认为应用程序层与表示层共同协作。也就是说,如果需要多个表示层(即 Web、移动 Web 和移动应用),那么拥有多个后续逻辑可重用性有限的应用程序层是可以接受的。

对于用户可能在获准的 UI 中触发的每个任务,应用程序层中都会有入口点方法。如果您如实地再现收到的线框,就会确切了解每个屏幕的输入和输出。您就是不能出错。可以效率很低或速度很慢,但就是不能出错。

应用程序层与表示层交换数据传输对象,并为任务安排所有需要的工作流。在此过程中,应用程序层通常需要读取和写入永久存储中的一些数据、访问外部 Web 服务以及执行计算。这就是域逻辑,域逻辑不随用例变化。无论请求是来自 Web 表示层还是来自移动表示层,业务域任务都采用相同的方式运行,且完全可重用。简而言之,业务逻辑的概念可分成两个更为具体的细分逻辑:实现特定展示用例所必需的应用程序逻辑,以及不随展示和用例变化的域逻辑。

可根据各种模式(包括事务脚本和表格模块)来设计域逻辑。在这些情况下,业务规则是在您使用的类中注入的外来规则。通常情况下,业务规则构成计算器,您调用此计算器来获取有关操作可行性的响应。域模型模式(通常被曲解为 DDD 的真正本质)只是建议您创建以下类的模式:行为类似于真正的实体,之后包括业务规则并在其提供的方法中隐藏其应用程序。设计域逻辑并无对错之分,纯粹取决于态度和效率。

在基于任务的设计中,您可以立即了解操作是否提醒或报告系统状态。通过使用 CQRS 设计,您可以将命令从查询中自然分离出来。将它们放入单独的堆叠中带来了明显益处,即能单独编写和优化它们,且不会存在回归风险,甚至还提供潜在的简易可伸缩性。CQRS 和 UXDD 上下文就是天作之合。

您最后关注的问题 - 持久化!

继由上而下设计之后,持久化必然是您最后关注的问题。别弄错了: 所有应用程序都需要持久有效且可以有效查询的数据。作为您最后关注的问题,并不意味着您可以忽略持久化,而是说在面向用户的交互式系统中,只有在其他各方面(尤其是用户体验和域逻辑)均已妥善处理后,持久化设计才变成关键问题。正因为如此,应使用哪项数据库技术不受任何约束。此外,如果您采用 CQRS 设计,则还可以轻松拥有多个持久化层:一个用于命令,一个用于查询。这些层甚至以不同的存储技术和范例为依据(如果有用的话)进行单独管理。例如,如果您知道在某个时间点必须向 UI 报告分析结果,则不妨采用事件溯源框架,并将每个更新和相关操作作为事件进行跟踪。这样一来,您可以获得系统中所发生事件的完整日志,并能在某个时间点将此日志用作自助商业智能分析的起点。同时,如果您需要使用现有数据快速为用户提供服务,只需确保事件存储与满足您展示需求的数据投影保持同步即可。在此上下文中,数据投影是指一系列操作之后生成的状态。这跟想知道一系列事务(事件)之后生成的银行帐户余额(投影)一样。查询堆叠通常只是由 SQL Server 普通视图组成,而命令堆叠则可能基于关系表格、NoSQL 或事件存储(或兼而有之)。

这就是所谓的混合持久化。

总结

无论我们喜欢与否,用户主要是通过对直接体验的直觉来判断系统。今天,节省软件项目费用的关键因素在于,能否确保立即创建完全满足用户需求的内容。不仅要做得正确,还要确保及时性。借助 UXDD,您可以尽早知道要创建的系统的输出,这就减少了在部署系统时需要应用的修补程序(这也是用户真正不喜欢的地方)数量。此外,借助由上而下设计,您的设计体验可以自然地以新型模式(如 CQRS 和混合持久化)为导向。这些模式已明显超越了流行语的范畴,是构建有效软件的行之有效做法。


Dino Esposito是《Microsoft.NET: 构建面向企业的应用程序》(Microsoft Press,2014 年)和《新型 Web 应用程序》(Microsoft Press,2016 年)的合著者。作为 JetBrains 内的 Microsoft .NET Framework 和 Android 平台技术传播者,Esposito 经常在全球行业活动中发表演讲,并在 software2cents.wordpress.com 和 Twitter @despos 上分享自己对软件的看法。

衷心感谢以下技术专家对本文的审阅: Jon Arne Saeteras