65.9K
CodeProject 正在变化。 阅读更多。
Home

CEO 的日记 - 质疑一切

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (22投票s)

2005年9月8日

10分钟阅读

viewsIcon

37534

关于创建灵活架构的决策。

这是计划中的系列文章的第二篇,涵盖了剪贴簿、设计与架构以及技术部分。虽然项目构思的故事非常有趣,但您还将阅读到产品设计中所涉及的公司决策、技术挑战和其他考虑因素。我们认为社区还会从一些独立的组件中获得价值,我们将在技术讨论中提供相应的代码。

往期回顾:如果您是第一次看到这篇文章,您可能想先阅读前面的文章: 序言

引言

在做出了创建新平台的心理准备后,是时候从更技术的角度重新审视目标了。首要的重点是什么?虽然新系统中有很多重要的事情,但最突出的无疑是灵活性。需要有简单的方法来修改系统的几乎每一个方面。一些简单的修改将由非技术用户来完成。

灵活性体现在三个主要方面

  • 数据模式的更改。
  • 标准业务逻辑的更改。
  • 屏幕布局和客户端工作流程的更改。

为了实现这些目标,系统设计将从一张白纸开始。没有什么是神圣不可侵犯或想当然的。我们的口号是“质疑一切”。确定一个需求,然后设计出满足该需求的最佳方法。通过一个迭代的过程,将这种新方法应用于当时已有的系统设计。如果需要向基础添加附加功能来支持新特性,就添加。所有这些结构都经过持续审查。如果底层结构变得过于复杂,就需要一种新的机制来满足所有已识别的需求,同时又不增加复杂性。尽管这听起来像是一个笨拙的过程,但我们有意识地努力使用简单的组件,而不引入大量的内置功能。这带来了高度灵活的设计,并很好地应对了挑战。

利用 XML

早期的一项决定是使用 XML 来指定配置信息。XML 非常灵活,尽管有些冗长。它也是当今的标准。以平台无关的方式指定任何内容,如今几乎都意味着使用 XML。利用 XML 还可以使用其他工具进行数据编辑、验证和处理。

文档层

通过 XML,可以使用“diff and merge”(差异和合并)功能将更改集成到基础文档中,这些更改将被称为“层”。“层”描述的含义是,可以应用不止一层,这是设计的一个驱动力。

一个简单的例子有助于说明这个概念

为了处理层和 XML 合并的所有复杂性,还需要额外的元数据。例如,UI 中的制表符顺序通常由控件反序列化的顺序决定。这为 XML 合并以及各种文档及其层的整体架构和定义增加了一个(无意中的)“层”的复杂性。

模式定义

允许更改数据模式相对简单。提供“基本结构”的规范,然后提供新增字段和表的额外规范。

例如,让我们来看一个用 XML 定义的简单模式元素。

<Table Name="Customer">
  <Field Name="ID"/>
  <Field Name="FirstName"/>
  <Field Name="LastName"/>
</Table>

如果一个“层”想扩展这个模式定义,它会定义额外的字段(或删除/修改现有字段)。

<Table Name="Customer">
  <Field Name="IsNewCustomer"/>
</Table>

然后,使用 XML 的“diff and merge”工具,将构建应用程序的完整模式,包括所有应用程序层。这些规范还允许引用完整性、默认值等。这种方法与原始产品类似。主要的变化是将规范切换到 XML。

XML 锤子变得太大了

对 XML 的这种迷恋导致了我们的第一个“锤子和钉子”的时刻。由于需要将业务逻辑集成到层中,为什么不也将其放入 XML 中呢?为了回答这个问题,需要确定 XML 实现的样子,以及如何合并来自其他层的业务逻辑。首先,XML 不包含编译代码。这意味着 XML 中的代码要么被解释,要么被“即时”编译。虽然后者是可能的,但它会比已编译的代码带来更多的问题。早期解释代码的经验几乎消除了这种选择。合并也是一个问题。除了 CVS 和类似系统,修改代码的合并并非一门完美的科学。查找问题将极其困难。

业务规则

从 XML 灾难的边缘退一步,是时候重新审视需要实现哪些业务逻辑修改了。在本讨论中,业务逻辑的一个元素将被称为业务规则。业务规则需要接收输入(参数),对它们(以及规则可访问的其他数据)执行一些处理,并生成输出。谈论业务逻辑修改时的第一个冲动是尝试改变规则内部的过程。这是不必要的!业务规则本身就是一个“黑匣子”。输入一些东西,输出一些东西。如果你想改变业务逻辑,你实际上并不想改变过程,你想改变过程使用的输入它生成的输出。如果你有能力做这两件事,你基本上可以改变业务逻辑所做的任何事情。

举例说明

熟悉面向切面编程的开发人员会发现 AOP 概念与此架构相似。

有了对业务逻辑修改目标的新的理解,答案就变得更清晰了。只要代码可以在运行时动态加载,我们就可以使用编译后的代码。需要的是能够向应用程序的能力添加新过程,并指定何时运行它们。这些新过程可以是全新的业务规则,也可以是对现有规则输入或输出的修改逻辑。现在唯一需要的是调用编译规则的机制,再加上一种传入参数和获取结果的方法。

虽然 XML 可能不是向系统中添加业务规则的方式,但它实际上是指定何时运行这些规则的好方法。确定了四种业务规则执行类型:

  • 由客户端应用程序的命令专门启动的规则。
  • 在其他规则内部调用的规则。
  • 在检测到触发事件时自动执行的规则。
  • 修改其他规则的输入或结果的规则。

结果是,第四种情况只是第三种情况的一种专门情况。需要识别业务规则,以便基于配置设置执行它们,因此将使用一个对象来识别和启动业务规则。“BeforeProcess”和“AfterProcess”事件将能够启动其他规则来做出这些更改,以修改输入或输出。现在,层只需要识别规则将“触发”的位置。

为了使用规则的“之前”和“之后”事件来覆盖业务逻辑,现有的规则必须尽可能“原子化”。也就是说,每个规则只应该做一件事。如果规则执行一个动作,然后在该规则内部使用该动作的结果,那么就没有机会覆盖第一个动作的结果或第二个动作的输入。同样,这是 AOP 架构的一个原则,未能实施模块化、隔离的业务规则会影响系统的灵活性,毕竟这是系统的主要目标。虽然新系统无法强制执行“原子”业务规则的做法,但肯定推荐这样做。

容器和数据对象

关于业务规则,最后要解决的是如何传入和传出信息。

业务规则参数

软件中的大多数方法都接受一系列参数,每个参数都是特定类型的数据。然后,这些方法可以在返回值中返回单个数据。不幸的是,传递的参数和返回值类型构成了方法的“签名”,这使得存储指向这些函数的指针并以标准方式调用它们变得很尴尬。

业务规则数据需求

新平台灵活性带来的另一个问题是,业务规则所需的数据并未明确定义。该规则可能会调用另一个规则,该规则具有已覆盖的“之前”或“之后”过程,需要任何相关规则都不需要的数据。确保每个规则都拥有所需所有数据的方法是传递所有可用数据。最终的解决方案是使用一个“数据容器”,其中包含任何规则可能使用的所有数据,并将该容器对象作为任何业务规则的唯一参数传递。结果也会以类似的方式返回,通过设置容器中的值。

客户端通信

拥有大量可用于业务规则的数据很重要,而且这并没有带来多少开销。另一方面,将这些数据传输到客户端可能涉及大量开销。需要设计一种机制来最大程度地减少服务器和客户端之间的网络流量。

因为客户端没有业务逻辑(基于为这些“层”强制执行的隔离),所以客户端不需要拥有服务器数据容器中的所有数据。为了限制传输的数据量,需要识别客户端感兴趣的数据。为此,数据元素存储在数据容器中,该容器是一个简单地称为“数据对象”的对象。数据对象中的两个标志协调数据传输的时间。

数据对象在客户端上的功能与服务器上的功能几乎相同(相对于确定何时需要更新),因此客户端上也创建了一个数据容器。实际上,在实现中,客户端和服务器共享完全相同的基类。“IsDirty”标志表示此数据对象中的值与另一侧数据对象中的值不同。“ClientAware”标志用于标识服务器上哪些数据对象是客户端需要的。每次数据对象中的值发生更改时,“IsDirty”标志都会被设置。通信始终由客户端发起。当客户端联系服务器时,它会构建一个“命令包”来告诉服务器该做什么。

此过程的第一部分是通过数据容器进行检查,并将每个更改的数据对象的新值放入数据包中。然后清除脏标志。在服务器端,完成处理后,它将遵循相同的过程,但只将同时为脏客户端感知(client aware)的项目添加到响应包中。通过这种方式,双方能够持续同步,但只传输更改的数据。

业务规则回顾

容器实际上是在业务规则的其他一些要求被完善之前设计(并大部分工作)的。到目前为止,这些决定都相对直接,但它们引入了一系列新的问题需要解决:

  • 业务规则将如何识别?
  • 容器中的数据将如何识别?
  • 客户端如何请求运行业务规则?
  • 哪些事件应触发自动业务规则?

这些问题的答案将很大程度上受到与客户端通信的影响。这部分难题背后的概念也变得越来越清晰,但需要经过多次迭代才能得出满意的解决方案。

这些问题的进一步讨论将在下一期中进行。

© . All rights reserved.