《Microsoft .NET 企业级应用架构设计》
========== ========== ==========
[作者] (意) Dino Esposito (意) Andrea Saltarello
[译者] (中) 陈黎夫
[出版] 人民邮电出版社
[版次] 2010年06月 第1版
[印次] 2010年06月 第1次 印刷
[定价] 69.00元
========== ========== ==========
【前言】
(P001)
每次遇到软件项目时,我们都会创建一个解决方案。这个过程就叫做架构设计,而架构设计的最终产物就是软件架构。
架构是一个系统中最为核心的部分,是构造系统过程中的支柱。
【第01章】 【当代的架构师和架构】
(P003)
在软件领域中,架构就是指为客户构建系统。
(P004)
优秀的架构师是团队所必需的。那么什么是优秀呢?这个人必须有丰富的经验、良好的教育以及相应的资格。
当代系统需要很多的工程以及理解,但不需要太多艺术和主观的猜测,这正是优秀架构师应当努力的方向。
(P007)
描述架构的一种非常流行的方法是使用 UML 图表。
(P009)
架构将被精炼到真正重要的事情上 —— 无论这事情是什么。
(P010)
领域驱动设计理论实际上建议总是使用工厂来创建复杂的对象。
一般来说,使用 virtual 或 sealed 修饰符需要承担不少的责任。
(P015)
架构设计要基于对需求的分析,分析会决定系统将要做什么,而架构则决定系统如何做。
(P017)
架构师最终将负责系统的开发,并与开发者团队协调,技术详细说明书正是架构师和开发者沟通并传达架构的工具。
沟通是架构师的关键。架构师需要与开发者沟通,也需要与项目经理和分析师沟通,或许还要与用户沟通。架构师很重要的一个技能就是语言要明确清晰。
(P018)
架构师不过就是一个优秀一些,更有经验的开发者而已。
(P019)
通常而言,分析应属于项目领域中的专家,而架构师无需如此。分析师可以告诉架构师他对系统的理解,包括系统如何工作以及系统需要做什么等。
(P020)
架构师和开发者之间的区别就在于经验和教育经历。
【第02章】 【UML 必要知识】
(P028)
UML 基于一系列的图形化标记,特别适合在面向对象场景中建模。
(P029)
建模对于任何软件项目来说都是个核心步骤,对于大型的企业级应用程序 (特别是那些支撑公司正常运作的程序) 来说,其作用更加不容忽视。
在软件领域中,模型应该与建筑中的导航图、计划图和物理模型一样重要 —— 这是必须而不是可选的。
【第03章】 【设计原则和模式】
(P056)
一个设计精良的系统并不是一系列指令和修补的堆砌,里面还有很多与设计直接或间接相关的东西。
一个良好的代码体系可以容易地找到 Bug 所在,也可以轻易地修复 Bug ,还可以在任何时候进行任何程度的改进,包括可扩展性和可伸缩性。考虑到这些,可维护性就成为了设计系统时最应该关注的问题。
(P060)
高内聚的模块意味着高的可维护性和可重用性,因为这些模块的外部依赖很少。
(P061)
模块之间显然需要通信,不过通信应该依赖于一系列设计良好且不易改变的接口。
低耦合和高内聚唇齿相依。若系统满足了这两个条件,那么通常来说该系统已经基本满足了高可读性、高可维护性、易于测试和易于重用的要求。
分离关注点的核心在于将系统拆分成各不相同且最好没有重叠的功能。每个系统中的功能都表示了一个关注点,即系统的一个方面。
(P062)
分离关注点是通过模块化代码以及大量运用信息隐藏来实现的。
(P063)
在过程式编程中,分离关注点是依靠函数和过程来实现的。
(P065)
面向对象设计的基本原则可以总结成 3 条 : 找到合适的对象、尽量降低耦合和尽量保证代码重用。
(P068)
基于接口,而不是实现编程。
(P070)
尽量使用对象组合,而不是类型继承。
(P072)
模块应该对扩展开放,但对修改关闭。
(P074)
开放 / 封闭 原则和里氏替换原则有着紧密的关系。任何使用了某个违反里氏替换原则的类型的方法都无法满足 开放 / 封闭 原则。
(P076)
高层次组件不应该依赖于低层次组件,二者均应依赖于接口。抽象不应该依赖于细节,细节应该依赖于抽象。
(P080)
使用设计模式在本质上并不能让你的解决方案更有价值,真正的价值在于,在交付的时候你的解决方案是否能正常工作并满足需求。
(P084)
惯用法是一个硬式编码在编程语言或实现在 框架 / 技术 中的模式。
惯用法是一种充分利用语言能力,并从代码中获取期待的行为的做法。
(P086)
推荐在公开签名中使用 IList<T> 或其派生接口,或者使用实现了 IList<T> 接口的自定义类型。
(P088)
所有的 IoC 框架都基于一个容器对象构造,这个容器对象将和一些配置信息绑定,并解析依赖。调用者代码将实例化容器,并将需要的接口以参数的形式传入。作为响应, IoC / DI 框架将返回一个实现了该接口的具体对象。
【第04章】 【业务层】
(P121)
业务层是所有分层系统的核心,包含了系统的核心逻辑。
(P123)
业务逻辑层处于分层系统的中间位置,负责表现层和数据访问层之间的信息交换。
很多时候,架构师更倾向于使用数据迁移对象 (Data-Transfer Object , DTO) 在层之间交换数据。
业务对象同时包含了数据和行为,是一个可以参与到领域逻辑中的完整对象。
数据迁移对象更像是一种值对象 —— 即一系列数据的容器而没有相关的行为。
为了序列化,业务对象中的数据会复制到数据迁移对象中。
数据迁移对象仅仅是所需要部分数据的投影而已。
(P125)
工作流与普通类型的不同之处在于,它允许通过逻辑上的图表来表达任意的逻辑。
(P126)
对于 Tier ,我们用其表示系统中的物理上的硬件和软件,由执行同样功能的一台或多台服务器定义。相反地, Layer 用来表示系统中完成指定任务的逻辑部分。
Layer 一般用来组织代码,而 Tier 则是指代码运行的位置。
多个 Layer 可以共存于同一个物理 Tier 上。同时,某个 Layer 也应该可以轻易地移动到另一个 Tier 上。
每个层都有可以看做是一个黑盒,这个黑盒对输入和输出有着定义清晰的契约 (接口) ,且没有或很少依赖于其他层以及物理服务层。
分层带来的好处主要是条理清晰,且有利于重用以及分离关注点。
分离关注点建议层应该有良好的封装,或者说高内聚低耦合。
良好的可重用性加上正确地对功能进行拆分将大大降低系统的维护成本。
(P127)
物理层中一个重要的概念是,一个层就表示一个需要穿过的边界,这个边界可能是进程边界或计算机边界。穿过边界是个代价很高的操作,若需要到达远程计算机,那么代价要比到达同一台计算机中不同的进程高。一个估算比例是穿越进程边界要比进程内部调用慢 100 倍左右。若需要通过网络访问,还要更慢一些。
我们应该尽可能地降低物理层的数目。添加一个新的物理层之前,必须要经过仔细的成本收益分析,这样做的代价大多在于增加了复杂性,而优势一般体现在安全性、可伸缩性以及容错性上。
(P128)
远程使用的业务逻辑层必须要由一个粗粒度的接口封装起来 —— 一般使用门面 (Facade) 模式。
对于那些不是一直在线的应用程序来说,将业务逻辑层部署到客户端是必须的。
业务逻辑层不应该看做是一个整体的组件,或者一些不相关模块的组合。多年的实际经验告诉我们,业务逻辑在其他层 (指经典的 3 层架构) 中的适当重复是可以接受的,也是很多现实程序的做法。不过这种做法有一定的限度,且不应该受到鼓励。
(P129)
分散在表现层和数据访问层中的逻辑有可能只是出于性能方面的考虑才进行的重复。
(P130)
数据访问层需要了解的仅仅是如何获取记录,需要检查的是数据类型以及可控性,需要确保的是数据完整性和索引。
你应该仅仅把存储过程当做一个数据库工具,而不是作为业务逻辑的表达工具。
(P132)
历史上,事务脚本是第一个被广泛应用的业务逻辑模式。若干年后,另一种基于表数据的模式逐渐浮出水面,名为表模块 (Table Module) 。表模块的设计角度仍是一系列事务,不过事务是按照数据分组的。
最简单的对象模型看上去就像是数据库表的数据模型,这里组成模型的对象就是数据库中的记录,并加了一些额外的方法。这个模式通常叫做活动记录 (Active Record) 。
(P133)
领域驱动的设计一定会使数据模型和领域模型之间存在着不小的差异。这个模式通常叫做领域模型 (Domain Model) 。
总而言之,若你感觉更应该关注于操作,可以选择事务脚本模式。若以数据表的形式考虑更方便,那么选择表模块模式。若领域模型与数据模型非常相似,那么活动记录模式可能更适合使用。而若建模的过程需要从各个相关对象开始,并划分成众多子系统,则可以选择领域模型模式。
(P134)
一旦你对领域模型模式有了足够的了解,无论系统的复杂性如何,你都可以使用领域模型模式。
过程式模式在达到某种程度之后,其添加功能带来的复杂性会以指数方式增长。
(P135)
事务脚本 (Transcation Script) 模式可能是业务逻辑层中最简单的模式了,它是一个纯粹的过程式模式。
事务脚本模式鼓励你放弃所有的面向对象设计,将业务组件直接映射到需要的用户操作上。
(P136)
事务脚本适合应用于那些业务逻辑非常简明直白,且最好不大可能改变的简单场景中。
简单是事务脚本最值得一提的优势,它没有开始时的额外代价,且可以和快速应用程序开发 (Rapid Application Development, RAD) 环境良好地配合。
对于逻辑不多、时间紧迫且依赖于强大的集成化开发环境 (如 Visual Studio) 的项目,事务脚本是其理想的选择。
(P146)
若系统中的表现层和数据访问层都是基于表状数据结构,那么表模式将是非常好的选择。
(P151)
DataSet 的核心功能是一个数据容器,专门用来管理表格状数据,其中的名词也使用了表、列和行等类似数据库的概念。DataSet 并不了解为其提供数据的对象,且 DataSet 可被序列化。
DataSet 要配合其他数据表相关的对象 (例如, DataTable 、 DataRow 和 DataRelation ) 工作。 DataSet 中包含了一系列数据表的集合以及这些表之间的关系,而 DataTable 中则包含了一个数据行的集合,以及相关的约束和索引信息。
(P157)
活动记录 (Active Record , AR) 是一种应用于相对简单的领域模型,但仍旧基于对象的模式。
活动记录基于数据表中的行,而不像表模块那样基于数据表。
活动记录对数据有行级别的粒度,而表模块关注的是整个数据表。
活动记录就是指一个封装了数据库表或视图的一行的对象,对象中可以同时包含数据 (列中的值) 和行为 (包含逻辑的方法) 。
活动记录对象中通常会包含用来执行常用查询的查找方法、 CRUD 操作、验证以及领域相关的计算和检查功能。
(P158)
活动记录非常适合于很多的 Web 站点,因为无论其中涉及多少个数据表,其业务实体和关系型数据表的结构大多比较类似。
(P167)
在设计领域逻辑时,若选用了过程式方法或活动记录模式,那么实际上采取的是以数据为核心的做法。
在数据为核心的方法中,我们一般从数据库开始设计,随后根据所需要的操作 (最终会操作数据库) 或数据表进行业务组件的建模。
(P168)
领域驱动设计其实是一种考虑问题的方法 —— 一种将问题领域放在所有事情中首要位置的方法。
(P169)
领域模型可以看做是活动记录的“大哥”。领域模型完全与数据库独立,是一个尽可能贴近真实流程的模型,而不是去贴近架构师希望的流程。
(P180)
仓储模式在领域模型和数据访问代码之间添加了另一个抽象的类。
【第05章】 【服务层】
(P186)
服务层可以看做是表现层结束、业务逻辑层开始的一个边界,服务层用来尽可能地降低表现层和业务逻辑之间的耦合,让表现层无需关注业务逻辑层中的具体实现组织方式。
(P188)
服务层位于系统中的两个互相通信的逻辑层之间,使两个层能够在松散耦合并优美地彼此分离开的同时,仍旧可以完美地彼此通信。
服务层将组织业务逻辑层中的组件 —— 包括服务、工作流和领域模型中的对象,并根据需要调用数据访问层。
业务逻辑层中唯一需要完全和数据库细节分离的部分就是领域模型。
服务层是表现层和其他层之间的边界,理论上服务层应该通过数据迁移对象与表现层交互,并在内部根据需要将其转换成领域模型类。服务层中暴露的每个方法都组织使用到其他服务,包括工作流,以及通过数据映射器或 对象 / 关系映射器 (Object / Relational Mapper, O/RM) 支持的语言执行数据库操作。
(P189)
服务层从表现层中得到输入,并组织工作流、服务和数据访问组件。
通常来说,服务层就是一系列暴露了逻辑上相关方法的类,供其他层 (通常是表现层) 调用。
(P191)
在设计应用程序时,面向服务允许使用独立的组件并同时提供固定的公开接口,这些组件可以随时被替换,只要与其他组件的契约不变,就不会影响到系统的功能。
(P192)
从架构上讲,服务层应用了软件设计中一个通用且人人皆知的原则 —— 低耦合,可能还应用了另一个原则 —— 高内聚,这是通过一个中间层解除用户界面和中间层的耦合来实现的。
服务层中的服务将被表现层直接调用,在 Web 前端中,也就是页面的代码后置 (Code-behind) 类将调用服务层。
(P193)
MVP 模式的实现能确保在 Windows 窗体或 Web 页面加载之后可以立即得到一个展示器对象,这通常使用依赖注入实现。
(P195)
服务层为用户界面和中间层提供了一个统一的契约,因此中间层即可专注于实现应用逻辑 (Application Logic) 。
(P198)
服务层添加了额外的抽象并除去了两个交互层之间的耦合,若这一点在你的系统中比较重要,那么应该创建服务层。服务层可以帮助你实现一个粗粒度的远程接口,并降低表现层与业务层之间的通信流量。
(P199)
若你对 .NET Framework 有所了解,那么应该知道创建服务 (WCF 或 Web 服务) 就是个普通的类加上一些特性修饰而已。
服务归根到底就是一个附加了一些东西的类。
(P203)
服务层是通过一系列的 (服务) 类集合来把方法暴露给用户界面的。
(P236)
若想在浏览器中的页面里调用 Web 或 WCF 服务,必须使用一个代理类。该代理类就是一个 JavaScript 类,将在访问某个特定的 URL 时由服务的运行时生成。
(P237)
JSON 是一个新的标准,用于以脚本方式发出请求时浏览器和 Web 服务器之间通过 HTTP 交换数据。 JSON 优于 XML 的最主要原因可以总结为 JSON 要比发展成熟的 XML 更加简单,且在所有支持 JavaScript 的浏览器中都有免费的内建反序列化引擎。
【第06章】 【数据访问层】
(P244)
数据访问层是系统中唯一知道并使用连接字符串和数据表名称的地方。
(P245)
数据访问层将对象模型保存至数据库中,并根据数据库中的数据创建类型实例。
(P246)
数据访问层是系统中唯一可以操作数据库的一层,系统的其他部分也不应该包含任何有关数据库的信息。
(P248)
仓储是一个用来放置对指定对象进行常用查询的类。
(P256)
面向数据访问层接口编程,而不是面向数据访问层实现。
(P266)
微软的 Unity Application Block (简称 Unity) 就是一个轻量级的依赖注入容器,支持构造函数、属性和方法调用的注入, Unity 随 Enterprise Library 4.0 一起发布。
(P271)
若需求不允许你使用商业或免费的 O/RM ,也必须要手工创建数据访问层。
(P334)
NHibernate 完全支持 POCO 对象,而 LINQ-to-SQL 和 EF 则无法做到。
【第07章】 【表现层】
(P345)
表现层由两个主要组件组成 : 用户界面和表现层逻辑 (也叫做 UI 逻辑) 。
表现层逻辑将负责中间层和 UI 之间数据流的交互。
(P346)
表现层的职责应该从更高的角度考虑,即不依赖于物理 UI 、较高可测试性和不依赖于数据模型。
一个设计良好的表现层会让开发者在开发周期中更容易地切换到不同的 UI 。
(P349)
架构师的主要职责是保证产品满足了所有的重要质量特质 (特别是可用性和可访问性) 。
表现层逻辑的最终目标是确保视图中的数据可以正确地 流入 / 流出 。
(P351)
自治视图目前已经不再流行,最主要的原因就是它难以测试。
我们并不是生活在完美的世界中,这也就意味着与理想情况相比,我们需要更多的让步。
(P352)
在设计系统的表现层时,有一个基本的原则需要遵守,那就是让表现层和程序其他部分完全分离,包括业务层和数据访问层。
(P356)
MVP 更加适合于大型、复杂的应用程序。
(P358)
视图的另一个关键职责是呈现。
(P369)
不管怎样, MVP 并不建议应用于任意的程序上。
(P373)
对于那些需要支持多个 GUI 的场景,可能唯一的选择就是 MVP 模式了。 MVP 模式提供了一个最佳的可测试性、分离关注点、维护性和代码重用的组合。