【iSAQB软件架构】以架构为中心的开发方法
在本文中,我们介绍一些在架构的设计和实现中使用的当代以架构为中心的开发方法和概念。目的是简要概述以架构为中心的开发方法。但只是一部分示例,并非涵盖了所有的情况。
DDD领域驱动设计
领域驱动设计(DDD)是一系列原则和模式的集合,帮助开发人员设计对象系统。这个术语是由埃里克·埃文斯(Eric Evans)提出的,由于它使得通过功能领域来构建大型系统变得更容易,所以它是更好地理解微服务的一个重要因素。使用这种方法,每个子系统都形成一个独立的单元。
DDD 的作用:
- 比如在构建一个电商平台时,通过 DDD 可以将订单管理、商品管理、用户管理等功能领域分别设计为独立的子系统,每个子系统都有清晰的职责和边界。
形成独立单元的好处:
- 假设开发一个医疗信息系统,将患者信息管理、诊断流程管理、药品库存管理等作为独立的子系统,便于独立开发、测试和维护,降低系统的复杂性。
作为设计的基础的功能模型
您应该通过构建功能领域来开始您的设计。领域模型应该纯粹基于功能进行构建,并且需要在整个项目中被接受。这个模型改善了领域专家和开发人员之间的沟通,并能够精确地制定需求。通过软件中的直接映射,领域模型可以极其容易地被测试。基于这个模型,创建了一种通用的、特定领域的语言,其元素应包含在项目词汇表中。
这种通用语言就是所谓的无处不在的语言,是领域驱动设计的核心概念。这种语言应该在软件开发的所有领域中使用——即所有项目成员在源代码以及数据库和其他组件中应该使用与领域专家相同的术语。它描述了功能领域、领域模型的元素、类、方法等等。
如下图,展示了使用领域驱动设计创建的领域模型的元素。
领域对象的系统管理
实体代表功能领域的核心对象,通常是持久的。在该领域内,它们具有一致的标识和明确界定的生命周期。实体是您系统中的“事物”。通常以名词的形式(即人或地点)想象实体是很有用的。
值对象描述其他对象的状态,本身没有标识。它们只是描述具有标识的对象。它们可以由其他值对象组成,但绝不是实体。与实体相反,值对象不能被修改。
服务是表示领域的过程序列的操作。它们不被实体识别,通常也没有自己的状态。这些操作的输入和输出是实体、聚合或值对象(即领域对象)。
对于领域对象的管理,埃文斯推荐了三种不同的管理对象:
• 聚合
聚合封装了相互关联的领域对象,并且恰好有一个以根对象形式的实体,表示对聚合的唯一访问。外部对象可能只包含对根实体的引用。
• 工厂
工厂封装了非平凡、复杂的对象结构。工厂无法访问其他层,专门用于构建功能对象。
• 存储库
存储库使所有类型的对象能够为其他对象获取对象引用,并通过底层持久化技术封装功能对象的访问。
功能领域的结构化
功能领域的结构化通常基于功能对象或用户事务进行。
• 基于功能对象的分解是合适的
o 重用很重要。
o 功能逻辑复杂、广泛或灵活。
o 面向对象范式有很好的理解。
• 这种类型的分解或多或少对应于面向对象的分解。
• 在以下情况下,基于用户事务的结构化是合适的
o 简单的数据采集和简单的数据操作
o 外部系统集成
o 简单或有限的功能逻辑
o 面向对象程序的经验有限
用户事务对应于系统用户可以执行的一个操作,包括所有系统内部的操作,如输入数据的检查。
这里最重要的是保持设计的完整性。在可能的情况下,您应该根据相似的方面分解系统的所有部分,并始终一致地应用(和记录)这个概念。
以下是进一步的分析和举例:
基于功能对象的分解:
- 比如在开发一个图像处理软件时,如果希望复用图像滤波、色彩调整等功能模块,且这些功能逻辑复杂,就适合基于功能对象进行分解。
- 对于一个企业资源规划(ERP)系统,如果对面向对象的开发范式理解深入,且各个业务模块(如采购、销售、库存管理)的功能逻辑丰富多变,那么基于功能对象分解能更好地组织系统结构。
基于用户事务的分解:
- 假设开发一个简单的在线投票系统,主要是用户进行投票操作和数据采集,功能逻辑相对简单,适合基于用户事务分解。
- 在一个小型电子商务网站的初期开发中,如果主要是处理用户的下单、支付等简单操作,以及与外部支付系统的集成,基于用户事务分解可能更合适。
域的类型
将一个系统细分为以下几个领域:
• 核心领域
• 通用子领域
• 支持性子领域
领域包含系统的核心功能,并描述了系统存在的原因。在可能的情况下,它应该只由最有经验的开发人员来实现。
通用子领域包含对业务重要但不属于核心领域的功能——例如,发票生成或信件发送。它可以购买或外包。
支持性子领域包含支持性和从属的功能,也可以由经验较少的开发人员处理。然而,它应该与核心领域严格分开——例如,使用反腐败层。
领域的集成
正如已经提到的,每个子系统都应该形成一个独立的单元。在 DDD 中,另一个重要的术语是**“有界上下文”**,它使得确定微服务的适当粒度更容易。每个模型都有一个上下文,一个复杂的功能领域很可能由几个有界上下文组成。
对于不同领域的集成,存在各种可能性。这些包括:
• 发布语言
应该有一种通用语言,通过它领域可以相互作用——例如 JSON 或 XML。
• 开放主机服务
这是一种经典的 SOA 形式的交互。一个领域指定一个协议,其他领域可以通过它来使用——例如,RESTful Web 服务。
• 反腐败层
通过一个隔离层使用来自另一个领域的服务。
• 分道扬镳
两个领域完全相互独立,没有集成。
MDA模型驱动架构
模型驱动架构(MDA)是一种能够从模型(如 UML)生成(部分)应用程序的概念,它由对象管理组织(OMG)开发。
模型驱动软件开发(MDSD)通过模型的转换自动生成软件组件[RH06]。MDSD 使用模型和生成器来改进软件开发。
模型是 MDA 的核心,通常借助特定领域语言(DSL)来制定。DSL 可以是基于文本的,也可以是图形的。然而,DSL 不是强制的。MDA、MDSD 和其他类似方法与 DSL 无关。
有两种创建可执行应用程序的方法。可执行模型要么由虚拟机直接解释(例如对象管理组织的可执行 UML),要么通过一个或多个转换被转换为可执行应用程序。
由 OMG 定义的模型驱动架构(MDA)不过是 MDSD 方法的一个特殊实例,与架构无关。虽然模型驱动软件开发提供了建模语言的选择,并且对转换为可执行应用程序没有限制,但 MDA 在这方面有更具体的要求。例如,要使用的 DSL 应该符合 MDA(即使用 OMG 的元对象设施(MOF)定义,MOF 形成了元模型)。在实践中,主要使用 UML 概要。
平台无关的方面在平台无关模型(PIM)的范围内建模。PIM 随后被映射到一个或多个特定于平台的模型(PSM)。因此,PSM 创建了与特定平台的链接,然后可以从此生成代码。
MDSD方法本质上具有以下优点:
• 提高开发效率
• 能够更好地整合专业专家
• 软件更容易修改
• 改进软件架构的实现
• 功能逻辑可以相对容易地移植到其他平台
然而,模型驱动软件开发需要建立由 DSL、建模工具、生成器、平台等组成的基础设施,并且在创建模型时需要大量的规范。规范工作也更大,通常只有模型的一部分可以自动转换为构件。
参考架构
系统构建模块的生成式创建
如果特定的操作必须以相同或相似的方式反复进行,那么可以借助生成技术实现自动化。软件系统的创建常常在一些细节上与其他系统有所不同,但具有功能性或技术性的共同特征。软件开发中的一个首要目标是尽可能多地复用系统构建模块。
基于模板的生成器是软件生成的一种常见手段。在此背景下的模板主要是基于文本的。模板的一部分访问输入数据,这些输入数据也主要是基于文本的。借助模式定义了可以应用模板的情况。使用预定义的规则,对模板进行修改,输出基于生成器的输入。这种方法的成熟示例有 Java 发射器模板(JET)或 XSLT 转换语言,它是可扩展样式表语言 XSL 的一部分,用于转换 XML 文档。
另一种生成技术使用基于 API 的生成器,这些生成器(除其他用途外)用于生成 PDF 文档。在这种情况下,要生成的文档的整个结构通过 API(应用程序编程接口)进行描述。
模型驱动的软件开发(MDSD)是在软件开发中使用生成器的一个很好的例子。
横切关注点
一个程序可能包含在代码的几个独立位置出现的任务。如果要记录活动,必须在活动之前和之后包含特定的代码。如果日志设计要出现在程序的多个位置,开发人员会在不同的地方编写或复制相同的代码。其他例子包括数据库访问、事务管理和身份验证的重复。这种代码的多次出现与“不要重复自己”(DRY)的概念不一致。面向切面编程能够封装此类任务,以便任务只需编程一次,但可以在多个地方执行。
面向切面编程实现了“横切关注点”的关注点分离原则。横切关注点——也称为系统级关注点——影响整个系统或技术约束,并且不容易被封装。它们对于特定的功能(如日志记录)实际上并非必要。
横切关注点的例子有:
• 日志记录
• 性能分析
• 验证
• 会话
• 同步
• 安全
• 错误处理
• 事件驱动编程(例如PropertyChangeEvents)
• 软件测试
已建立的面向切面编程的实现有 AspectJ、JBoss AOP 和 AspectWerkz。
面向对象
在面向对象的上下文中,过程被称为操作或方法。面向对象背后的思想是将现实世界的概念映射到对象中——例如,一辆汽车。这个对象还可以存储其数据(类型、颜色等),并提供编辑和查询此数据所需的操作。
面向对象的一个重要特征是分类。让我们继续以汽车为例。汽车经销商不只有一辆车,而是有许多不同的汽车。因此,“汽车”类可以被视为一种抽象。面向对象的代码定义这样一个类一次,但允许它被实例化多次。
如果可以从一个类实例化一系列对象,则需要在运行时区分它们。因此,每个对象都有其自己唯一的对象 ID,可用于调用对象的操作。
下图展示了“汽车”类的 UML 类图和该类的两个实例化对象的对象图。
为了更精确地指定对象之间可能的交互,有一系列关系可用,如关联、聚合、继承、接口和抽象类。
通过这些额外的抽象,面向对象的架构比过程式架构提供了更好的模块化支持。一般来说,实现上述原则更容易,但这并不自动使面向对象的架构成为一个好的架构。在这里,架构师也必须开发一个适当的面向对象模型,并正确应用相关的技术和方法。
面向过程
架构结构化的一种经典(并且仍然流行)的方法是使用过程。过程能够将一个复杂的算法分解为可重用的子算法,这构成了实现关注点分离原则的基础。
许多编程语言,如 C 或 Cobol ,都是基于过程的,面向对象的系统(如 Java 的静态方法)也支持过程式抽象。
这份以架构为中心的开发方法总结涵盖了三个核心支柱:DDD、MDA和参考架构。下面来为你解析其要点和应用场景:
1. DDD (领域驱动设计)
- 核心思想: 将软件系统设计的核心聚焦在业务领域本身及其复杂性上,通过建立精确的业务模型来驱动设计和实现。
- 关键要素:
- 1.1 作为设计基础的功能模型: 强调深入理解业务功能、流程和规则,并将其直接转化为软件模型的核心。模型是开发的核心资产。
- 1.2 领域对象的系统管理: 围绕业务概念(如“客户”、“订单”、“账户”)构建对象模型。这些对象包含数据和行为,封装业务逻辑。
- 1.3 功能领域的结构化:
- 战略设计: 划分复杂的业务领域为更小的、高内聚的限界上下文。每个限界上下文拥有自己独立的领域模型、术语和实现。
- 战术设计: 在限界上下文内部,使用模式如实体(具有唯一标识的生命周期对象)、值对象(描述属性、无标识)、聚合(定义一致性和事务边界)、领域服务(处理不属于单个对象的逻辑)、领域事件(记录领域发生的重要事情)等来构建模型。
- 1.4 域的类型: 识别不同类型的子域:
- 核心域: 业务的核心竞争力和差异化所在,投入最多资源。
- 支撑子域: 为核心域提供支持,对业务重要但非核心差异化因素。
- 通用子域: 常见、通用的问题领域,通常可以使用现成解决方案或简单实现。
- 1.5 领域的集成: 定义不同限界上下文(子域)之间如何通信和交换数据。常用模式包括:
- 开放主机服务: 提供定义良好的协议(如API)供其他上下文调用。
- 防腐层: 在上下文边界进行转换,防止外部模型的“腐化”影响内部模型。
- 发布/订阅: 通过领域事件进行异步通信和解耦。
- 共享内核: 谨慎地共享一小部分公共模型和代码(需高度协调)。
- 价值: 解决复杂业务问题,提升模型与业务的一致性,提高代码可维护性和可理解性,促进团队沟通(使用统一的通用语言)。
2. MDA (模型驱动架构)
- 核心思想: 通过在不同抽象层次上创建模型,并(部分)自动化地将高层模型转换为低层模型或代码,来提高开发效率、一致性和可移植性。
- 关键要素:
- 2.1 基础设施与挑战:
- 需要强大的基础设施:领域特定语言、建模工具、代码生成器、目标平台规范等。
- 规范工作量大: 创建精确、可转换的模型本身需要大量工作。
- 自动化程度受限: 通常只有模型的核心结构或骨架能被自动生成,复杂的业务逻辑、算法等仍需手动编码补充(“通常只有模型的一部分可以自动转换为构件”)。
- 2.2 核心分层与转换:
- 平台无关模型: 关注业务逻辑和核心结构,不涉及具体技术细节(如Java、.NET、数据库类型)。
- 平台特定模型: 由PIM转换而来,添加了特定目标平台(如Java EE, .NET Core, Oracle DB)的技术细节(框架、API、部署约束等)。
- 代码生成: 最终从PSM生成可执行代码。
- 2.1 基础设施与挑战:
- 价值: 提高抽象层次,提升平台可移植性(PIM可在不同平台上实现),增强模型的一致性(通过生成减少手动编码错误),可能提升开发效率(特别是对于重复性结构代码)。
3. 参考架构
- 核心思想: 为特定类型的问题域或应用场景提供经过验证的、可重用的、高层次的结构化解决方案蓝图。它定义了系统的主要构建块、它们的职责以及它们之间的关系。
- 关键要素 (基于你的描述):
- 系统构建模块的生成式创建: 参考架构可以作为模板或基础,通过生成工具(如脚手架、代码模板)快速创建系统的基本结构和核心组件,加速项目启动。
- 横切关注点: 好的参考架构会明确考虑并规定如何处理横切关注点,如日志记录、安全认证、授权、事务管理、监控、异常处理等。通常会采用特定的模式(如AOP - 面向切面编程)或设计将这些关注点模块化,避免核心业务逻辑的代码污染。
- 面向对象: 许多参考架构(尤其是企业应用)基于面向对象原则设计其构建块(组件、服务)及其交互。
- 过程式方法: 在某些特定领域(如高性能计算、嵌入式系统)的参考架构中,过程式或基于数据流的方法可能更合适。参考架构应明确其主导范式。
- 价值: 提供最佳实践的起点,减少设计风险,提高系统质量和一致性,促进团队间理解,加速开发进程。常见的参考架构例子包括分层架构(n-tier)、微服务架构、事件驱动架构(EDA)、企业服务总线(ESB)模式等。
三者关系与架构为中心
- DDD 提供业务建模基础: 它帮助深入理解业务领域,识别核心概念和边界(限界上下文),这是设计高质量架构(无论是MDA中的PIM还是参考架构的实例化)的根本前提。
- MDA 提供自动化与抽象: 它利用模型(起点通常是业务/领域模型)来驱动开发过程,通过抽象和自动化提升效率和一致性。DDD建立的领域模型可以作为MDA中PIM的重要输入。
- 参考架构 提供结构蓝图: 它为如何组织系统、划分模块、处理技术关注点提供了经过验证的模式和模板。在实现DDD限界上下文或构建MDA中的PSM/PIM时,选择合适的参考架构(如微服务用于上下文,分层架构用于上下文内部)是架构设计的核心决策。
- 以架构为中心: 所有这些方法都强调架构设计在整个软件开发过程中的核心地位:
- DDD 将架构重心放在业务领域模型及其结构上。
- MDA 将架构重心放在不同抽象层次的模型及其转换上。
- 参考架构 提供可重用的架构模式作为设计的起点和约束。
- 共同目标是:通过明确的、深思熟虑的架构决策,管理复杂性,确保系统满足功能性需求和非功能性需求(性能、可维护性、可扩展性、安全性等)。
总结
iSAQB强调的这套“以架构为中心的开发方法”融合了业务驱动(DDD)、模型驱动(MDA)和模式重用(参考架构)的理念。DDD确保架构扎根于业务本质,MDA提升架构设计的抽象层次和效率,参考架构提供实现架构的可靠蓝图和最佳实践。三者协同,旨在构建出更健壮、可维护、更能响应业务变化的高质量软件系统。理解这三者的内涵及其相互关系,是掌握现代软件架构设计的关键。