当前位置: 首页 > 新闻 > 信息荟萃
编号:7258
领域驱动设计软件核心复杂性应对之道高清.pdf
http://www.100md.com 2021年2月22日
第1页
第10页
第20页
第26页
第48页
第161页

    参见附件(21400KB,614页)。

     领域驱动设计软件核心复杂性应对之道

    全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计,小编今天给大家带来了领域驱动设计软件核心复杂性应对之道书籍,可以哦.

    编辑推荐

    ●“领域驱动设计之父”经典著作●众多声名显赫软件大师鼎力推荐●凝聚领域建模专家数十年的实战经验●深度剖析构建高质量复杂系统的核心技术领域模型使开发人员可以表达丰富的软件功能需求,由此实现的软件可以满足用户真正的需要,因此被公认为是软件设计的关键所在,其重要性显而易见。但讲述如何将领域模型用于软件开发过程的杰出的实用资料却不多见。本书正是这一领域声名显赫的作品,受到众多业界大师的赞美和推介,广受读者好评。要通过创建领域模型来加速复杂的软件开发,就需要利用大量实践和标准模式在开发团队中形成统一的交流语言;不但要重构代码,而且要重构代码底层的模型;

    同时采取反复迭代的敏捷开发方法,深入理解领域特点,促进领域专家与程序员的良好沟通。针对这些内容,本书结合真实项目,系统地介绍了领域驱动开发的目标、意义和方法,充分讨论了复杂系统的建模与设计问题。

    本书将指导面向对象开发人员、系统分析人员和设计人员合理地组织工作,各有侧重、彼此协作,有条不紊地进行复杂系统的开发,帮助他们建立丰富而实用的领域模型,并由此创建长期适用的优质软件。

    相关内容部分预览

    内容简介

    本书是领域驱动设计方面的经典之作,修订版更是对之前出版的中文版进行了全面的修订和完善。全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计。

    书中给出了领域驱动设计的系统化方法,并将人们普遍接受的一些实践综合到一起,融入了作者的见解和经验,展现了一些可扩展的设计新实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。

    作者简介

    Eric Evans “领域驱动设计之父”,世界杰出软件建模专家。他创建了Domain Language公司,致力于帮助公司机构创建与业务紧密相关的软件。他在世界各地宣讲领域驱动设计(Domain-Driven Design ......

    目 录

    封面

    其他

    扉页

    版权

    版权声明

    译者序

    序

    前言

    致谢

    第一部分 运用领域模型

    第1章 消化知识

    1.1 有效建模的要素

    1.2 知识消化

    1.3 持续学习

    1.4 知识丰富的设计

    1.5 深层模型

    第2章 交流与语言的使用

    2.1 模式:UBIQUITOUS LANGUAGE

    2.2 “大声地”建模

    2.3 一个团队,一种语言

    2.4 文档和图

    2.4.1 书面设计文档

    2.4.2 完全依赖可执行代码的情况

    2.5 解释性模型

    第3章 绑定模型和实现

    3.1 模式:MODEL-DRIVEN DESIGN3.2 建模范式和工具支持

    3.3 揭示主旨:为什么模型对用户至关重要

    3.4 模式:HANDS-ON MODELER

    第二部分 模型驱动设计的构造块

    第4章 分离领域

    4.1 模式:LAYERED ARCHITECTURE

    4.1.1 将各层关联起来

    4.1.2 架构框架

    4.2 领域层是模型的精髓

    4.3 模式:THE SMART UI“反模式”

    4.4 其他分离方式

    第5章 软件中所表示的模型

    5.1 关联

    5.2 模式:ENTITY(又称为REFERENCE OBJECT)

    5.2.1 ENTITY建模

    5.2.2 设计标识操作

    5.3 模式:VALUE OBJECT

    5.3.1 设计VALUE OBJECT

    5.3.2 设计包含VALUE OBJECT的关联

    5.4 模式:SERVICE

    5.4.1 SERVICE与孤立的领域层

    5.4.2 粒度

    5.4.3 对SERVICE的访问

    5.5 模式:MODULE(也称为PACKAGE)

    5.5.1 敏捷的MODULE

    5.5.2 通过基础设施打包时存在的隐患

    5.6 建模范式

    5.6.1 对象范式流行的原因5.6.2 对象世界中的非对象

    5.6.3 在混合范式中坚持使用MODEL-DRIVEN DESIGN

    第6章 领域对象的生命周期

    6.1 模式:AGGREGATE

    6.2 模式:FACTORY

    6.2.1 选择FACTORY及其应用位置

    6.2.2 有些情况下只需使用构造函数

    6.2.3 接口的设计

    6.2.4 固定规则的相关逻辑应放置在哪里

    6.2.5 ENTITY FACTORY与VALUE OBJECT FACTORY

    6.2.6 重建已存储的对象

    6.3 模式:REPOSITORY

    6.3.1 REPOSITORY的查询

    6.3.2 客户代码可以忽略REPOSITORY的实现,但开

    发人员不能忽略

    6.3.3 REPOSITORY的实现

    6.3.4 在框架内工作

    6.3.5 REPOSITORY与FACTORY的关系

    6.4 为关系数据库设计对象

    第7章 使用语言:一个扩展的示例

    7.1 货物运输系统简介

    7.2 隔离领域:引入应用层

    7.3 将ENTITY和VALUE OBJECT区别开

    7.4 设计运输领域中的关联

    7.5 AGGREGATE边界

    7.6 选择REPOSITORY

    7.7 场景走查

    7.7.1 应用程序特性举例:更改Cargo的目的地7.7.2 应用程序特性举例:重复业务

    7.8 对象的创建

    7.8.1 Cargo的FACTORY和构造函数

    7.8.2 添加Handling Event

    7.9 停一下,重构:Cargo AGGREGATE的另一种设计

    7.10 运输模型中的MODULE

    7.11 引入新特性:配额检查

    7.11.1 连接两个系统

    7.11.2 进一步完善模型:划分业务

    7.11.3 性能优化

    7.12 小结

    第三部分 通过重构来加深理解

    第8章 突破

    8.1 一个关于突破的故事

    8.1.1 华而不实的模型

    8.1.2 突破

    8.1.3 更深层模型

    8.1.4 冷静决策

    8.1.5 成果

    8.2 机遇

    8.3 关注根本

    8.4 后记:越来越多的新理解

    第9章 将隐式概念转变为显式概念

    9.1 概念挖掘

    9.1.1 倾听语言

    9.1.2 检查不足之处

    9.1.3 思考矛盾之处

    9.1.4 查阅书籍9.1.5 尝试,再尝试

    9.2 如何为那些不太明显的概念建模

    9.2.1 显式的约束

    9.2.2 将过程建模为领域对象

    9.2.3 模式:SPECIFICATION

    9.2.4 SPECIFICATION的应用和实现

    第10章 柔性设计

    10.1 模式:INTENTION-REVEALING INTERFACES

    10.2 模式:SIDE-EFFECT-FREE FUNCTION

    10.3 模式:ASSERTION

    10.4 模式:CONCEPTUAL CONTOUR

    10.5 模式:STANDALONE CLASS

    10.6 模式:CLOSURE OF OPERATION

    10.7 声明式设计

    10.8 声明式设计风格

    10.9 切入问题的角度

    10.9.1 分割子领域

    10.9.2 尽可能利用已有的形式

    第11章 应用分析模式

    第12章 将设计模式应用于模型

    12.1 模式:STRATEGY(也称为POLICY)

    12.2 模式:COMPOSITE

    12.3 为什么没有介绍FLYWEIGHT

    第13章 通过重构得到更深层的理解

    13.1 开始重构

    13.2 探索团队

    13.3 借鉴先前的经验

    13.4 针对开发人员的设计13.5 重构的时机

    13.6 危机就是机遇

    第四部分 战略设计

    第14章 保持模型的完整性

    14.1 模式:BOUNDED CONTEXT

    14.2 模式:CONTINUOUS INTEGRATION

    14.3 模式:CONTEXT MAP

    14.3.1 测试CONTEXT的边界

    14.3.2 CONTEXT MAP的组织和文档化

    14.4 BOUNDED CONTEXT之间的关系

    14.5 模式:SHARED KERNEL

    14.6 模式:CUSTOMERSUPPLIER DEVELOPMENT TEAM

    14.7 模式:CONFORMIST

    14.8 模式:ANTICORRUPTION LAYER

    14.8.1 设计ANTICORRUPTION LAYER的接口

    14.8.2 实现ANTICORRUPTION LAYER

    14.8.3 一个关于防御的故事

    14.9 模式:SEPARATE WAY

    14.10 模式:OPEN HOST SERVICE

    14.11 模式:PUBLISHED LANGUAGE

    14.12 “大象”的统一

    14.13 选择你的模型上下文策略

    14.13.1 团队决策或更高层决策

    14.13.2 臵身上下文中

    14.13.3 转换边界

    14.13.4 接受那些我们无法更改的事物:描述外部

    系统

    14.13.5 与外部系统的关系14.13.6 设计中的系统

    14.13.7 用不同模型满足特殊需要

    14.13.8 部署

    14.13.9 权衡

    14.13.10 当项目正在进行时

    14.14 转换

    14.14.1 合并CONTEXT:SEPARATE WAY →SHARED

    KERNEL

    14.14.2 合 并 CONTEXT : SHARED

    KERNEL→CONTINUOUS INTEGRATION

    14.14.3 逐步淘汰遗留系统

    14.14.4 OPEN HOST SERVICE→PUBLISHED LANGUAGE

    第15章 精炼

    15.1 模式:CORE DOMAIN

    15.1.1 选择核心

    15.1.2 工作的分配

    15.2 精炼的逐步提升

    15.3 模式:GENERIC SUBDOMAIN

    15.3.1 通用不等于可重用

    15.3.2 项目风险管理

    15.4 模式:DOMAIN VISION STATEMENT

    15.5 模式:HIGHLIGHTED CORE

    15.5.1 精炼文档

    15.5.2 标明CORE

    15.5.3 把精炼文档作为过程工具

    15.6 模式:COHESIVE MECHANISM

    15.6.1 GENERIC SUBDOMAIN与COHESIVE MECHANISM

    的比较15.6.2 MECHANISM是CORE DOMAIN一部分

    15.7 通过精炼得到声明式风格

    15.8 模式:SEGREGATED CORE

    15.8.1 创建SEGREGATED CORE的代价

    15.8.2 不断发展演变的团队决策

    15.9 模式:ABSTRACT CORE

    15.10 深层模型精炼

    15.11 选择重构目标

    第16章 大型结构

    16.1 模式:EVOLVING ORDER

    16.2 模式:SYSTEM METAPHOR

    16.3 模式:RESPONSIBILITY LAYER

    16.4 模式:KNOWLEDGE LEVEL

    16.5 模式:PLUGGABLE COMPONENT FRAMEWORK

    16.6 结构应该有一种什么样的约束

    16.7 通过重构得到更适当的结构

    16.7.1 最小化

    16.7.2 沟通和自律

    16.7.3 通过重构得到柔性设计

    16.7.4 通过精炼可以减轻负担

    第17章 领域驱动设计的综合运用

    17.1 把大型结构与BOUNDED CONTEXT结合起来使用

    17.2 将大型结构与精炼结合起来使用

    17.3 首先评估

    17.4 由谁制定策略

    17.4.1 从应用程序开发自动得出的结构

    17.4.2 以客户为中心的架构团队

    17.5 制定战略设计决策的6个要点17.5.1 技术框架同样如此

    17.5.2 注意总体规划

    结束语

    附录

    术语表

    参考文献

    图片说明

    索引其他DOMAIN-DRIVEN DESIGN TACKLING COMPLEXITY IN THE HEART OF

    SOFTWARE

    领域驱动设计 软件核心复杂性应对之道

    [美]Eric Evans◎著

    赵俐 盛海艳 刘霞 等◎译

    任发科◎审校

    人民邮电出版社

    北京图书在版编目(CIP)数据

    领域驱动设计:软件核心复杂性应对之道(美)埃文斯

    (Evans,E.)著;赵俐等译.--2版(修订本).--北京:人民邮电出版

    社,2016.6

    书名原文:Domain-Driven Design:Tackling Complexity in the

    Heart of Software

    ISBN 978-7-115-37675-6

    Ⅰ.①领… Ⅱ.①埃…②赵… Ⅲ.①软件设计 Ⅳ.①TP311.5

    中国版本图书馆CIP数据核字(2016)第069725号

    内容提要

    本书是领域驱动设计方面的经典之作,修订版更是对之前出版的

    中文版进行了全面的修订和完善。

    全书围绕着设计和开发实践,结合若干真实的项目案例,向读者

    阐述如何在真实的软件开发中应用领域驱动设计。书中给出了领域驱

    动设计的系统化方法,并将人们普遍接受的一些最佳实践综合到一

    起,融入了作者的见解和经验,展现了一些可扩展的设计最佳实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。

    本书适合各层次的面向对象软件开发人员、系统分析员阅读。

    ◆著 [美]Eric Evans

    译 赵俐 盛海艳 刘霞 等

    审校 任发科

    责任编辑 杨海玲

    责任印制 焦志炜

    ◆人民邮电出版社出版发行 北京市丰台区成寿寺路11号

    邮编 100164 电子邮件 315@ptpress.com.cn

    网址 http:www.ptpress.com.cn

    三河市海波印务有限公司印刷◆开本:800×1000 116

    印张:24.25

    字数:515千字 2016年6月第2版

    印数:1-3000册 2016年6月河北第1次印刷

    著作权合同登记号 图字:01-2010-0695号

    定价:69.00

    读者服务热线:(010)81055410 印装质量热线:(010)81055316

    反盗版热线:(010)81055315版权声明

    Authorized translation from the English language

    edition,entitled Domain-Driven Design:Tackling Complexity in

    the Heart of Software,9780321125217 by Eric Evans,published

    by Pearson Education,Inc.,publishing as Addison-

    Wesley,Copyright ? 2004 by Eric Evans.

    All rights reserved.No part of this book may be

    reproduced or transmitted in any form or by any

    means,electronic or mechanical,including

    photocopying,recording or by any information storage

    retrieval system,without permission from Pearson

    Education,Inc.

    CHINESE SIMPLIFIED language edition published by PEARSON

    EDUCATION ASIA LTD.and POSTS TELECOM PRESS Copyright ?

    2016.

    本书中文简体字版由 Pearson Education Asia Ltd.授权人民邮

    电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄

    袭本书内容。

    本书封面贴有 Pearson Education(培生教育出版集团)激光防

    伪标签,无标签者不得销售。

    版权所有,侵权必究。译者序

    我最早听说Eric Evans的《领域驱动设计》是在2007年,那时我

    所在的项目组出于知识储备的考虑购进了一批软件设计书和相关资

    料。其中一篇英文的短篇技术文档与我们当时的项目非常相关,于是

    我们就仔细研读了一番。这篇仅有几万字的文档多次提到了Eric

    Evans的《领域驱动设计》,并引用了他的很多精辟观点。由于当时领

    域驱动设计远远没有现在这样普及,因此这些观点使我耳目一新,也

    给我留下了深刻的印象。随后我又经常在一些文献中看到Eric Evans

    的名字,更多地了解了他的领域驱动设计思想,没想到时隔几年后竟

    然有机会把这位大师的作品翻译出来奉献给各位读者,也算是机缘巧

    合了。

    相信大家对这本书都不陌生,它已经成为软件设计书中的经典。

    在网上搜索一下,读者对它好评如潮,我再多说一句赞美的话都是多

    余的。而我能想到的也唯有“经典”二字,它堪称经典中的经典。

    我们对“领域”这个概念都很熟悉,但有多少人真正重视过它

    呢?软件开发人员几乎总是专注于技术,把技术作为自己能力的展示

    和成功的度量。而直到Eric Evans出版了他的这部巨著之后,人们才

    真正开始关注领域,关注核心领域,关注领域驱动的设计,关注模型

    驱动的开发。相信在读完本书后,你会对软件设计有全新的认识。

    我曾经和一些好友探讨过以下一些问题。项目怎样开发才能确保

    成功?什么样的软件才能为用户提供真正的价值?什么样的团队才算是优秀的团队?现在,在仔细研读完本书后,这些问题都找到了答

    案。

    本书广泛适用于各种领域的软件开发项目。在每个项目的生命周

    期中,都会有一些重大关头或转折点。如何制定决策,如何把握项目

    的方向,如何处理和面对各种机会和挑战,将对项目产生决定性的影

    响。让我们一起跟随大师的脚步,分享他通过大量项目获得的真知灼

    见和开发心得吧。

    最后,衷心感谢人民邮电出版社各位编辑在翻译工作中给予的帮

    助和宝贵意见,感谢热心读者魏海枫,他在百忙之中抽出时间对本书

    译稿做了修订工作,发现并修正了很多问题。由于译者水平有限,在

    翻译过程中难免还会留有一些错误,恳请读者批评指正。序

    有很多因素会使软件开发复杂化,但最根本的原因是问题领域本

    身错综复杂。如果你要为一家人员复杂的企业提高自动化程度,那么

    你开发的软件将无法回避这种复杂性,你所能做的只有控制这种复杂

    性。

    控制复杂性的关键是有一个好的领域模型,这个模型不应该仅仅

    停留在领域的表面,而是要透过表象抓住领域的实质结构,从而为软

    件开发人员提供他们所需的支持。好的领域模型价值连城,但要想开

    发出好的模型也并非易事。精通此道的人并不多,而且这方面的知识

    也很难传授。

    Eric Evans就是为数不多的能够创建出优秀领域模型的人。我是

    在与他合作时发现他的这种才能的——发现一个客户竟然比我技术更

    精湛,这种感觉有些奇妙。我们的合作虽然短暂,但却充满乐趣。从

    那之后我们一直保持联系,我也有幸见证了本书整个“孕育”过程。

    本书绝对值得期待。

    本书终于实现了一个宏伟抱负,即描述并建立了领域建模艺术的

    词汇库。它提供了一个参考框架,人们可以用它来解释相关活动,并

    用它来传授这门难学的技艺。本书在写作过程中,也带给我很多新想

    法,如果哪位概念建模方面的老手没有从阅读本书中获得大量的新思

    想,那我反而该惊诧莫名了。

    Eric还对我们多年以来学过的知识进行了归纳总结。首先,在领

    域建模过程中不应将概念与实现割裂开来。高效的领域建模人员不仅应该能够在白板上与会计师进行讨论,而且还应该能与程序员一道编

    写Java代码。之所以要具备这些能力,一部分原因是如果不考虑实现

    问题就无法构建出有用的概念模型。但概念与实现密不可分的最主要

    原因在于,领域模型的最大价值是它提供了一种通用语言,这种语言

    是将领域专家和技术人员联系在一起的纽带。

    我们将从本书中学到的另一个经验是领域模型并不是按照“先建

    模,后实现”这个次序来工作的。像很多人一样,我也反对“先设

    计,再构建”这种固定的思维模式。Eric的经验告诉我们,真正强大

    的领域模型是随着时间演进的,即使是最有经验的建模人员也往往发

    现他们是在系统的初始版本完成之后才有了最好的想法。

    我衷心希望本书成为一本有影响力的著作,并希望本书能够将如

    何利用领域模型这一宝贵工具的知识传授给更多的人,从而为这个高

    深莫测的领域梳理出一个结构,并使它更有内聚力。领域模型对软件

    开发的控制有着巨大影响,不管软件开发是用什么语言或环境实现

    的。

    最后,也是很重要的一点,我最敬佩Eric的一点是他敢于在本书

    中谈论自己的一些失败经历。很多作者都喜欢摆出一副无所不能的架

    势,有时着实让人不屑。但Eric清楚地表明他像我们大多数人一样,既品尝过成功的美酒,也体验过失败的沮丧。重要的是他能够从成功

    和失败中学习,而对我们来说更重要的是他能够将所有经验传授给我

    们。

    Martin Fowler

    2003年4月前言

    至少20年前,一些顶尖的软件设计人员就已经认识到领域建模和

    设计的重要性,但令人惊讶的是,这么长时间以来几乎没有人写出点

    儿什么,告诉大家应该做哪些工作或如何去做。尽管这些工作还没有

    被清楚地表述出来,但一种新的思潮已经形成,它像一股暗流一样在

    对象社区中涌动,我把这种思潮称为领域驱动设计(domain-driven

    design)。

    过去10年中,我在几个业务和技术领域开发了一些复杂的系统。

    我在设计和开发过程中尝试了一些最佳实践,它们都是面向对象开发

    高手用过的领先技术。有些项目非常成功,但有几个项目却失败了。

    成功的项目有一个共同的特征,那就是都有一个丰富的领域模型,这

    个模型在迭代设计的过程中不断演变,而且成为项目不可分割的一部

    分。

    本书为作出设计决策提供了一个框架,并且为讨论领域设计提供

    了一个技术词汇库。本书将人们普遍接受的一些最佳实践综合到一

    起,并融入了我自己的见解和经验。面对复杂领域的软件开发团队可

    以利用这个框架来系统性地应用领域驱动设计。

    三个项目的对比

    谈到领域设计实践对开发结果的巨大影响时,我的记忆中立即就

    会跳出三个项目,它们就是鲜活的例子。虽然这三个项目都交付了有

    用的软件,但只有一个项目实现了宏伟的目标——交付了能够满足组

    织后续需求、可以不断演进的复杂软件。我要说的第一个项目完成得很迅速,它提供了一个简单实用的Web

    交易系统。开发人员主要凭直觉开发,但这并没有妨碍他们,因为简

    单软件的编写并不需要过多地注意设计。由于最初的这次成功,人们

    对未来开发的期望值变得极高。我就是在这个时候被邀请开发它的第

    二个版本的。当我仔细研究这个项目时,发现他们没有使用领域模

    型,甚至在项目中没有一种公共语言,而且项目完全没有一种结构化

    的设计。项目领导者对我的评价并不赞同,于是我拒绝了这项工作。

    一年后,这个项目团队陷入困境,无法交付第二个版本。尽管他们在

    技术的使用方面也值得商榷,但真正挫败他们的是业务逻辑。他们的

    第一个版本过早地变得僵化,成为一个维护代价十分高昂的遗留系

    统。

    要想克服这种复杂性,需要非常严格地使用领域逻辑设计方法。

    在我职业生涯的早期,我幸运地完成了一个非常重视领域设计的项

    目,这就是我要说的第二个项目。这个项目的领域复杂性与上面提到

    的那个项目相仿,它最初也小获成功,为贸易机构提供了一个简单的

    应用程序。但在最初交付之后紧跟着又进行了连续的加速开发。每次

    迭代都为上一个版本在功能的集成和完善上增加了非常好的新选项。

    开发团队能够按照贸易商的要求提供灵活性和扩展性。这种良性发展

    直接归功于深刻的领域模型,它得到了反复精化,并在代码中得以体

    现。当团队对该领域有了新的理解后,领域模型也随之深化。开发人

    员之间、开发人员与领域专家之间的沟通质量都得到改善,而且设计

    不但没有加重维护负担,反而变得易于修改和扩展。

    遗憾的是,仅靠重视模型并不会使项目达到这样的良性循环。我

    要说的第三个项目就是这种情况,它开始制订的目标很高,打算基于

    一个领域模型建立一个全球企业系统,但在经过了几年的屡战屡败之

    后,不得不降格以求,最终“泯然众人矣”。团队拥有很好的工具,对业务也有较好的理解,也非常认真地进行了建模。但团队却错误地将开发人员的角色独立出来,导致建模与实现脱节,因此设计无法反

    映不断深化的分析。总之,详细的业务对象设计不能保证它们能够严

    丝合缝地被整合到复杂的应用程序中。反复的迭代并没有使代码得以

    改进,因为开发人员的技术水平参差不齐,他们没有认识到他们使用

    了非正式的风格和技术体系来创建基于模型的对象(这些对象也充当

    了实用的、可运行的软件)。几个月过去了,开发工作由于巨大的复

    杂性而陷入困境,而团队对项目也失去了一致的认识。经过几年的努

    力,项目确实创建了一个适当的、有用的软件,但团队已经放弃了当

    初的宏伟抱负,也不再重视模型。

    复杂性的挑战

    很多因素可能会导致项目偏离轨道,如官僚主义、目标不清、资

    源缺乏等。但真正决定软件复杂性的是设计方法。当复杂性失去控制

    时,开发人员就无法很好地理解软件,因此无法轻易、安全地更改和

    扩展它。而好的设计则可以为开发复杂特性创造更多机会。

    一些设计因素是技术上的。软件的网络、数据库和其他技术方面

    的设计耗费了人们大量的精力。很多书籍都介绍过如何解决这些问

    题。大批开发人员很注意培养自己的技能,并紧跟每一次技术进步。

    然而很多应用程序最主要的复杂性并不在技术上,而是来自领域

    本身、用户的活动或业务。当这种领域复杂性在设计中没有得到解决

    时,基础技术的构思再好也无济于事。成功的设计必须系统地考虑软

    件的这个核心方面。

    本书有两个前提:

    (1)在大多数软件项目中,主要的焦点应该是领域和领域逻辑;

    (2)复杂的领域设计应该基于模型。

    领域驱动设计是一种思维方式,也是一组优先任务,它旨在加速

    那些必须处理复杂领域的软件项目的开发。为了实现这个目标,本书

    给出了一整套完整的设计实践、技术和原则。设计过程与开发过程

    设计书就是讲设计,过程书只是讲过程。它们之间很少互相参

    考。设计和过程本身就是两个足够复杂的主题。本书是一本设计书,但我相信设计与过程这二者是密不可分的。设计思想必须被成功实

    现,否则它们就只是纸上谈兵。

    当人们学习设计技术时,各种可能性令他们兴奋不已,然而真实

    项目的错综复杂又会为他们泼上一盆冷水。他们无法用所使用的技术

    来贯彻新的设计思想,或者不知道何时应该为了节省时间而放弃某个

    设计方面,何时又应该坚持不懈直至找到一个干净利落的解决方案。

    开发人员可以抽象地讨论设计原则的应用,而且他们也确实在进行着

    这样的讨论,但更自然的做法应该是讨论如何完成实际工作。因此,虽然本书是一本有关设计的书,但我会在必要的时候穿越这条人为设

    臵的边界,进入过程的领域。这有助于将设计原则放到一个适当的语

    境下进行讨论。

    虽然本书并不局限于某一种特定的方法,但主要还是面向“敏捷

    开发过程”这一新体系。特别地,本书假定项目必须遵循两个开发实

    践,要想应用书中所讲的方法,必须先了解这两个实践。

    (1)迭代开发。人们倡导和实践迭代开发已经有几十年时间了,而且它是敏捷开发方法的基础。在敏捷开发和极限编程(XP)的文献

    中有很多关于迭代开发的精彩讨论,其中包括Surviving Object-

    Oriented Projects[Cockburn 1998] [1] 和 Extreme Programming

    Explained[Beck 1999]。

    (2)开发人员与领域专家具有密切的关系。领域驱动设计的实质

    就是消化吸收大量知识,最后产生一个反映深层次领域知识并聚焦于

    关键概念的模型。这是领域专家与开发人员的协作过程,领域专家精

    通领域知识,而开发人员知道如何构建软件。由于开发过程是迭代式

    的,因此这种协作必须贯穿整个项目的生命周期。极限编程的概念是由Kent Beck、Ward Cunningham和其他人共同

    提出的[Beck 2000],它是敏捷过程最重要的部分,也是我使用得最多

    的一种编程方法。为了使讨论更加具体,整本书都将使用XP作为基础

    讨论设计和过程的交互。本书论述的原则很容易应用于其他敏捷过

    程。

    近年来,反对“精细开发方法学”(elaborate development

    methodology)的呼声渐起,人们认为无用的静态文档以及死板的预先

    规划和设计加重了项目的负担。相反,敏捷过程(如XP)强调的是应

    对变更和不确定性的能力。

    极限编程承认设计决策的重要性,但强烈反对预先设计。相反,它将相当大的精力投入到促进沟通和提高项目快速变更能力的工作

    中。具有这种反应能力之后,开发人员就可以在项目的任何阶段只利

    用“最简单而管用的方案”,然后不断进行重构,一步一步做出小的

    设计改进,最终得到满足客户真正需要的设计。

    这种极端的简约主义是解救那些过度追求设计的执迷者的良方。

    那些几乎没有价值的繁琐文档只会为项目带来麻烦。项目受到“分析

    瘫痪症”的困扰,团队成员十分担心会出现不完美的设计,这导致他

    们根本没法取得进展。这种状况必须得到改变。

    遗憾的是,这些有关过程的思想可能会被误解。每个人对“最简

    单”都有不同的定义。持续重构其实是一系列小规模的重新设计,没

    有严格设计原则的开发人员将会创建出难以理解或修改的代码,这恰

    好与敏捷的精神相悖。而且,虽然对意外需求的担心常常导致过度设

    计,但试图避免过度设计又可能走向另一个极端——不敢做任何深入

    的设计思考。

    实际上,XP最适合那些对设计的感觉很敏锐的开发人员。XP过程

    假定人们可以通过重构来改进设计,而且可以经常、快速地完成重

    构。但重构本身的难易程度取决于先前的设计选择。XP过程试图改善团队沟通,但模型和设计的选择有可能使沟通更明确,也有可能会使

    沟通不畅。

    本书将设计和开发实践结合起来讨论,并阐述领域驱动设计与敏

    捷开发过程是如何互相增强的。在敏捷开发过程中使用成熟的领域建

    模方法可以加速开发。过程与领域开发之间的相互关系使得这种方法

    比任何“纯粹”真空式的设计更加实用。

    本书的结构

    本书分为4个部分。

    第一部分“运用领域模型”提出领域驱动开发的基本目标,这些

    目标是后面几部分中所讨论的实践的驱动因素。由于软件开发方法有

    很多,因此第一部分还定义了一些术语,并给出了用领域模型来驱动

    沟通和设计的总体含义。

    第二部分“模型驱动设计的构造块”将面向对象领域建模中的一

    些核心的最佳实践提炼为一组基本的构造块。这一部分主要是消除模

    型与实际运行的软件之间的鸿沟。团队一致使用这些标准模式就可以

    使设计井然有序,并且使团队成员更容易理解彼此的工作。使用标准

    模式还可以为公共语言贡献术语,使得所有团队成员可以使用这些术

    语来讨论模型和设计决策。

    但这一部分的主旨是讨论一些能够保持模型和实现之间互相协调

    并提高效率的设计决策。要想达到这种协调,需要密切注意个别元素

    的一些细节。这种小规模的仔细设计为开发人员提供了一个稳固的基

    础,在此基础上就可以应用第三部分和第四部分讨论的建模方法了。

    第三部分“通过重构来加深理解”讨论如何将构造块装配为实用

    的模型,从而实现其价值。这一部分没有直接讨论深奥的设计原则,而是着重强调一个发现过程。有价值的模型不是立即就会出现的,它

    们需要对领域的深入理解。这种理解是一步一步得到的,首先需要深

    入研究模型,然后基于最初的(可能是不成熟的)模型实现一个初始设计,再反复改进这个设计。每次团队对领域有了新的理解之后,都

    需要对模型进行改进,使模型反映出更丰富的知识,而且必须对代码

    进行重构,以便反映出更深刻的模型,并使应用程序可以充分利用模

    型的潜力。这种一层一层“剥洋葱”的方法有时会创造一种突破的机

    会,使我们得到更深刻的模型,同时快速进行一些更深入的设计修

    改。

    探索本身是永无止境的,但这并不意味着它是随机的。第三部分

    深入阐述一些指引我们保持正确方向的建模原则,并提供了一些指导

    我们进行探索的方法。

    第四部分“战略设计”讨论在复杂系统、大型组织以及与外部系

    统和遗留系统的交互中出现的复杂情况。这一部分探讨了作为一个整

    体应用于系统的3条原则:上下文、提炼和大型结构。战略设计决策通

    常由团队制定,或者由多个团队共同制定。战略设计可以保证在大型

    系统或应用程序(它们应用于不断延伸的企业级网络)上以较大规模

    去实现第一部分提出的目标。

    本书通篇讨论使用的例子并不是一些过于简单的“玩具式”问

    题,而是全部选自实际项目。

    本书的大部分内容实际上是作为一系列的“模式”编写的。但读

    者无需顾忌这一方法也应该能够理解本书,对模式的风格和格式感兴

    趣的读者可以参考附录。

    补充材料可以参考http:domaindrivendesign.org,该网站提供

    了示例代码和社区讨论内容。

    本书面向的读者

    本书主要是为面向对象软件开发人员编写的。软件项目团队的大

    部分成员都能够从本书的某些部分获益。本书最适合那些正在项目上

    尝试这些实践的人员,以及那些已经在这样的项目上积累了丰富经验

    的人员。要想从本书受益,掌握一些面向对象建模知识是非常必要的,如

    UML图和Java代码,因此一定要具备基本读懂这些语言的能力,但不必

    精通细节。了解极限编程的知识有助于从这个角度来理解开发过程的

    讨论,但不具备这一背景知识也能读懂这些内容。

    一些中级软件开发人员可能已经了解面向对象设计的一些知识,也许读过一两本软件设计的书,那么本书将填补这些读者的知识空

    缺,向他们展示如何在实际的软件项目上应用对象建模技术。本书将

    帮助这些开发人员学会用高级建模和设计技巧来解决实际问题。

    高级软件开发人员或专家可能会对书中用于处理领域的综合框架

    感兴趣。这种系统性的设计方法将帮助技术负责人指导他们的团队保

    持正确的方向。此外,本书从头至尾所使用的明确术语将有助于高级

    开发人员与他们的同行沟通。

    本书采用记叙体,读者可以从头至尾阅读,也可以从任意一章的

    开头开始阅读。具有不同背景知识的读者可能会有不同的阅读方式,但我推荐所有读者从第一部分的引言和第1章开始阅读。除此之外,本

    书的核心是第2、3、9和14章。已经掌握一定知识的读者可以采取跳跃

    式阅读的方式,通过阅读标题和粗体字内容即可掌握要点。一些高级

    读者则可以跳过前两部分,重点阅读第三部分和第四部分。

    除了这些主要读者以外,分析员和相关的技术项目经理也可以从

    阅读本书中获益。分析员在掌握了领域与设计之间的联系之后,能够

    在敏捷项目中作出更卓越的贡献,也可以利用一些战略设计原则来更

    有重点地组织工作。

    项目经理感兴趣的重点是提高团队的工作效率,并致力于设计出

    对业务专家和用户有用的软件。由于战略设计决策与团队组织和工作

    风格紧密相关,因此这些设计决策必然需要项目领导者的参与,而且

    对项目的路线有着重要的影响。

    领域驱动团队尽管开发人员个人能够从理解领域驱动设计中学到有价值的设计

    技术和观点,但最大的好处却来自团队共同应用领域驱动设计方法,并且将领域模型作为项目沟通的核心。这样,团队成员就有了一种公

    共语言,可以用来进行更充分的沟通,并确保围绕软件来进行沟通。

    他们将创建出一个与模型步调一致的清晰的实现,从而为应用程序的

    开发提供帮助。所有人都了解不同团队的设计工作之间的互相联系,而且他们会一致将注意力集中在那些对组织最有价值、最与众不同的

    特性的开发上。

    领域驱动设计是一项艰巨的技术挑战,但它也会带来丰厚的回

    报,当大多数软件项目开始僵化而成为遗留系统时,它却为你敞开了

    机会的大门。

    [1].这种表述指这是本书参考文献中提到的图书。——编者注致谢

    本书的创作历时4年多,其间经历了诸多工作形式的变化,在这个

    过程中很多人为我提供了帮助和支持。

    感谢那些阅读本书书稿并提出意见的人。没有这些人的反馈意

    见,本书将不可能出版。其中有几个团队和一些人员对本书的评阅给

    予了特别的关注。由Russ Rufer和Tracy Bialek领导的硅谷模式小组

    (Silicon Valley Patterns Group)花费了几周时间详细审阅了本书

    完整的第一稿。由Ralph Johnson领导的伊利诺伊大学的阅读小组也花

    费了几周时间审阅了本书的第二稿。这些小组长期、精彩的讨论对本

    书产生了深远的影响。Kyle Brown和Martin Fowler提供了细致入微的

    反馈意见和宝贵的建议,也给了我无价的精神支持(在我们坐在一起

    钓鱼的时候)。Ward Cunningham的意见帮助我弥补了一些重大的缺

    陷。Alistair Cockburn在早期给了我很多鼓励,并和Hilary Evans一

    起帮助我完成了整个出版过程。David Siegel和Eugene Wallingford

    帮助我避免了很多技术上的错误。VibhuMohindra和Vladimir

    Gitlevich不厌其烦地检查了所有代码示例。

    Rob Mee看了我对一些素材所做的早期研究,并在我尝试表达这种

    设计风格的时候与我进行了头脑风暴活动,帮我产生了很多新的想

    法。他后来又与我一起仔细探讨了后面的书稿。

    本书在写作过程中经历了一次重大转折,这完全归功于

    JoshKerievsky。他劝说我在写作本书时借鉴“亚历山大”模式 [1]

    ,后来本书正是按这种方式组织的。在1999年PLoP会议临近时的忙碌时刻,Josh还帮我收集第二部分的材料,首次将它们组织为更严密的

    形式。这些材料成了一粒种子,本书大部分后续内容都是围绕这些内

    容创作的。

    还要感谢Awad Faddoul,我有数百个小时坐在他的咖啡厅中写

    作。咖啡厅宁静优雅,窗外的湖面上总有片片风帆,我正是这样才坚

    持写下去。

    此 外 还 要 感 谢 Martine Jousset 、 Richard Paselk 和 Ross

    Venables,他们拍摄了一些非常精美的照片,用来演示一些关键概念

    (参见本书后面的图片说明)。

    在构思本书之前,我必须先要形成我自己对软件开发的看法和理

    解。这个过程得到了一些杰出人员的无私帮助,他们是我的良师益

    友。David Siegel、Eric Gold和Iseult White各自从不同方面帮助我

    形 成 了 对 软 件 设 计 的 思 考 方 式 。 同 时 , Bruce Gordon 、RichardFreyberg和Judith Segal博士也从不同角度帮助我找到了项目

    的成功之路。

    我自己的观念就是从那时的思想体系中自然而然发展形成的。有

    些内容我在正文中清楚地列了出来,并且在可能的地方标明了出处。

    还有些可能是十分基础的知识,我甚至自己都没有意识到它们对我产

    生了影响。

    我的硕士论文导师Bala Subramanium博士是我在数学建模方面的

    引路人,当时我们用数学建模来进行化学反应动力学方面的研究。虽

    说建模本身没什么稀奇,但那时的工作是引导我创作本书的一部分原

    因。

    在更早之前,我的母亲Carol和父亲Gary对我思维模式的形成产生

    了很大影响。还有几位特别值得一提的教师激发了我的兴趣,帮助我

    打下坚实的基础,在此感谢Dale Currier(我的高中数学老师)、Mary Brown(我的高中英文写作老师)和Josephine McGlamery(我上

    6年级时的自然科学老师)。

    最后,感谢我的朋友和家人,以及Fernando De Leon,感谢他们

    一直以来给我的鼓励。

    [1].克里斯托弗〃亚历山大(Christopher Alexander),1936年10月

    4日出生于奥地利的维也纳,是一名建筑师,以其设计理论和丰富的建

    筑设计作品而闻名于世。亚历山大认为,建筑的使用者比建筑师更清

    楚他们需要什么,他创造并以实践验证了“模式语言”,建筑模式语

    言赋予所有人设计并建造建筑的能力。亚历山大的代表作是《建筑模

    式语言》,该书对计算机科学领域中的“设计模式”运动产生了巨大

    的影响。亚历山大创立的增量、有机和连贯的设计理念也影响了“极

    限编程”运动。——编者注第一部分 运用领域模型上面这张图是18世纪中国描绘的世界地图。图中央最大的部分是

    中国,其周围散布着其他国家,但这些国家只是草草地表示了一下。

    这是适用于当时中国社会的世界模型,它意在关注中国自身。然而,这幅地图所呈现的世界观对于处理外交事务并无助益。当然,它对现

    代中国也毫无用处。地图就是模型,而模型被用来描绘人们所关注的

    现实或想法的某个方面。模型是一种简化。它是对现实的解释——把

    与解决问题密切相关的方面抽象出来,而忽略无关的细节。

    每个软件程序是为了执行用户的某项活动,或是满足用户的某种

    需求。这些用户应用软件的问题区域就是软件的领域。一些领域涉及

    物质世界,例如,机票预订程序的领域中包括飞机乘客在内。有些领

    域则是无形的,例如,会计程序的金融领域。软件领域一般与计算机

    关系不大,当然也有例外,例如,源代码控制系统的领域就是软件开

    发本身。

    为了创建真正能为用户活动所用的软件,开发团队必须运用一整

    套与这些活动有关的知识体系。所需知识的广度可能令人望而生畏,庞大而复杂的信息也可能超乎想象。模型正是解决此类信息超载问题

    的工具。模型这种知识形式对知识进行了选择性的简化和有意的结构

    化。适当的模型可以使人理解信息的意义,并专注于问题。

    领域模型并非某种特殊的图,而是这种图所要传达的思想。它绝

    不单单是领域专家头脑中的知识,而是对这类知识严格的组织且有选

    择的抽象。图可以表示和传达一种模型,同样,精心书写的代码或文

    字也能达到同样的目的。

    领域建模并不是要尽可能建立一个符合“现实”的模型。即使是

    对具体、真实世界中的事物进行建模,所得到的模型也不过是对事物

    的一种模拟。它也不单单是为了实现某种目的而构造出来的软件机

    制。建模更像是制作电影——出于某种目的而概括地反映现实。即使

    是一部纪录片也不会原封不动地展现真实生活。就如同电影制片人讲述故事或阐明观点时,他们会选择素材,并以一种特殊方式将它们呈

    现给观众,领域建模人员也会依据模型的作用来选择具体的模型。

    模型在领域驱动设计中的作用

    在领域驱动的设计中,3个基本用途决定了模型的选择。

    (1) 模型和设计的核心互相影响。正是模型与实现之间的紧密联

    系才使模型变得有用,并确保我们在模型中所进行的分析能够转化为

    最终产品(即一个可运行的程序)。模型与实现之间的这种紧密结合

    在维护和后续开发期间也会很有用,因为我们可以基于对模型的理解

    来解释代码。(参见第3章)

    (2) 模型是团队所有成员使用的通用语言的中枢。由于模型与实

    现之间的关联,开发人员可以使用该语言来讨论程序。他们可以在无

    需翻译的情况下与领域专家进行沟通。而且,由于该语言是基于模型

    的,因此我们可借助自然语言对模型本身进行精化。(参见第2章)

    (3) 模型是浓缩的知识。模型是团队一致认同的领域知识的组织

    方式和重要元素的区分方式。透过我们如何选择术语、分解概念以及

    将概念联系起来,模型记录了我们看待领域的方式。当开发人员和领

    域专家在将信息组织为模型时,这一共同的语言(模型)能够促使他

    们高效地协作。模型与实现之间的紧密结合使来自软件早期版本的经

    验可以作为反馈应用到建模过程中。(参见第1章)

    接下来的3章分别考查上述3种基本用途的意义和价值,以及它们

    之间的关联方式。遵循这些原则使用模型可以很好地支持具有丰富功

    能的软件的开发,否则就需要耗费大规模投资进行专门开发。

    软件的核心

    软件的核心是其为用户解决领域相关的问题的能力。所有其他特

    性,不管有多么重要,都要服务于这个基本目的。当领域很复杂时,这是一项艰巨的任务,要求高水平技术人员的共同努力。开发人员必须钻研领域以获取业务知识。他们必须磨砺其建模技巧,并精通领域

    设计。

    然而,在大多数软件项目中,这些问题并未引起足够的重视。大

    部分有才能的开发人员对学习与他们的工作领域有关的知识不感兴

    趣,更不会下力气去扩展自己的领域建模技巧。技术人员喜欢那些能

    够提高其技能的可量化问题。领域工作很繁杂,而且要求掌握很多复

    杂的新知识,而这些新知识看似对提高计算机科学家的能力并无裨

    益。

    相反,技术人才更愿意从事精细的框架工作,试图用技术来解决

    领域问题。他们把学习领域知识和领域建模的工作留给别人去做。软

    件核心的复杂性需要我们直接去面对和解决,如果不这样做,则可能

    导致工作重点的偏离。

    在一次电视访谈节目中,喜剧演员John Cleese讲述了电影《巨蟒

    和圣杯》(Monty Python and the Holy Grail)在拍摄期间发生的一

    个小故事。有一幕他们反复拍了很多次,但就是感觉不够滑稽。最

    后,他停下来,与另一位喜剧演员Michael Palin(该幕中的另一位演

    员)商量了一下,他们决定稍微改变一下。随后又拍了一次,终于令

    他们满意了,于是收工。

    第二天早上,Cleese观看了剪辑人员为前一天工作所做的粗剪。

    到了那个令他们颇费周章的场景时,Cleese发现剪辑人员竟然使用了

    先前拍摄的一个镜头,影片到这里又变得不滑稽了。

    他问剪辑人员为什么没有按要求使用最后拍的那个镜头,剪辑人

    员回答说:“那个镜头不能用,因为有人闯入了镜头。”Cleese连看

    了两遍,仍未发现有什么不妥。最后,剪辑人员将影片暂停,并指出

    在屏幕边缘有一只一闪而过的大衣袖子。

    影片的剪辑人员专注于准确完成自己的工作。他担心其他看到这

    部电影的剪辑人员会给他挑错。在这个过程中,镜头的核心作用被忽略了(“The Late Late Show with Craig Kilborn”,CBS,2001年9

    月)。

    幸运的是,该剧的导演很懂喜剧,他最终使用了那个镜头。同

    样,在一个团队中,反映了对领域深层次理解的模型开发有时也会在

    混乱中迷失方向,此时,理解领域核心的领导者能够将软件项目带回

    到正确的轨道上来。

    本书将展示领域开发中蕴藏的巨大机会,它能够培养精湛的设计

    技巧。大多数混乱的软件领域其实是一项充满乐趣的技术挑战。事实

    上,在许多科学领域中,“复杂性”都是当前最热门的话题之一,因

    为研究人员都在想办法解决真实世界中的复杂性。软件开发人员在面

    对尚未规范的复杂领域时,也会有同样的期望。创建一个克服这些复

    杂性的易懂模型会带来巨大的成就感。

    开发人员可以采用一些系统性的思考方法来透彻地理解领域并开

    发出有效的模型。还有一些设计技巧可以使毫无头绪的软件应用变得

    井井有条。掌握这些技能可以令开发人员的价值倍增,即使是在一个

    最初不熟悉的领域中也是如此。

    第1章 消化知识

    几 年前,我着手设计一个用于设计印制电路板(PCB)的专用软

    件工具。但有一个问题,我对电子硬件一无所知。当然,我也曾拜访

    过一些PCB设计师,但用不了3分钟,他们就令我晕头转向。如何才能

    了解足够多的知识,以便开始编写这个软件呢?当然,我并不打算在

    交付期限到来之前成为电子工程师。我们试着让PCB设计师说明软件具体应该做些什么,但我们错了。

    虽然他们是优秀的电路设计师,但软件知识却太有限了,往往只知道

    如何读取一个ASCII文件、对它排序,然后添加一些注释并将它写回文

    件中,再生成一个报告。这些知识显然无法帮助他们大幅度提高效

    率。

    最初的几次会面令人气馁,但我们在他们要求的报告中也看到了

    一丝希望。这些报告中总是涉及net这个词以及与其相关的各种细节。

    在这个领域中,net实质上是一种导线,它可以连接PCB上任意数量的

    元件,并向它连接的所有元件传递电子信号。这样,我们就得到了领

    域模型的第一个元素,如图1-1所示。

    图1-1

    就这样,我们一边讨论所需的软件功能,一边开始画图。我使用

    一种非正式的、稍加变化的对象交互图来走查 [1] 各种场景,如图1-

    2所示。

    图1-2PCB专家1:元件不一定就是芯片(chip)。

    开发人员(我):那它们是不是只应该叫做“元件”?

    专家1:我们将它们称作“元件实例”(component instance)。

    相同的元件可能有很多。

    专家2:他把“net”画成和元件实例一样的框了。

    专家1:他没有使用我们的符号。我猜想,他要把每一项都画成方

    框。

    开发人员:很抱歉,是这样的。我想我最好对这个符号稍加解

    释。

    他们不断地纠正我的错误,在这个过程中我开始学习他们的知

    识。我们共同消除了术语上的不一致和歧义,也消除了他们在技术观

    点上的分歧,在这个过程中,他们也得到了学习。他们的解释更准确

    和一致了,然后我们开始共同开发一个模型。

    专家1:只说一个信号到达一个ref-des是不够明确的,我们必须

    知道信号到达了哪个引脚。

    开发人员:什么是ref-des?

    专家2:它就是一个元件实例。我们用的一个专门工具中用ref-

    des这个名称。

    专家1:总之,net将一个实例的某个引脚与另一个实例的某个引

    脚相连。

    开发人员:一个引脚是不是只属于一个元件实例,而且只与一个

    net相连?

    专家1:对,是这样。

    专家2:还有,每个net都有一个拓扑结构,也就是电路的布局,它决定了net内部各元件的连接方式。

    开发人员:嗯,这样画如何(如图1-3所示)?图1-3

    为了让讨论更集中,接下来的一段时间我们探讨了一个特定的功

    能:探针仿真(probe simulation)。探针仿真跟踪信号的传播,以

    便检测在设计中可能出现特定类型问题的位臵。

    开发人员:现在我已经明白了Net是如何将信号传播给它所连接的

    所有Pin的,但如何将信号传送得更远呢?这与拓扑结构(topology)

    有关系吗?

    专家2:没有,是元件推送信号前进。

    开发人员:我们肯定无法对芯片的内部行为建模,因为这太复杂

    了。

    专家2:我们不必这样做。可以使用一种简化形式。只需列出通过

    元件可从某些Pin将信号推送到其他引脚即可。

    开发人员:类似于这样吗?

    (经过反复的尝试和修改,我们终于共同绘制出了一个草图,如

    图1-4所示。)图1-4

    开发人员:但你想从这种计算中知道什么呢?

    专家2:我们要查找较长的信号延迟,也就是说,查找超过2或3跳

    的信号路径。这是一条经验法则。如果路径太长,信号可能无法在时

    钟周期内到达。

    开发人员:超过3跳……这么说我们需要计算路径长度。那么怎样

    算作一跳呢?

    专家2:信号每通过一个Net,就称为1跳。

    开发人员:那么我们可以沿着电路来计算跳数,每遇到一个net,跳数就加1,如图1-5所示。

    图1-5开发人员:现在我唯一不明白的地方是“推动”是从哪里来的。

    是否每个元件实例都需要存储该数据?

    专家2:一个元件的所有实例的推动行为都是相同的。

    开发人员:那么元件的类型决定了推动行为,而每个实例的推动

    行为都是相同的(如图1-6所示)?

    图1-6

    专家2:这个图的意思我没完全明白,但我猜想每个元件存储的推

    动行为就差不多是这样的吧。

    开发人员:抱歉,这个地方我可能问得有点过细了。我只是想考

    虑得全面一些……现在,拓扑结构对它有什么影响吗?

    专家1:拓扑结构不影响探针仿真。

    开发人员:那么可以暂不考虑它,是吗?等用到这些特性时再回

    来讨论它。

    就这样,我们的讨论一直进行下去(其中遇到的困难比上面显示

    的多得多)。我们一边进行“头脑风暴”式的讨论,一边对模型进行

    精化,边提问边回答。随着我对领域理解的加深,以及他们对模型在解决方案中作用的理解的加深,模型不断发展。图1-7显示了那个早期

    模型的类图。

    图1-7

    随后,我们又拿出一部分工作时间进行了几轮这样的讨论,我觉

    得自己已经理解了足够多的知识,可以试着编写一些代码了。我写了

    一个非常简单的原型,并用一个自动测试框架来测试它。我避开了所

    有的基础设施。这个原型没有持久化机制,也没有用户界面(UI)。

    这样我就可以专注于代码的行为。只不过几天我就能够演示简单的探

    针仿真了。虽然它使用的是虚拟数据,而且向控制台输出的是原始文

    本,但确实是使用Java对象对路径长度执行实际的计算。这些Java对

    象所反映的模型正是我和领域专家们一起开发出来的。

    这个具体的原型使得领域专家们更清楚地理解了模型的含义,以

    及它与最终软件之间的联系。从那时起,我们的模型讨论越来越具有

    互动性了,因为他们可以看到我如何将新学到的知识融合到模型中,然后反映到软件上。他们也可以从原型得到具体的反馈,从而印证自

    己的想法。

    模型中包含与我们要解决的问题有关的PCB领域知识,这些知识远

    远比我们在这里演示的复杂。模型将很多同义词和语言描写中的微小差别做了统一,并排除了数百条与问题没有直接关系的事实(虽然工

    程师们都理解这些事实),如元件的实际数字特性。像我这样的软件

    专业人员看到这张图后,几分钟内就能明白软件是做什么的。这个模

    型就相当于一个框架,开发人员可以借助它来组织新的信息并更快地

    学习,从而更准确地判断哪些部分重要,哪些部分不重要,并更好地

    与PCB工程师进行沟通。

    当PCB工程师提出新的功能需求时,我就让他们带我走查对象交互

    的场景。当模型对象无法清楚地表达某个重要场景时,我们就通过头

    脑风暴活动创建新的模型对象或者修改原有的模型对象,并消化理解

    这些模型对象中的知识。在我们精化模型的过程中,代码也随之一步

    步演进。几个月后,PCB工程师们得到了一个远远超乎他们期望的功能

    丰富的工具。

    1.1 有效建模的要素

    以下几方面因素促使上述案例得以成功。

    (1) 模型和实现的绑定。最初的原型虽然简陋,但它在模型与实

    现之间建立了早期链接,而且在所有后续的迭代中我们一直在维护该

    链接。

    (2) 建立了一种基于模型的语言。最初,工程师们不得不向我解

    释基本的PCB问题,而我也必须向他们解释类图的含义。但随着项目的

    进展,双方都能够直接使用模型中的术语,并将它们组织为符合模型

    结构的语句,而且无需翻译即可理解互相要表达的意思。

    (3) 开发一个蕴含丰富知识的模型。对象具有行为和强制性规

    则。模型并不仅仅是一种数据模式,它还是解决复杂问题不可或缺的

    部分。模型包含各种类型的知识。(4) 提炼模型。在模型日趋完整的过程中,重要的概念不断被添

    加到模型中,但同样重要的是,不再使用的或不重要的概念则从模型

    中被移除。当一个不需要的概念与一个需要的概念有关联时,则把重

    要的概念提取到一个新模型中,其他那些不要的概念就可以丢弃了。

    (5) 头脑风暴和实验。语言和草图,再加上头脑风暴活动,将我

    们的讨论变成“模型实验室”,在这些讨论中可以演示、尝试和判断

    上百种变化。当团队走查场景时,口头表达本身就可以作为所提议的

    模型的可行性测试,因为人们听到口头表达后,就能立即分辨出它是

    表达得清楚、简捷,还是表达得很笨拙。

    正是头脑风暴和大量实验的创造力才使我们找到了一个富含知识

    的模型并对它进行提炼,在这个过程中,基于模型的语言提供了很大

    帮助,而且贯穿整个实现过程中的反馈闭环也对模型起到了“训练”

    作用。这种知识消化将团队的知识转化为有价值的模型。

    1.2 知识消化

    金融分析师要消化理解的内容是数字。他们筛选大量的详细数

    字,对其进行组合和重组以便寻求潜在的意义,查找可以产生重要影

    响的简单表示方式——一种可用作金融决策基础的理解。

    高效的领域建模人员是知识的消化者。他们在大量信息中探寻有

    用的部分。他们不断尝试各种信息组织方式,努力寻找对大量信息有

    意义的简单视图。很多模型在尝试后被放弃或改造。只有找到一组适

    用于所有细节的抽象概念后,工作才算成功。这一精华严谨地表示了

    所发现的最为相关的知识。

    知识消化并非一项孤立的活动,它一般是在开发人员的领导下,由开发人员与领域专家组成的团队来共同协作。他们共同收集信息,并通过消化而将它组织为有用的形式。信息的原始资料来自领域专家头脑中的知识、现有系统的用户,以及技术团队以前在相关遗留系统

    或同领域的其他项目中积累的经验。信息的形式也多种多样,有可能

    是为项目编写的文档,有可能是业务中使用的文件,也有可能来自大

    量的讨论。早期版本或原型将经验反馈给团队,然后团队对一些解释

    做出修改。

    在传统的瀑布方法中,业务专家与分析员进行讨论,分析员消化

    理解这些知识后,对其进行抽象并将结果传递给程序员,再由程序员

    编写软件代码。由于这种方法完全没有反馈,因此总是失败。分析员

    全权负责创建模型,但他们创建的模型只是基于业务专家的意见。他

    们既没有向程序员学习的机会,也得不到早期软件版本的经验。知识

    只是朝一个方向流动,而且不会累积。

    有些项目使用了迭代过程,但由于没有对知识进行抽象而无法建

    立起知识体系。开发人员听专家们描述某项所需的特性,然后开始构

    建它。他们将结果展示给专家,并询问接下来做什么。如果程序员愿

    意进行重构,则能够保持软件足够整洁,以便继续扩展它;但如果程

    序员对领域不感兴趣,则他们只会了解程序应该执行的功能,而不去

    了解它背后的原理。虽然这样也能开发出可用的软件,但项目永远也

    不会从原有特性中自然地扩展出强大的新特性。

    好的程序员会自然而然地抽象并开发出一个可以完成更多工作的

    模型。但如果在建模时只是技术人员唱独角戏,而没有领域专家的协

    作,那么得到的概念将是很幼稚的。使用这些肤浅知识开发出来的软

    件只能做基本工作,而无法充分反映出领域专家的思考方式。

    在团队所有成员一起消化理解模型的过程中,他们之间的交互也

    会发生变化。领域模型的不断精化迫使开发人员学习重要的业务原

    理,而不是机械地进行功能开发。领域专家被迫提炼自己已知道的重

    要知识的过程往往也是完善其自身理解的过程,而且他们会渐渐理解

    软件项目所必需的概念严谨性。所有这些因素都促使团队成员成为更合格的知识消化者。他们对

    知识去粗取精。他们将模型重塑为更有用的形式。由于分析员和程序

    员将自己的知识输入到了模型中,因此模型的组织更严密,抽象也更

    为整洁,从而为实现提供了更大支持。同时,由于领域专家也将他们

    的知识输入到了模型中,因此模型反映了业务的深层次知识,而且真

    正的业务原则得以抽象。

    模型在不断改进的同时,也成为组织项目信息流的工具。模型聚

    焦于需求分析。它与编程和设计紧密交互。它通过良性循环加深团队

    成员对领域的理解,使他们更透彻地理解模型,并对其进一步精化。

    模型永远都不会是完美的,因为它是一个不断演化完善的过程。模型

    对理解领域必须是切实可用的。它们必须非常精确,以便使应用程序

    易于实现和理解。

    1.3 持续学习

    当开始编写软件时,其实我们所知甚少。项目知识零散地分散在

    很多人和文档中,其中夹杂着其他一些无关信息,因此我们甚至不知

    道哪些知识是真正需要的知识。看起来没什么技术难度的领域很可能

    是一种错觉——我们并没意识到不知道的东西究竟有多少。这种无知

    往往会导致我们做出错误的假设。

    同时,所有项目都会丢失知识。已经学到了一些知识的人可能干

    别的事去了。团队可能由于重组而被拆散,这导致知识又重新分散

    开。被外包出去的关键子系统可能只交回了代码,而不会将知识传递

    回来。而且当使用典型的设计方法时,代码和文档不会以一种有用的

    形式表示出这些来之不易的知识,因此一旦由于某种原因人们没有口

    头传递知识,那么知识就丢失了。高效率的团队需要有意识地积累知识,并持续学习[Kerievsky

    2003]。对于开发人员来说,这意味着既要完善技术知识,也要培养一

    般的领域建模技巧(如本书中所讲的那些技巧)。但这也包括认真学

    习他们正在从事的特定领域的知识。

    那些善于自学的团队成员会成为团队的中坚力量,涉及最关键领

    域的开发任务要靠他们来攻克(有关这方面的更多内容,参见第15

    章)。这个核心团队头脑中积累的知识使他们成为更高效的知识消化

    者。

    读到这里,请先停一下来问自己一个问题。你是否学到了一些PCB

    设计知识?虽然这个示例只对该领域作了些表面处理,但当讨论领域

    模型时,仍会学到一些知识。我学习了大量知识,但并没有学习如何

    成为一名PCB工程师,因为这不是我的目的。我的目的是学会与PCB专

    家沟通,理解与应用有关的主要概念,并学会检查所构建的内容是否

    合理。

    事实上,我们的团队最终发现探针仿真并不是一项重要的开发任

    务,因此最后彻底放弃了这个功能。连同它一起删除的还有模型中的

    一些部分,这些部分只是帮助我们理解如何通过元件推动信号以及如

    何计算跳数。这样,应用程序的核心就转移到了别处,而且模型也随

    之改变,将新的重点作为核心。在这个过程中,领域专家们也学到了

    很多东西,而且更加清楚地理解了应用程序的目标(第15章会更深入

    地讨论这些问题)。

    尽管如此,那些早期工作还是非常重要的。关键的模型元素被保

    留下来,而更重要的是,早期工作启动了知识消化的过程,这使得所

    有后续工作更加高效:团队成员、开发人员和领域专家等都学到了知

    识,他们开始使用一种公共的语言,而且形成了贯穿整个实现过程的

    反馈闭环。这样,一个发现之旅悄然开始了。1.4 知识丰富的设计

    通过像PCB示例这样的模型获得的知识远远不只是“发现名词”。

    业务活动和规则如同所涉及的实体一样,都是领域的核心,任何领域

    都有各种类别的概念。知识消化所产生的模型能够反映出对知识的深

    层理解。在模型发生改变的同时,开发人员对实现进行重构,以便反

    映出模型的变化,这样,新知识就被合并到应用程序中了。

    当我们的建模不再局限于寻找实体和值对象时,我们才能充分吸

    取知识,因为业务规则之间可能会存在不一致。领域专家在反复研究

    所有规则、解决规则之间的矛盾以及以常识来弥补规则的不足等一系

    列工作中,往往不会意识到他们的思考过程有多么复杂。软件是无法

    完成这一工作的。正是通过与软件专家紧密协作来消化知识的过程才

    使得规则得以澄清和充实,并消除规则之间的矛盾以及删除一些无用

    规则。

    示例 提取一个隐藏的概念

    我们从一个非常简单的领域模型开始学习,基于此模型的应用程

    序用来预订一艘船在一次航程中要运载的货物,如图1-8所示。

    图1-8

    我们规定这个应用程序的任务是将每件货物(Cargo)与一次航程

    (Voyage)关联起来,记录并跟踪这种关系。现在看来一切都还算简

    单。应用程序代码中可能会有一个像下面这样的方法:由于总会有人在最后一刻取消订单,因此航运业的一般做法是接

    受比其运载能力多一些的货物。这称为“超订”。有时使用一个简单

    的容量百分比来表示,如预订110%的载货量。有时则采用复杂的规则

    ——主要客户或特定种类的货物优先。

    这是航运领域的一个基本策略,从事航运业的业务人员都知道

    它,但在软件团队中可能不是所有技术人员都知道这条规则。

    需求文档中包含下面这句话:

    允许10%的超订。

    现在,类图就应该像图1-9这样,代码如下:

    图1-9现在,一条重要的业务规则被隐藏在上面这段方法代码的一个卫

    语句 [2] 中。第4章将介绍LAYERED ARCHITECTURE,它会帮助我们将

    超订规则转移到领域对象中,但现在我们主要考虑如何把这条规则更

    清楚地表达出来,并让项目中的每个人都能了解到它。这将使我们得

    到一个类似的解决方案。

    (1) 如果业务规则如上述代码所写,不可能有业务专家会通过阅

    读这段代码来检验规则,即使在开发人员的帮助下也无法完成。

    (2) 非业务的技术人员很难将需求文本与代码联系起来。

    如果规则更复杂,情况将更糟。

    我们可以改变一下设计来更好地捕获这个知识。超订规则是一个

    策略,如图1-10所示。策略(policy)其实是STRATEGY模式 [3]

    [Gamma et al.1995]的别名。我们知道,使用STRATEGY的动机一般是

    为了替换不同的规则,虽然在这里并不需要这么做。但我们要获取的

    概念的确符合策略的含义,这在领域驱动设计中是同等重要的动机

    (参见第12章)。

    图1-10

    修改后的代码如下:新的Overbooking Policy类包含以下方法:

    现在所有人都清楚超订是一个独特的策略,而且超订规则的实现

    即明确又独立。

    现在,我并不建议将这样的精细设计应用到领域的每个细节中。

    第15章将深入阐述如何关注重点以及如何隔离其他问题或使这些问题

    最小化。这个例子的目的是说明领域模型和相应的设计可用来保护和

    共享知识。更明确的设计具有以下优点:

    (1) 为了实现更明确的设计,程序员和其他各位相关人员都必须

    理解超订的本质,明白它是一个明确且重要的业务规则,而不只是一

    个不起眼的计算。

    (2) 程序员可以向业务专家展示技术工件,甚至是代码,但应该

    是领域专家(在程序员指导下)可以理解的,以便形成反馈闭环。

    1.5 深层模型

    有用的模型很少停留在表面。随着对领域和应用程序需求的理解

    逐步加深,我们往往会丢弃那些最初看起来很重要的表面元素,或者切换它们的角度。这时,一些开始时不可能发现的巧妙抽象就会渐渐

    浮出水面,而它们恰恰切中问题的要害。

    前面的例子大体上是基于一个集装箱航运项目,这是本书列举的

    几个项目之一,本书还有几个示例会引用这个项目。本书所举的示例

    都很简单,即使不是航运专家也能理解它们。但在一个需要团队成员

    持续学习的真实项目中,要想建立实用且清晰的模型则要求团队成员

    既精通领域知识,也要精通建模技术。

    在这个项目中,由于航运从预订货运开始,因此我们开发了一个

    能够描述货物和运货航线等事物的模型。这是必要且有用的,但领域

    专家却不买账。他们有自己的考虑业务的方式,这种方式是我们没有

    考虑到的。

    最后,在经过几个月的知识消化后,我们知道货物的处理主要是

    由转包商或公司中的操作人员完成的,这包括实际的装货、卸货和运

    货。航运专家的观点是,各部分之间存在一系列的责任传递。法律责

    任和执行责任的传递由一个过程控制—从托运人传递到某个本地运输

    商,再从这家运输商传递到另一家运输商,最后到达收货人。通常,在一些重要的步骤中,货物停放在仓库里。在其他时间里,货物则是

    通过复杂的物理步骤来运输,而这些与航运公司的业务决策无关。在

    处理航线的物流之前,必须先确定诸如提单等法律文件以及支付流

    程。

    对航运业务有了更深刻的认识后,我们并没有删除Itinerary(航

    线)对象,但模型发生了巨大改变。我们对航运业务的认识从“集装

    箱在各个地点之间的运输”转变为“运货责任在各个实体之间的传

    递”。处理这些责任传递的特性不再是一些附属于装货作业的次要特

    性,而是由一个独立的模型来提供支持,这个模型正是在理解了作业

    与责任之间的重要关系之后开发出来的。

    知识消化是一种探索,它永无止境。第2章 交流与语言的使用

    领 域模型可成为软件项目通用语言的核心。该模型是一组得自于

    项目人员头脑中的概念,以及反映了领域深层含义的术语和关系。这

    些术语和相互关系提供了模型语言的语义,虽然语言是为领域量身定

    制的,但就技术开发而言,其依然足够精确。正是这条至关重要的纽

    带,将模型与开发活动结合在一起,并使模型与代码紧密绑定。

    这种基于模型的交流并不局限于UML(统一建模语言)图。为了最

    有效地使用模型,需要充分利用各种交流手段。基于模型的交流提高

    了书面文档的效用,也提高了敏捷过程中再度强调的非正式图表和交

    谈的效用。它还通过代码本身及对应的测试促进了交流。

    在项目中,语言的使用很微妙,但却至关重要……

    2.1 模式:UBIQUITOUS LANGUAGE

    首先写下一个句子,然后将它分成小段,再将它们打乱并重新排序。

    仿佛是巧合一样,短语的顺序对意思完全没有影响。

    ——Lewis Carroll,“Poeta Fit,Non Nascitur”

    要想创建一种灵活的、蕴含丰富知识的设计,需要一种通用的、共享的团队语言,以及对语言不断的试验——然而,软件项目上很少

    出现这样的试验。

    虽然领域专家对软件开发的技术术语所知有限,但他们能熟练使

    用自己领域的术语——可能还具有各种不同的风格。另一方面,开发人员可能会用一些描述性的、功能性的术语来理解和讨论系统,而这

    些术语并不具备领域专家的语言所要传达的意思。或者,开发人员可

    能会创建一些用于支持设计的抽象,但领域专家无法理解这些抽象。

    负责处理问题不同部分的开发人员可能会开发出各自不同的设计概念

    以及描述领域的方式。

    由于语言上存在鸿沟,领域专家们只能模糊地描述他们想要的东

    西。开发人员虽然努力去理解一个自己不熟悉的领域,但也只能形成

    模糊的认识。虽然少数团队成员会设法掌握这两种语言,但他们会变

    成信息流的瓶颈,并且他们的翻译也不准确。

    在一个没有公共语言的项目上,开发人员不得不为领域专家做翻

    译。而领域专家需要充当开发人员与其他领域专家之间的翻译。甚至

    开发人员之间还需要互相翻译。这些翻译使模型概念变得混淆,而这

    会导致有害的代码重构。这种间接的沟通掩盖了分裂的形成——不同

    的团队成员使用不同的术语而尚不自知。由于软件的各个部分不能够

    浑然一体,因此这就导致无法开发出可靠的软件(参见第14章)。翻

    译工作导致各类促进深入理解模型的知识和想法无法结合到一起。

    如果语言支离破碎,项目必将遭遇严重问题。领域专家使用他

    们自己的术语,而技术团队所使用的语言则经过调整,以便从设计

    角度讨论领域。

    日常讨论所使用的术语与代码(软件项目的最重要产品)中使

    用的术语不一致。甚至同一个人在讲话和写东西时使用的语言也不

    一致,这导致的后果是,对领域的深刻表述常常稍纵即逝,根本无

    法记录到代码或文档中。

    翻译使得沟通不畅,并削弱了知识消化。

    然而任何一方的语言都不能成为公共语言,因为它们无法满足

    所有的需求。所有翻译的开销,连带着误解的风险,成本实在太高了。项目需

    要一种公共语言,这种语言要比所有语言的最小公分母健壮得多。通

    过团队的一致努力,领域模型可以成为这种公共语言的核心,同时将

    团队沟通与软件实现紧密联系到一起。该语言将存在于团队工作中的

    方方面面。

    UBIQUITOUS LANGUAGE(通用语言)的词汇包括类和主要操作的名

    称。语言中的术语,有些用来讨论模型中已经明确的规则,还有一些

    则来自施加于模型上的高级组织原则(如第14章和第16章要讨论的

    CONTEXT MAP和大型结构)。最后,团队常常应用于领域模型的模式名

    称也使这种语言更为丰富。

    模型之间的关系成为所有语言都具有的组合规则。词和短语的意

    义反映了模型的语义。

    开发人员应该使用基于模型的语言来描述系统中的工件、任务和

    功能。这个模型应该为开发人员和领域专家提供一种用于相互交流的

    语言,而且领域专家还应该使用这种语言来讨论需求、开发计划和特

    性。语言使用得越普遍,理解进行得就越顺畅。

    至少,我们应该将它作为目标。但最初,模型可能不太好,因此

    无法很好地履行这些职责。它可能不会像领域的专业术语那样具有丰

    富的语义。但我们又不能直接使用那些术语,因为它们有歧义和矛

    盾。模型可能缺乏开发人员在代码中所创建的更为微妙和灵活的特

    性,这要么是因为开发人员认为模型不必具备这些特性,要么是因为

    编码风格是过程式的,只能隐含地表达领域概念。

    尽管模型和基于模型的语言之间的次序像是循环论证,但是,能

    够产生更有用模型的知识消化过程依赖于团队投身于基于模型的语

    言。持续使用UBIQUITOUS LANGUAGE可以暴露模型中存在的缺点,这样

    团队就可以尝试并替换不恰当的术语或组合。当在语言中发现缺失

    时,新的词语将被引入到讨论中。这些语言上的更改也会在领域模型中引起相应的更改,并促使团队更新类图并重命名代码中的类和方

    法,当术语的意义改变时,甚至会导致行为也发生改变。

    通过在实现的过程中使用这种语言,开发人员能够指出不准确和

    矛盾之处,并和领域专家一起找到有效的替代方案。

    当然,为了解释和给出更广泛的上下文,领域专家的语言会超出

    UBIQUITOUS LANGUAGE的范围。但在模型应对的范围内,他们应该使用

    UBIQUITOUS LANGUAGE,并在发现不合适、不完整或错误之处后要引起

    注意。通过大量使用基于模型的语言,并且不达流畅绝不罢休,我们

    可以逐步得到一个完整的、易于理解的模型,它由简单元素组成,并

    通过组合这些简单元素表达复杂的概念。

    因此:

    将模型作为语言的支柱。确保团队在内部的所有交流中以及代

    码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用

    这种语言。

    通过尝试不同的表示方法(它们反映了备选模型)来消除难

    点。然后重构代码,重新命名类、方法和模块,以便与新模型保持

    一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一致

    的理解一样。

    要认识到,UBIQUITOUS LANGUAGE的更改就是对模型的更改。

    领域专家应该抵制不合适或无法充分表达领域理解的术语或结

    构,开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的

    地方。

    有了UBIQUITOUS LANGUAGE,模型就不仅仅是一个设计工件了。它

    成为开发人员和领域专家共同完成的每项工作中不可或缺的部分。语

    言以动态形式传递知识。使用这种语言进行讨论能够呈现图和代码背

    后的真实含义。我们在这里讨论的UBIQUITOUS LANGUAGE假设只有一个模型在起作

    用。第14章将讨论不同模型(和语言)的共存,以及如何防止模型分

    裂。

    UBIQUITOUS LANGUAGE是那些以非代码形式呈现的设计的主要载

    体,这些包括把整个系统组织在一起的大尺度结构(参见第16章)、定义了不同系统和模型之间关系的限界上下文(参见第14章),以及

    在模型和设计中使用的其他模式。

    示例 制定货运路线

    下面这两段对话有着微妙但重要的差别。在每个对话场景中,注

    意观察讲话者有多少内容是谈论软件的业务功能,有多少内容是从技

    术上谈论软件的工作机理的。用户和开发人员用的是同一种语言吗?

    它的表达是否丰富,足以应对应用程序功能的讨论?

    场景1:最小化的领域抽象

    图2-1

    用户:那么,当更改清关(customs clearance) [4] 地点时,需要重新制定整个路线计划啰。开发人员:是的。我们将从货运表(shipment table)中删除所

    有与该货物id相关联的行,然后将出发地、目的地和新的清关地点传

    递给Routing Service,它会重新填充货运表。Cargo中必须设立一个

    布尔值,用于指示货运表中是否有数据。

    用户:删除行?好,就按你说的做。但是,如果先前根本没有指

    定清关地点,也需要这么做吗?

    开发人员:是的,无论何时更改了出发地、目的地或清关地点

    (或是第一次输入),都将检查是否已经有货运数据,如果有,则删

    除它们,然后由Routing Service重新生成数据。

    用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要

    这样做了。

    开发人员:哦,没问题。但让Routing Service每次重新加载或卸

    载数据会更容易些。

    用户:是的,但为新航线制定所有支持计划的工作量很大,因

    此,除非非改不可,我们一般不想更改航线。

    开发人员:哦,好的,当第一次输入清关地点时,我们需要查询

    表格,找到以前的清关地点,然后与新的清关地点进行比较,从而判

    断是否需要重做。

    用户:这个处理不必考虑出发地和目的地,因为航线在此总要变

    更。

    开发人员:好的,我明白了。

    场景2:用领域模型进行讨论图2-2

    用户:那么,当更改清关地点时,需要重新制定整个路线计划

    啰。

    开发人员:是的。当更改Route Specification(路线说明)的任

    意属性时,都将删除原有的Itinerary(航线),并要求Routing

    Service(路线服务)基于新的Route Specification生成一个新的

    Itinerary。

    用户:如果先前根本没有指定清关地点,也需要这么做吗?

    开发人员:是的,无论何时更改了Route Spec的任何属性,都将

    重新生成Itinerary。这也包括第一次输入某些属性。

    用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要

    这样做了

    开发人员:哦,没问题。但让Routing Service每次重新生成一个

    Itinerary会更容易些。

    用户:是的,但为新航线制定所有支持计划的工作量很大,因

    此,除非非改不可,我们一般不想更改路线。开发人员:哦。那么需要在Route Specification添加一些功能。

    这样,当更改Route Specification中的属性时,查看Itinerary是否

    仍满足Specification。如果不满足,则需要由Routing Service重新

    生成Itinerary。

    用户:这一点不必考虑出发地和目的地,因为Itinerary在此总是

    要变更的。

    开发人员:好的,但每次只做比较就简单多了。只有当不满足

    Route Specification时,才重新生成Itinerary。

    第二段对话表达了领域专家的更多意图。在这两段对话中,用户

    都使用了“itinerary”这个词,但在第二段中它是一个对象,这使得

    双方可以更准确、具体地进行讨论。他们明确讨论了“route

    specification”,而不是每次都通过属性和过程来描述它。

    这两段对话有意使用了相似的结构。实际上,第一段对话显得更

    啰嗦,对话双方需要不断对应用程序的特性和表达不清的地方进行解

    释。第二段对话使用了基于领域模型的术语,因此讨论更简洁。

    2.2 “大声地”建模

    假如将交谈从沟通方式中除去的话,那会是巨大的损失,因为人

    类本身颇具谈话的天赋。遗憾的是,当人们交谈时,通常并不使用领

    域模型的语言。

    可能开始时你并不认为上述论断是正确的,而且的确有例外情

    况。但下次你参加需求或设计讨论时,不妨认真听一下。你将听到人

    们用业务术语或者各种业余术语来描述功能。还会听到人们讨论技术

    工件和具体的功能。当然,你还会听到来自领域模型的术语;在人们

    共同使用的那部分业务术语中,那些显而易见的名词在编码时通常被用作对象名称,因此这些术语经常被人们提及。但你是否也听到一些

    使用当前领域模型中的关系和交互来描述的措辞呢?

    改善模型的最佳方式之一就是通过对话来研究,试着大声说出可

    能的模型变化中的各种结构。这样不完善的地方很容易被听出来。

    “如果我们向Routing Service提供出发地、目的地和到达时间,就可以查询货物的停靠地点,嗯……将它们存到数据库中。”(含糊

    且偏重于技术)

    “出发地、目的地……把它们都输入到Routing Service中,而后

    我们得到一个Itinerary,它包含我们所需的全部信息。”(更具体,但过于啰嗦)

    “Routing Service 查 找 满 足 Route Specification 的

    Itinerary。”(简洁)

    使用单词和短语是极为重要的——其将我们的语言能力用于建模

    工作,这就如同素描对于表现视觉和空间推理十分重要一样。我们即

    要利用系统性分析和设计方面的分析能力,也要利用对代码的神秘

    “感觉”。这些思考方式互为补充,要充分利用它们来找到有用的模

    型和设计。在所有这些方式中,语言上的试验常常是最容易被忽视的

    (本书第三部分将深入探讨这种发现过程,并通过几段对话来显示它

    们之间的相互影响)。

    事实上,我们的大脑似乎很擅长处理口语的复杂性(对于像我这

    样 的 门 外 汉 , 有 本 好 书 是 Steven Pinker 所 著 的 The Language

    Instinct[Pinker 1994])。例如,当具有不同语言背景的人凑在一起

    做生意时,如果没有公共语言,他们就会创造一种称为“混杂语”

    (pidgin)的公共语言。混杂语虽然不像讲话者的母语那样详尽,但

    它适合当前任务。当人们交谈时,自然会发现词语解释和意义上的差

    别,而后会自然而然地解决这些差别。他们会发现这种语言中的简陋

    晦涩之处并把它们搞顺畅。上大学时,我曾经修过西班牙语速成课。课堂上规定不准讲英

    语。起初,令人相当沮丧。这不仅感觉很别扭,而且需要很强的自制

    力。但最终我和同学们都达到了通过书面练习永远不可能达到的流利

    程度。

    当我们在讨论中使用领域模型的UBIQUITOUS LANGUAGE时,特别是

    在开发人员和领域专家一起推敲场景和需求时,通用语言的使用会越

    来越流利,而且我们还可以互相指点一些细微的差别。我们自然而然

    地共享了我们所说的语言,而这种方式是图和文档无法做到的。

    想要在软件项目上产生一种UBIQUITOUS LANGUAGE,说起来容易,做起来却难,我们必须充分利用自然赋予我们的才能来实现这个目

    标。正如人类的视觉和空间思维能力使我们能够快速传达和处理图形

    概述中的信息一样,我们也可以利用自己在基于语法的、有意义的语

    言方面的天赋来推动模型的开发。

    因此,下面这段话可作为UBIQUITOUS LANGUAGE模式的补充:

    讨论系统时要结合模型。使用模型元素及其交互来大声描述场

    景,并且按照模型允许的方式将各种概念结合到一起。找到更简单

    的表达方式来讲出你要讲的话,然后将这些新的想法应用到图和代

    码中。

    2.3 一个团队,一种语言

    技术人员通常认为业务专家最好不要接触领域模型,他们认为:

    “领域模型对他们来说太抽象了。”

    “他们不理解对象。”

    “这样我们就不得不用他们的术语来收集需求。”

    上面只列举了我从一个使用两种语言的团队中听到的少数几个原

    因。忘掉它们吧。当然,设计中有一些技术组件与领域专家无关,但模型的核心最

    好让他们参与。过于抽象?那你怎么知道抽象是否合理?你是否像他

    们一样深入理解领域?有时,某些特定需求是从底层用户那里收集

    的,他们在描述这些需求时可能会用到一小部分更具体的术语,但领

    域专家应该能够更深入地思考他们所从事的领域。如果连经验丰富的

    领域专家都不能理解模型,那么模型一定出了什么问题。

    最初,当用户讨论系统尚未建模的未来功能时,他们没有模型可

    供使用。但当他们开始与开发人员一起仔细讨论这些新想法时,探索

    共享模型的过程就开始了。最初的模型可能很笨拙且不完整,但会逐

    渐精化。随着新语言的演进,领域专家必须付出更多努力来适应它,并更新那些仍然很重要的旧文档。

    当领域专家使用这种语言互相讨论,或者与开发人员进行讨论

    时,很快就会发现模型中哪些地方不符合他们的需要,甚至是错误

    的。另一方面,模型语言的精确性也会促使领域专家(在开发人员的

    帮助下)发现他们想法中的矛盾和含糊之处。

    开发人员和领域专家可以通过一步一步地使用模型对象来走查场

    景,从而对模型进行非正式的测试。每次讨论都是开发人员和专家一

    起使用模型的机会,在这个过程中,他们可以加深彼此的理解,并对

    概念进行精化。

    领域专家可以使用模型语言来编写用例,甚至可以直接利用模型

    来具体说明验收测试。

    有时,有人会反对使用模型语言来收集需求。毕竟,难道需求不

    应该独立于实现它们的设计吗?这种观点忽视了所有语言都要基于某

    种模型这一事实。词的意义是不明确。领域模型通常是从领域专家自

    己的术语中推导出来的,但已经经过了“清理”,以便具有更明确、更严密的定义。当然,如果这些定义与领域公认的意义有较大差别,领域专家应该反对。在敏捷过程中,需求是随着项目的前进而演变的,因为几乎不存在现成的知识可以充分说明一个应用程序。用精化

    后的UBIQUITOUS LANGUAGE来重新组织需求应该是这种演变过程的一部

    分。

    语言的多样性通常是必要的,但领域专家与开发人员之间不应该

    有语言上的分歧(第12章将讨论多个模型在同一个项目上共存的情

    况)。

    当然,开发人员的确会使用领域专家无法理解的技术术语。开发

    人员有其所需的大量术语来讨论系统技术。几乎可以肯定的是,用户

    也会用开发人员无法理解的、超出应用程序范畴的专用术语。这些都

    是对语言的扩展。但在这些语言扩展中,同一领域的相同词汇不应该

    反映不同的模型。

    图2-3 术语的交集产生了UBIQUITOUS LANGUAGE

    有了UBIQUITOUS LANGUAGE之后,开发人员之间的对话、领域专家

    之间的讨论以及代码本身所表达的内容都基于同一种语言,都来自于

    一个共享的领域模型。2.4 文档和图

    每当我参加讨论软件设计的会议时,如果不在白板或画板上画

    图,我就很难讨论下去。我画的大部分是UML图,主要以类图和对象交

    互图为主。

    有些人天生是视觉动物,图可以帮助人们掌握某些类型的信息。

    UML图在传达对象之间的关系上真是游刃有余,而且也很擅长表现交

    互。但它们却无法给出这些对象的概念定义。在会议中,我会一边画

    图一边用语言来丰富它们的意义,或者在与其他参与者讨论时进行解

    释。

    简单、非正式的UML图能够维系整个讨论。绘制一幅包含当前问题

    最关键的3~5个对象的图,这样每个人都可以集中注意力。所有人就

    对象关系会达成一致的认识,更重要的是,他们将使用相同的对象名

    称。如此,口头讨论会更加高效。当人们尝试不同的想法时,图也随

    之改变,草图在某种程度上可以反映讨论的变化,这是讨论中真正重

    要的部分。毕竟,UML就是统一建模语言。

    当人们必须通过UML图表示整个模型或设计时,麻烦也随之而来。

    很多对象模型图在某些方面过于细致,同时在某些方面又有很多遗

    漏。说它们过于细致是因为人们认为必须将所有要编码的对象都放到

    建模工具中。而细节过多的结果是“只见树木,不见森林”。

    尽管存在所有这些细节,但属性和关系只是对象模型的一部分。

    这些对象的行为以及这些对象上的约束就不那么容易表示了。对象交

    互图可以阐明设计中的一些复杂之处,但却无法用这种方式来展示大

    量的交互,就是工作量太大了,既要制作图,还要学习这些图。而且

    交互图也只能暗示出模型的目的。要想把约束和断言包括进来,需要

    在UML图中使用文本,这些文本用括号括起来,插入到图中。操作名称可能会暗示出对象的行为职责,对象交互图(或序列

    图)中也会隐含地展示出这些职责,但无法直接表述。因此,这项任

    务就要靠补充文本或对话来完成。换言之,UML图无法传达模型的两个

    最重要的方面,一个方面是模型所表示的概念的意义,另一方面是对

    象应该做哪些事情。但是,这并不是大问题,因为通过仔细地使用语

    言(英语、西班牙语或其他任何一种语言)就可以很好地完成这项任

    务。

    UML也不是一种十分令人满意的编程语言。我从未见过有人使用建

    模工具的代码生成功能达到了预期目的。如果UML的能力无法满足需

    要,通常人们就不得不忽略模型最关键的部分,因为有些规则并不适

    合用线框图来表示。当然,代码生成器也无法使用上面所说的那些文

    本注释。如果确实能使用UML这样的绘图语言来编写可执行程序,那么

    UML图就会退化为程序本身的另一种视图,这样,“模型”的真正含义

    就丢失了。如果使用UML作为实现语言,则仍然需要利用其他手段来表

    达模型的确切含义。

    图是一种沟通和解释手段,它们可以促进头脑风暴。简洁的小图

    能够很好地实现这些目标,而涵盖整个对象模型的综合性大图反而失

    去了沟通或解释能力,因为它们将读者淹没在大量细节之中,加之这

    些图也缺乏目的性。鉴于此,我们应避免使用包罗万象的对象模型

    图,甚至不能使用包含所有细节的UML数据存储库。相反,应使用简化

    的图,图中只包含对象模型的重要概念——这些部分对于理解设计至

    关重要。本书中的图都是我在项目中使用过比较典型的图。它们很简

    单,而且具有很强的解释能力,在澄清一些要点时,还使用了一些非

    标准的符号。它们显示了设计约束,但它们不是面面俱到的设计规

    范。它们只体现了思想纲要。

    设计的重要细节应该在代码中体现出来。良好的实现应该是透明

    的,清楚地展示其背后的模型(下一章及本书其他许多章节的主题就是阐述如何做到这一点)。互为补充的图和文档能够引导人们将注意

    力放在核心要点上。自然语言的讨论可以填补含义上的细微差别。这

    就是为什么我喜欢把典型的UML使用方法颠倒过来的原因。通常的用法

    是以图为主,辅以文本注释;而我更愿意以文本为主,用精心挑选的

    简化图作为说明。

    务必要记住模型不是图。图的目的是帮助表达和解释模型。代码

    可以充当设计细节的存储库。书写良好的Java代码与UML具有同样的表

    达能力。经过仔细选择和构造的图可以帮助人们集中注意力,并起到

    指导作用,当然前提条件是不能强制用图来表示全部模型或设计,因

    为这样会削弱图的清晰表达的能力。

    2.4.1 书面设计文档

    口头交流可以解释代码的含义,因此可作为代码精确性和细节的

    补充。虽然交谈对于将人们与模型联系起来是至关重要的,但书面文

    档也是必不可少的,任何规模的团队都需要它来提供稳定和共享的交

    流。但要想编写出能够帮助团队开发出好软件的书面文档却是一个不

    小的挑战。

    一旦文档的形式变得一成不变,往往会从项目进展流程中脱离出

    来。它会跟不上代码或项目语言的演变。

    书面文档有很多编写方法。本书第四部分将介绍几种满足特定需

    要的具体文档,但不会列出项目需要使用的所有文档,而是给出两条

    用于评估文档的总体原则。

    文档应作为代码和口头交流的补充

    每种敏捷过程在编写文档方面都有自己的理念。极限编程主张完

    全不使用(多余的)设计文档,而让代码解释自己。实际运行的代码

    不会说谎,而其他文档则不然。运行代码所产生的行为是明确的。极限编程只关注对程序及可执行测试起作用的因素。由于为代码

    添加的注释并不影响程序的行为,因此它们往往无法与当前代码及其

    模型保持同步。外部文档和图也不会影响程序的行为,因此它们也无

    法保持同步。另一方面,口头交流和临时在白板上画的图不会长久保

    留而产生混淆。依赖代码作为交流媒介可以促使开发人员保持代码的

    整洁和透明。

    然而,将代码作为设计文档也有局限性。它可能会把读代码的人

    淹没在细节中。尽管代码的行为是非常明确的,但这并不意味着其行

    为是显而易见的。而且行为背后的意义可能难以表达。换言之,只用

    代码做文档与使用大而全的UML图面临着差不多相同的基本问题。当

    然,团队进行大量的口头交流能够为代码提供上下文和指导,但是,口头交流很短暂,而且范围很小。此外,开发人员并不是唯一需要理

    解模型的人。

    文档不应再重复表示代码已经明确表达出的内容。代码已经含有

    各个细节,它本身就是一种精确的程序行为说明。

    其他文档应该着重说明含义,以便使人们能够深入理解大尺度结

    构,并将注意力集中在核心元素上。当编程语言无法直接明了地实现

    概念时,文档可以澄清设计意图。我们应该把书面文档作为代码和口

    头讨论的补充。

    文档应当鲜活并保持最新

    我在为模型编写书面文档时,会仔细选择一个小的模型子集来画

    图,然后让文字放臵在这些图周围。我用文字定义类及其职责,并且

    像自然语言那样把它们限定在一个语义上下文中。而图显示了在将概

    念形式化和简化为对象模型的过程中所做的一些选择。这些图可以随

    意一些,甚至是手绘的。手绘图除了节省工作量,也让人们一看就知

    道它们是不正式、临时的。这些优点都非常有利于交流,因为它们适

    用于我们的模型思想。设计文档的最大价值在于解释模型的概念,帮助在代码的细节中

    指引方向,或许还可以帮助人们深入了解模型预期的使用风格。根据

    不同的团队理念,整个设计文档可能会十分简单,如只是贴在墙上的

    一组草图,也可能会非常详尽。

    文档必须深入到各种项目活动中去。判断是否做到这一点的最简

    单方法,是观察文档与UBIQUITOUS LANGUAGE之间的交互。文档是用人

    们(当前)在项目上讲的语言编写的吗?它是用嵌入到代码中的语言

    编写的吗?

    注意听UBIQUITOUS LANGUAGE,观察它是如何变化的。如果设计文

    档中使用的术语不再出现在讨论和代码中,那么文档就没有起到它的

    作用。或许是文档太大或太复杂了,或许是它没有关注足够重要的主

    题。人们要么不阅读文档,要么觉得它索然无味。如果文档对

    UBIQUITOUS LANGUAGE没有影响,那么一定是出问题了。

    相反,我们会注意到UBIQUITOUS LANGUAGE随着文档渐渐过时而自

    然地改变。显然,要么人们不再关心文档,要么认为它不重要而不再

    去更新它。这时可以将它作为历史文件安全地归档,如果继续使用这

    样的文档可能会产生混淆并损害项目。如果文档不再担负重要的作

    用,那么纯粹靠意志和纪律保持其更新就是浪费精力。

    UBIQUITOUS LANGUAGE可以使其他文档(如需求规格说明)更简洁

    和明确。当领域模型反映了与业务最相关的知识时,应用程序的需求

    成为该模型内部的场景,而UBIQUITOUS LANGUAGE可直接用MODEL-

    DRIVEN DESIGN(模型驱动设计)的方式描述此类场景(参见第3

    章)。结果就是规格说明的编写更简单,因为它们不必传达模型背后

    隐含的业务知识。

    通过将文档减至最少,并且主要用它来补充代码和口头交流,就

    可以避免文档与项目脱节。根据UBIQUITOUS LANGUAGE及其演变来选择

    那些需要保持更新并与项目活动紧密交互的文档。2.4.2 完全依赖可执行代码的情况

    现在,我们来考查一下XP社区和其他一些人为何选择几乎完全依

    赖可执行代码及其测试。本书主要讨论了如何通过MODEL-DRIVEN

    DESIGN使代码表达出设计的含义(参见第3章)。良好的代码具有很强

    的表达能力,但它所传递的信息不能确保是准确的。一段代码所产生

    的实际行为是不会改变的。但是,方法名称可能会有歧义、会产生误

    导或者因为已经过时而无法表示方法的本质含义。测试中的断言是严

    格的,但变量和代码组织方式所表达出来的意思未必严格。好的编程

    风格会尽力使这种联系直接化,但其仍然主要靠开发人员的自律。编

    码时需要一丝不苟的态度,只有这样才能编写出“言行全部正确”的

    代码。

    消除这些差异是诸如声明式设计(参见第10章)这样的方法的最

    大优点,在这类方法中,程序元素用途的陈述决定了它在程序中的实

    际行为。从UML生成程序的部分动机就来源于此,虽然目前看来这通常

    不会得到好的结果。

    尽管代码可能会产生误导,但它仍然比其他文档更基础。要想利

    用当前的标准技术使代码所传达的消息与它的行为和意图保持一致,需要纪律和思考设计的特定方式(第三部分将详细讨论这些问题)。

    要有效地交流,代码必须基于在编写需求时所使用的同一种语言,也

    就是开发人员之间、开发人员与领域专家之间进行讨论时所使用的语

    言。

    2.5 解释性模型

    本书的核心思想是在实现、设计和团队交流中使用同一个模型作

    为基础。如果各有各的模型,将会造成危害。模型在帮助领域学习方面也具有很大价值。对设计起到推动作用

    的模型是领域的一个视图,但为了学习领域,还可以引入其他视图,这些视图只用作传递一般领域知识的教学工具。出于此目的,人们可

    以使用与软件设计无关的其他种类模型的图片或文字。

    使用其他模型的一个特殊原因是范围。驱动软件开发过程的技术

    模型必须经过严格的精简,以便用最小化的模型来实现其功能。而解

    释性模型则可以包含那些提供上下文的领域方面——这些上下文用于

    澄清范围更窄的模型。

    解释性模型提供了一定的自由度,可以专门为某个特殊主题定制

    一些表达力更强的风格。领域专家在一个领域中所使用的视觉隐喻通

    常呈现了更清晰的解释,这可以教给开发人员领域知识,同时使领域

    专家们的意见更一致。解释性模型还可以以一种不同的方式来呈现领

    域,并且各种不同角度的解释有助于人们更好地学习。

    解释性模型不必是对象模型,而且最好不是。实际上在这些模型

    中不使用UML是有好处的,这样可以避免人们错误地认为这些模型与软

    件设计是一致的。尽管解释性模型与驱动设计的模型往往有对应关

    系,但它们并不完全类似。为了避免混淆,每个人都必须知道它们之

    间的区别。

    示例 航运操作和路线

    考虑一个用来追踪航运公司货物的应用程序。模型包含一个详细

    的视图,它显示了如何将港口装卸和货轮航次组合为一次货运的操作

    计划(“路线”)。如图2-4所示。但对外行而言,类图可能起不到多

    大的说明作用。图2-4 航运路线的类图

    在这种情况下,解释性模型可以帮助团队成员理解类图的实际含

    义。图2-5是表示相同概念的另一种方式。

    图2-5 航运路线的解释性模型图中的每根线段都表示货物的一种状态——或者正在港口装卸

    (装货或卸货),或者停放在仓库里,或者正在运输途中。这个图并

    没有与类图中的细节一一对应,但强调了领域的要点。

    这种图连同对它所表示的模型的自然语言解释,能够帮助开发人

    员和领域专家理解更严格的软件模型图。综合使用这两种图要比单独

    使用一种图更容易理解。

    第3章 绑定模型和实现

    当 我走进办公室,首先映入眼帘的是打印在数张大纸上的完整类

    图,它铺满了一整面墙。这是我进入某个项目的第一天,在此之前,聪明的项目组成员花费了几个月的时间进行仔细的研究并且开发出了

    上面这幅详尽的领域模型。该模型中的对象一般都与3~4个其他对象有

    着复杂的关联,而这张关联网几乎没有边界。在这方面,分析人员忠

    实地反映了领域自身的性质。

    尽管这张墙面大小的图让人吃不消,但是它所表现的模型确实捕

    获了一些知识。经过一段时间的研究,我确实从中学到了不少知识

    (但是这种学习很难找到头绪,更像是在随意地浏览网页)。然而对

    类图的研究并不能让我深入地了解该应用程序的代码和设计,这让我

    备感困扰。

    当开发人员开始实现应用程序时,他们很快就发现,尽管分析人

    员说得头头是道,他们依然无法将这种错综复杂的关系转换成可存

    储、可检索的且具有事务完整性的单元。请注意,该项目使用的是对

    象数据库,也就是说开发人员根本不用考虑对象—关系表映射这种难

    题。从根本上说,该模型无法为应用程序的实现提供帮助。由于模型是“正确的”,这是经过技术分析人员和业务专家大量

    协作才得到的结果,因此开发人员得出这样的结论:无法把基于概念

    的对象作为设计的基础。于是他们开始进行专门针对程序开发的设

    计。他们的设计确实用了一些原有模型中类和属性的名称进行数据存

    储,但这种设计并不是建立在任何已有模型的基础上的。

    这个项目虽然建立了领域模型,但是如果模型不能直接帮助开发

    可运行的软件,那么这种纸上谈兵的模型又有什么意义呢?

    几年后,我在一个完全不同的项目中又看到了完全相同的结果。

    该项目要用Java实现新设计,并用新设计替换现存的C++应用程序。老

    版本的程序根本没有进行对象建模,仅仅是把功能堆积在一起。老版

    本的设计(如果有的话)就是在已有代码的基础上一个一个地堆积新

    功能,完全没有任何泛化或抽象的迹象。

    奇怪的是,这两种开发流程所完成的最终产品却非常相似!它们

    都充斥了大量功能,难于理解,难以维护。尽管有些程序实现是比较

    直观的,但是仅通过阅读代码依然无法深入了解该系统的目的所在。

    除了精心设计的数据结构之外,这两种开发流程都没有利用其开发环

    境中的面向对象的设计范式。

    模型种类繁多,作用各有不同,即使是那些仅用于软件开发项目

    的模型也是如此。领域驱动设计要求模型不仅能够指导早期的分析工

    作,还应该成为设计的基础。这种设计方法对于代码的编写有着重要

    的意义。不太明显的一点就是:领域驱动设计要求一种不同的建模方

    法……

    3.1 模式:MODEL-DRIVEN DESIGN过去用来计算星体位置的星盘 [5] 是天空模型的机械实现

    中世纪的星象电脑

    星盘是由古希腊的天文学家发明的;在中世纪,伊斯兰科学

    家又对它进行了改进。星盘上可旋转的铜环(又称“网环”)代

    表各恒星在天球上的位置。刻有当地地平坐标系的盘面是可换

    的,它代表的是不同纬度的星空景象。在星盘盘面上旋转网环,可以计算出全年任何时刻的天体位置。反过来,如果知道太阳或

    某个恒星的位置,也可以用星盘算出时间。星盘以机械化的方式

    实现了代表星空的面向对象模型。

    严格按照基础模型来编写代码,能够使代码更好地表达设计含

    义,并且使模型与实际的系统相契合。那些压根儿就没有领域模型的项目,仅仅通过编写代码来实现一

    个又一个的功能,它们无法利用前两章所讨论的知识消化和沟通所带

    来的好处。如果涉及复杂的领域就会使项目举步维艰。

    另一方面,许多复杂项目确实在尝试使用某种形式的领域模型,但是并没有把代码的编写与模型紧密联系起来。这些项目所设计的模

    型,在项目初期还可能用来做一些探索工作,但是随着项目的进展,这些模型与项目渐行渐远,甚至还会起误导作用。所有在模型上花费

    的精力都无法保证程序设计的正确性,因为模型和设计是不同的。

    模型和程序设计之间的联系可能在很多情况下被破坏,但是二者

    的这种分离往往是有意而为之的。很多设计方法都提倡使用完全脱离

    于程序设计的分析模型,并且通常这二者是由不同的人员开发的。之

    所以称其为分析模型,是因为它是对业务领域进行分析的结果,它在

    组织业务领域中的概念时,完全不去考虑自己在软件系统中将会起到

    的作用。分析模型仅仅是理解工具,人们认为把它与程序实现联系在

    一起无异于搅浑一池清水。随后的程序设计与分析模型之间可能仅仅

    保持一种松散的对应关系。在创建分析模型时并没有考虑程序设计的

    问题,因此分析模型很有可能无法满足程序设计的需求。

    这种分析中会有一些知识消化的过程,但是在编码开始后,如果

    开发人员不得不重新对设计进行抽象,那么大部分的领域知识就会被

    丢弃。如此一来,就不能保证在新的程序设计中还能保留或者重现分

    析人员所获得的并且嵌入在模型中的领域知识。到了这一步,要维护

    程序设计和松散连接的模型之间的对应关系就很不合算了。

    纯粹的分析模型甚至在实现理解领域这一主要目的方面也捉襟见

    肘,因为在程序设计和实现过程中总是会发现一些关键的知识点,而

    细节问题则会出人意料地层出不穷。前期模型可能会深入研究一些不

    相关的问题,反而忽略了一些重要的方面。而且它对于其他问题的描

    述也可能对应用程序没有任何帮助。最后的结果就是:编码工作一开始,纯粹的分析模型就被抛到一边,大部分的模型都需要重新设计。

    即便是重新设计,如果开发人员认为分析与程序开发毫不相关,那么

    建模过程就不会那么规范。而如果项目经理也这么认为,那么开发团

    队可能没有足够的机会与领域专家进行交流。

    无论是什么原因,软件的设计如果缺乏概念,那么软件充其量不

    过是一种机械化的产品——只实现有用的功能却无法解释操作的原

    因。

    如果整个程序设计或者其核心部分没有与领域模型相对应,那

    么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模

    型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项

    目中,当设计改变时也无法维护这种关系。若分析与和设计之间产

    生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共

    享。

    分析工作一定要抓住领域内的基础概念,并且用易于理解和易于

    表达的方式描述出来。设计工作则需要指定一套可以由项目中使用的

    编程工具创建的组件,使项目可以在目标部署环境中高效运行,并且

    能够正确解决应用程序所遇到的问题。

    MODEL-DRIVEN DESIGN(模型驱动设计)不再将分析模型和程序设

    计分离开,而是寻求一种能够满足这两方面需求的单一模型。不考虑

    纯粹的技术问题,程序设计中的每个对象都反映了模型中所描述的相

    应概念。这就要求我们以更高的标准来选择模型,因为它必须同时满

    足两种完全不同的目标。

    有很多方法可以对领域进行抽象,也有很多种设计可以解决应用

    程序的问题。因此,绑定模型和程序设计是切实可行的。但是这种绑

    定不能够因为技术考虑而削弱分析的功能,我们也不能接受那些只反

    映了领域概念却舍弃了软件设计原则的拙劣设计。模型和设计的绑定

    需要的是在分析和程序设计阶段都能发挥良好作用的模型。如果模型对于程序的实现来说显得不太实用时,我们必须重新设计它。而如果

    模型无法忠实地描述领域的关键概念,也必须重新设计它。这样,建

    模和程序设计就结合为一个统一的迭代开发过程。

    将领域模型和程序设计紧密联系在一起绝对是必要的,这也使得

    在众多可选模型中选择最适用的模型时,又多了一条选择标准。它要

    求我们认真思考,并且通常会经过多次反复修改和重新构建的过程,但是通过这样的过程可以得到与设计关联的模型。

    因此:

    软件系统各个部分的设计应该忠实地反映领域模型,以便体现

    出这二者之间的明确对应关系。我们应该反复检查并修改模型,以

    便软件可以更加自然地实现模型,即使想让模型反映出更深层次的

    领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的UBIQUITOUS LANGUAGE(通用语言)。

    从模型中获取用于程序设计和基本职责分配的术语。让程序代

    码成为模型的表达,代码的改变可能会是模型的改变。而其影响势

    必要波及接下来相应的项目活动。

    完全依赖模型的实现通常需要支持建模范式的软件开发工具和

    语言,比如面向对象的编程。

    有时,不同的子系统会有不同的模型(参见第14章),但是从需

    求分析到代码编写的整个开发过程中,软件系统的每一部分只能对应

    一个模型。

    单一模型能够减少出错的概率,因为程序设计直接来源于经过仔

    细考虑而创建的模型。程序设计,甚至是代码本身,都与模型密不可

    分。

    要想创建出能够抓住主要问题并且帮助程序设计的单一模型并没

    有说的那么容易。我们不可能随手抓个模型就把它转化成可使用的设

    计。只有经过精心设计的模型才能促成切实可行的实现。想要使代码有效地描述模型就需要用到程序设计和实现的技巧(参见第二部

    分)。知识消化人员需要研究模型的各个选项,并将它们细化为实用

    的软件元素。软件开发于是就成了一个不断精化模型、设计和代码的

    统一的迭代过程(参见第三部分)。

    3.2 建模范式和工具支持

    为了使MODEL-DRIVEN DESIGN发挥作用,一定要在可控范围内严格

    保证模型与设计之间的一致性。要实现这种严格的一致性,必须要运

    用由软件工具支持的建模范式,它可以在程序中直接创建模型中的对

    应概念。

    图3-1

    面向对象编程之所以功能强大,是因为它基于建模范式,并且为

    模型构造提供了实现方式。如图3-1所示。从程序员的角度来看,对象

    真实存在于内存中,它们与其他对象相互联系,它们被组织成类,并

    且通过消息传递来完成相应的行为。许多开发人员只是得益于对象的

    技术能力——用其组织程序代码,只有用代码表达模型概念时,对象设计的真正突破之处才彰显出来。Java和许多其他工具都允许创建直

    接反映概念对象模型的对象和关系。

    Prolog语言并不像面向对象语言那样被广泛使用,但是它却非常

    适合MODEL-DRIVEN DESIGN。在MODEL-DRIVEN DESIGN中,建模范式是

    逻辑的,而模型则是一组逻辑规则以及这些规则所操作的事实。

    像C这样的语言并不适用于MODEL-DRIVEN DESIGN,因为没有适用

    于纯粹过程语言的建模范式。对过程语言而言,程序员要告诉电脑一

    系列要执行的操作步骤。尽管程序员也会考虑到领域中的概念,但是

    程序本身仅仅是一组对数据进行的技术操作。最终程序可能是实用

    的,但是它并没有包含太多的意义。过程语言通常支持复杂的数据类

    型,这些数据类型一开始就能很自然地对应到领域中的概念上,但是

    它们也只是被组织在一起的数据,并不能描述领域的活跃方面。因

    此,用过程语言编写的软件具有复杂的函数,这些函数基于预先制定

    的执行路径连接在一起,而不是通过领域模型中的概念联系进行连接

    的。

    在我还没听说面向对象编程的时候,我是通过Fortran程序来实现

    数学模型的,这也正是Fortran所擅长的领域。数学函数是这种模型中

    主要的概念组件,也是Fortran语言能够清晰描述的。即便如此,对于

    超越函数的更高层次的意义,Fortran就毫无办法了。大部分非数学领

    域都不适合用过程语言来进行MODEL-DRIVEN DESIGN,因为这些领域无

    法被抽象成数学函数或者过程中的操作步骤。

    面向对象设计是目前大多数项目所使用的建模范式,也是本书中

    使用的主要方法。

    示例 从过程设计到MODEL-DRIVEN DESIGN

    第1章讲过,我们可以把PCB看作是连接各种电路元件引脚的导体

    (称为net)集合。电路板上通常都会有成千上万个net。有一种叫做

    PCB布线工具的专用软件,能够为所有net安排物理布线,而不会使它们相互交叉或干扰。它的实现方法就是根据设计者规定的大量限制条

    件来限制布线方式以及优化路径选择。尽管PCB布线工具已经非常先进

    了,但是它仍然有一些缺陷。

    其中一个问题是这些数以千计的net都拥有各自的布线规则,而

    PCB工程师会根据net自身的性质将其分组,同组的net共用相同的规

    则。比如,有些net构成了总线。如图3-2所示。

    图3-2 net构成总线的示意图

    工程师每次用8个、16个或者256个net组合成总线,这样布线工作

    就更易于管理了,不但提高了效率也减少了错误。问题是布线工具中

    没有类似于总线这样的概念。布线规则不得不应用于成千上万个net,一次处理一个net。

    呆板的设计走投无路的工程师只能用变通方法绕过布线工具的限制,编写脚

    本来分析布线工具的数据文件,然后将规则直接插入到文件中,从而

    一次性将规则应用于整个总线。

    布线工具在net列表文件中存储每个电路连接,如下所示:

    而布线规则被存储在类似于下面格式的文件中:

    工程师为net制定严格的命名约定,这样将数据文件的内容按照字

    母排序,就可以使构成同一条总线的所有net都排列在一起。然后他们

    编写的脚本就可以解析该文件并且基于总线来修改每个net。用来解

    析、处理和写入文件的实际代码太过冗长晦涩,对解释这个例子没有

    什么帮助,所以我在下面只列出了这个处理过程中要执行的步骤。

    (1) 按照net名称将net列表文件排序。

    (2) 逐行读取文件,寻找以总线名称开头的第一行数据。(3) 解析名称匹配的每一行,获取每行中net的名称。

    (4) 将net名称和规则文本附加到规则文件的末尾。

    (5) 从第(3)步起重复执行,直到没有匹配该总线名称的行。

    总线规则的输入文件采用如下的格式:

    经过处理后,输出的是添加了net规则的文件,如下所示:

    我猜想第一个编写这个脚本的人只有这种简单的需求,如果情况

    确实如此,那么使用这样的脚本是完全合理的。但实际情况是,已经

    存在成堆的脚本了。当然,可以通过重构来共用排序及字符串匹配之

    类的函数,而且如果脚本语言支持通过函数调用来封装细节,那么这

    些脚本看上去会和上面给出的步骤差不多。但是,它们依然只是对文

    件进行操作。文件格式一有不同(确实有几种)就需要重新编写一套

    程序,即便是总线分组以及为其分配规则的概念是相同的。如果你想

    要实现更多的功能和交互,就得下血本。

    脚本的编写者试图在布线工具的领域模型中补充“总线”这个概

    念,他们的脚本通过排序和字符串匹配来判断总线的存在,却没有明确地定义总线概念。

    MODEL-DRIVEN DESIGN

    前面我们已经描述了领域专家思考问题时所使用的概念。现在需

    要将这些概念组织成模型,作为软件开发的基础。

    图3-3 用来高效指定布线规则的类图

    用面向对象的语言实现上图的这些对象后,核心功能的实现会变

    得轻而易举。

    方法assignRule可以在抽象类AbstractNet中实现。而类Net中的

    方法assignedRules则分配了其自身的规则以及类Bus的规则。当然,程序还需要大量的支持代码,但上面的代码片段已经呈现

    了脚本的基本功能。

    这个程序还需要导入导出逻辑,我们可以将其封装成一些简单的

    服务。我们还需要几个工具类:

    现在,启动应用程序,用导入数据来初始化Net和Bus仓库。

    上面提到的服务和仓库都可以进行单元测试。更重要的是还可以

    测试核心领域逻辑。下面是对最核心的行为进行的单元测试(采用了

    JUnit测试框架):程序应该有一个交互式的用户界面,可以列出所有总线,让用户

    逐个指定规则;或者可以向后兼容,从规则文件中读取规则。采用外

    观(fa?ade)模式可以更容易地访问这些接口,如下代码是对应于上

    面测试的实现代码:

    最后一行代码:

    (这项服务调用每个Net的assignedRules方法,然后将所有规

    则完全写入规则文件。)

    当然,如果只有一种操作(就像这个例子一样),那么基于脚本

    的处理方式可能也同样实用。但实际上,通常会需要20个甚至更多的

    操作。MODEL-DRIVEN DESIGN易于扩展,能够为规则的组合设臵限制条

    件,还能提供其他的一些增强功能。

    MODEL-DRIVEN DESIGN也为测试提供了方便。它的组件都具有定义

    完善的接口,可以进行单元测试。而测试脚本程序的唯一方法就是基

    于文件进行端到端的比较。

    记住,这样的设计不是一蹴而就的。我们需要反复研究领域知

    识,不断重构模型,才能将领域中重要的概念提炼成简单而清晰的模

    型。

    3.3 揭示主旨:为什么模型对用户至关重要从理论上讲,也许你可以向用户展示任何一种系统视图,而不管

    底层如何实现。但实际上,系统上下层结构的不匹配轻则导致误解,重则产生bug。让我们看一个非常简单的例子——微软IE浏览器的早期

    版中,网站书签功能对应的模型是如何误导用户的 [6] 。

    IE浏览器用户会认为“收藏夹”是存储网站名称的列表,网站名

    称在不同的会话中是保持不变的。但是系统实现却将收藏夹中的书签

    当作一个包含URL的文件,并将文件名称存储在收藏夹列表中。这样做

    的问题是,如果网页标题含有Windows系统文件名不能接受的非法字

    符,就会出现错误。假如用户想要收藏某页面并将其命名为:

    “Laziness: The Secret to Happiness”(懒惰:幸福的秘密),就

    会弹出一个错误信息:“文件名不能包含下列任何字符:\ : ?

    < > |”。用户会奇怪文件名是指什么。另一方面,如果网页标题已经

    包含非法字符,IE浏览器则会悄悄地把字符删除。这种数据丢失在这

    种情况下也许危害不大,但绝不是用户所期望的。在大多数应用中,程序悄悄地修改数据是不能接受的。

    MODEL-DRIVEN DESIGN要求只使用一个模型(在任何一个上下文中

    都是如此,第14章会讨论这一点)。大部分的设计建议和例子都只针

    对将分析模型和设计模型分离的问题,但是这里的问题涉及了另外一

    对不同的模型:用户模型和设计实现模型。

    当然,大多数情况下,没有经过处理的领域模型视图肯定不便于

    用户使用。但是在用户界面中出现与领域模型不同的“影像”将会使

    用户产生迷惑,除非这个“影像”完美无缺。如果网站收藏夹实际上

    只是快捷方式文件的集合,那么应该将这一事实告诉用户,还应该删

    除之前那个起误导作用的模型。这样不但能使收藏夹的功能更加清

    晰,用户还可以利用自己所知道的文件系统的知识来对网站收藏夹进

    行操作。比如,用户可以用资源管理器来重新组织已收藏的文件,而

    不是用浏览器内臵的拙劣工具。而电脑高手还能够灵活地在文件系统的任何位臵存储网页快捷方式。仅仅通过删除起误导作用的多余模型

    就可以让应用程序的功能更加强大且清晰。如果程序员认为原有模型

    足够好,那么为什么还要让用户学习新模型呢?

    此外,如果以不同的方式来存储收藏夹,比如将其存储在一个数

    据文件中,这样收藏文件就可以有自己的规则了。这些规则很可能是

    应用于网页的命名规则。这又是一个单一模型。这个模型告诉用户所

    有关于网站的命名规则都适用于网站收藏夹。

    如果程序设计基于一个能够反映出用户和领域专家所关心的基本

    问题的模型,那么与其他设计方式相比,这种设计可以将其主旨更明

    确地展示给用户。让用户了解模型,将使他们有更多机会挖掘软件的

    潜能,也能使软件的行为合乎情理、前后一致。

    3.4 模式:HANDS-ON MODELER

    人们总是把软件开发比喻成制造业。这个比喻的一个推论是:经

    验丰富的工程师做设计工作,而技能水平较低的劳动力负责组装产

    品。这种做法使许多项目陷入困境,原因很简单——软件开发就是设

    计。虽然开发团队中的每个成员都有自己的职责,但是将分析、建

    模、设计和编程工作过度分离会对MODEL-DRIVEN DESIGN产生不良影

    响。

    我曾经在一个项目中负责协调不同的应用程序开发团队,帮助开

    发可以驱动程序设计的领域模型。但是管理层认为建模人员就应该只

    负责建模工作,编写代码就是在浪费这种技能,于是他们不准我编写

    代码或者与程序员讨论细节问题。

    开始项目进展的还算顺利。我和领域专家以及各团队的开发负责

    人共同工作,消化领域知识并提炼出了一个不错的核心模型。但是该

    模型却从来没有派上用场,原因有两个。其一,模型的一些意图在其传递过程中丢失了。模型的整体效果

    受细节的影响很大(这将在第二部分和第三部分讨论),这些细节问

    题并不是总能在UML图或者一般讨论中遇到的。如果我能撸起袖子,直

    接与开发人员共同工作,提供一些参考代码和近距离的技术支持,那

    么他们也许能够理解模型中的抽象概念并据此进行开发。

    第二个原因是模型与程序实现及技术互相影响,而我无法直接获

    得这种反馈。例如,程序实现过程中发现模型的某部分在我们的技术

    平台上的工作效率极低,但是经过几个月的时间,我才一点一点获得

    了关于这个问题的全部信息。其实只需较少的改动就能解决这个问

    题,但是几个月过去了,改不改已经不重要了。因为开发人员已经自

    行编写出了可以运行的软件——完全脱离了模型的设计,在那些还在

    使用模型的地方,也仅仅是把它当作纯粹的数据结构。开发人员不分

    好坏地把模型全盘否定,但是他们又有什么办法呢?他们再也不愿意

    冒险任由呆在象牙塔里的架构师摆布了。

    与其他项目一样,这个项目的初始环境倾向于不让建模人员参与

    太多的程序实现。对于该项目所使用的大部分技术,我都有着大量的

    实践经验。在做建模工作之前,我甚至曾经在同类项目中领导过一个

    小的开发团队,所以我对项目开发过程和编程环境非常熟悉。但是如

    果不让建模人员参与程序实现,我就是有这些经历也无法有效地工

    作。

    如果编写代码的人员认为自己没必要对模型负责,或者不知道

    如何让模型为应用程序服务,那么这个模型就和程序没有任何关

    联。如果开发人员没有意识到改变代码就意味着改变模型,那么他

    们对程序的重构不但不会增强模型的作用,反而还会削弱它的效

    果。同样,如果建模人员不参与到程序实现的过程中,那么对程序

    实现的约束就没有切身的感受,即使有,也会很快忘记。MODEL-

    DRIVEN DESIGN的两个基本要素(即模型要支持有效的实现并抽象出关键的领域知识)已经失去了一个,最终模型将变得不再实用。

    最后一点,如果分工阻断了设计人员与开发人员之间的协作,使他

    们无法转达实现MODEL-DRIVEN DESIGN的种种细节,那么经验丰富

    的设计人员则不能将自己的知识和技术传递给开发人员。

    HANDS-ON MODELER(亲身实践的建模者)并不意味着团队成员不

    能有自己的专业角色。包括极限编程在内的每一种敏捷过程都会给团

    队成员分配角色,其他非正式的专业角色也会自然而然地产生。但是

    如果把MODEL-DRIVEN DESIGN中密切相关的建模和实现这两个过程分离

    开,则会产生问题。

    整体设计的有效性有几个非常敏感的影响因素——那就是细粒度

    的设计和实现决策的质量和一致性。在MODEL-DRIVEN DESIGN中,代码

    是模型的表达,改变某段代码就改变了相应的模型。程序员就是建模

    人员,无论他们是否喜欢。所以在开始项目时,应该让程序员完成出

    色的建模工作。

    因此:

    任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。任何负责修改代码的人员则必须学会用代

    码来表达模型。每一个开发人员都必须不同程度地参与模型讨论并

    且与领域专家保持联系。参与不同工作的人都必须有意识地通过

    UBIQUITOUS LANGUAGE与接触代码的人及时交换关于模型的想法。

    将建模和编程过程完全分离是行不通的,然而大型项目依然需要

    技术负责人来协调高层次的设计和建模,并帮助做出最困难或最关键

    的决策。本书的第四部分描述的就是这种决策,通过学习该部分内容

    可以激发灵感,找到更高效的方法来定义高级技术人员的角色和职

    责。

    MODEL-DRIVEN DESIGN利用模型来为应用程序解决问题。项目组通

    过知识消化将大量杂乱无章的信息提炼成实用的模型。而MODEL-DRIVEN DESIGN 将 模 型 和 程 序 实 现 过 程 紧 密 结 合 。 UBIQUITOUS

    LANGUAGE则成为开发人员、领域专家和软件产品之间传递信息的渠

    道。

    最终的软件产品能够在完全理解核心领域的基础上提供丰富的功

    能。

    如上所述,MODEL-DRIVEN DESIGN的成功离不开详尽的设计决策。

    在下面几章中我们将会讲述这方面的内容。

    [1].走查,walk through,原来是指一种非正式的代码评审活动,现

    在也广泛用于其他方面,一般是指一步步检查或分步讨论。——译者

    注

    [2].卫语句,guard clause,指起保护作用的语句。——译者注

    [3].STRATEGY一般是指定义一组算法,将每个算法都封装起来,并且

    使它们之间可以互换。策略模式的优点是软件可以由许多可替换的部

    分组成,各个部分之间是弱连接的关系,这样软件具有更强的可扩展

    性、可维护性和可重用性。作者在这里提到策略模式,是指将每个规

    则当成一个算法。——译者注

    [4].清关即结关,习惯上又称通关,是指进口货物、出口货物和转运

    货物进出一国海关或国境时必须向海关申报,办理海关规定的各项手

    续,履行各项法规规定的义务。——译者注

    [5].一种中世纪的仪器,曾用来测量太阳或其他天体的位臵。——译

    者注

    [6].Brian Marick曾向我提及这个示例。第二部分 模型驱动设计的构造块

    为了保证软件实现得简洁并且与模型保持一致,不管实际情况如

    何复杂,必须运用建模和设计的最佳实践。本书既不是一本介绍面向

    对象设计的书,也不是为了提出一些基本的设计原理。领域驱动设计

    改变了某些传统观念的侧重点。

    某些设计决策能够使模型和程序紧密结合在一起,互相促进对方

    的效用。这种结合要求我们注意每个元素的细节。对细节问题的精雕

    细琢能够打造出一个稳定的平台,开发人员可以在这个平台上运用第

    三部分和第四部分中要讲到的建模方法。

    本书中的软件设计风格主要遵循“职责驱动设计”的原则,这个

    原则是在[Wirfs-Brock et al.1990]中提出的,并在[Wirfs-Brock

    2003]中进行了更新。同时本书也大量利用了[Meyer 1988]中所提出的

    “契约式设计”思想。它与其他被广泛采用的面向对象设计最佳实践

    有着基本相同的背景,这些最佳实践在[Larman 1998]等书中给出了描

    述。

    当项目遇到或大或小的困难时,开发人员可能会发现这些原则都

    无法适用于项目当前的状况。为了使领域驱动设计过程更灵活,开发

    人员需要理解上面这些众所周知的基本原理是如何支持MODEL-DRIVEN

    DESIGN的,这样才能在设计过程中做出一些折中选择,而又不脱离正

    确的轨道。

    下面3章的内容是按照“模式语言”(参见附录A)组织的,主要

    说明了细微的模型差别和设计决策是如何影响领域驱动设计过程的。下面的简图是一张导航图,它描述的是本部分所要讲解的模式以

    及这些模式彼此关联的方式。

    共用这些标准模式可以使设计有序进行,也使项目组成员能够更

    方便地了解彼此的工作内容。同时,使用标准模式也使UBIQUITOUS

    LANGUAGE更加丰富,所有的项目组成员都可以使用UBIQUITOUS

    LANGUAGE来讨论模型和设计决策。

    开发一个好的领域模型是一门艺术。而模型中各个元素的实际设

    计和实现则相对系统化。将领域设计与软件系统中的其他关注点分离

    会使设计与模型之间的关系非常清晰。根据不同的特征来定义模型元

    素则会使元素的意义更加鲜明。对每个元素使用已验证的模式有助于

    创建出更易于实现的模型。

    MODEL-DRIVEN DESIGN语言的导航图只有在充分考虑这些基本原理之后,精心设计的模型才能化繁为

    简,创建出项目组成员可以放心地进行组合使用的详细元素。

    第4章 分离领域

    在 软件中,虽然专门用于解决领域问题的那部分通常只占整个软

    件系统的很小一部分,但其却出乎意料的重要。要想实现本书的想

    法,我们需要着眼于模型中的元素并且将它们视为一个系统。绝不能

    像在夜空中辨认星座一样,被迫从一大堆混杂的对象中将领域对象挑

    选出来。我们需要将领域对象与系统中的其他功能分离,这样就能够

    避免将领域概念和其他只与软件技术相关的概念搞混了,也不会在纷

    繁芜杂的系统中完全迷失了领域。

    分离领域的复杂技术早已出现,而且都是我们耳熟能详的,但是

    它对于能否成功运用领域建模原则起着非常关键的作用,所以我们要

    从领域驱动的视角对它进行简要的回顾。

    4.1 模式:LAYERED ARCHITECTURE在一个运输应用程序中,要想支持从城市列表中选择运送货物目

    的地这样的简单用户行为,程序代码必须包括:(1) 在屏幕上绘制一

    个屏幕组件(widget);(2) 查询数据库,调出所有可能的城市;(3)

    解析并验证用户输入;(4) 将所选城市与货物关联;(5) 向数据库提

    交此次数据修改。上面所有的代码都在同一个程序中,但是只有一小

    部分代码与运输业务相关。

    软件程序需要通过设计和编码来执行许多不同类型的任务。它们

    接收用户输入,执行业务逻辑,访问数据库,进行网络通信,向用户

    显示信息,等等。因此程序中的每个功能都可能需要大量的代码来实

    现。在面向对象的程序中,常常会在业务对象中直接写入用户界

    面、数据库访问等支持代码。而一些业务逻辑则会被嵌入到用户界

    面组件和数据库脚本中。这么做是为了以最简单的方式在短期内完

    成开发工作。

    如果与领域有关的代码分散在大量的其他代码之中,那么查看

    和分析领域代码就会变得异常困难。对用户界面的简单修改实际上

    很可能会改变业务逻辑,而要想调整业务规则也很可能需要对用户

    界面代码、数据库操作代码或者其他的程序元素进行仔细的筛查。

    这样就不太可能实现一致的、模型驱动的对象了,同时也会给自动

    化测试带来困难。考虑到程序中各个活动所涉及的大量逻辑和技

    术,程序本身必须简单明了,否则就会让人无法理解。

    要想创建出能够处理复杂任务的程序,需要做到关注点分离——

    使设计中的每个部分都得到单独的关注。在分离的同时,也需要维持

    系统内部复杂的交互关系。

    软件系统有各种各样的划分方式,但是根据软件行业的经验和惯

    例,普遍采用LAYERED ARCHITECTURE(分层架构),特别是有几个层

    基本上已成了标准层。分层这种隐喻被广泛采用,大多数开发人员都

    对其有着直观的认识。许多文献对LAYERED ARCHITECTURE也进行了充

    分的讨论,有些是以模式的形式给出的[Buschmann et al.1996,pp.31-51]。LAYERED ARCHITECTURE的基本原则是层中的任何元素都仅

    依赖于本层的其他元素或其下层的元素。向上的通信必须通过间接的

    方式进行,这些将在后面讨论。

    分层的价值在于每一层都只代表程序中的某一特定方面。这种限

    制使每个方面的设计都更具内聚性,更容易解释。当然,要分离出内

    聚设计中最重要的方面,选择恰当的分层方式是至关重要的。在这

    里 , 经 验 和 惯 例 又 一 次 为 我 们 指 明 了 方 向 。 尽 管 LAYEREDARCHITECTURE的种类繁多,但是大多数成功的架构使用的都是下面这4

    个概念层的某种变体。

    有些项目没有明显划分出用户界面层和应用层,而有些项目则有

    多个基础设施层。但是将领域层分离出来才是实现MODEL-DRIVEN

    DESIGN的关键。

    因此:

    给复杂的应用程序划分层次。在每一层内分别进行设计,使其

    具有内聚性并且只依赖于它的下层。采用标准的架构模式,只与上

    层进行松散的耦合。将所有与领域模型相关的代码放在一个层中,并把它与用户界面层、应用层以及基础设施层的代码分开。领域对

    象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示

    和存储问题,也无需管理应用任务等内容。这使得模型的含义足够

    丰富,结构足够清晰,可以捕捉到基本的业务知识,并有效地使用

    这些知识。

    将领域层与基础设施层以及用户界面层分离,可以使每层的设计

    更加清晰。彼此独立的层更容易维护,因为它们往往以不同的速度发

    展并且满足不同的需求。层与层的分离也有助于在分布式系统中部署

    程序,不同的层可以灵活地放在不同服务器或者客户端中,这样可以

    减少通信开销,并优化程序性能[Fowler 1996]。示例 为网上银行功能分层

    该应用程序能提供维护银行账户的各种功能。其中一个功能就是

    转账,用户可以输入或者选择两个账户号码,填写要转的金额,然后

    开始转账。

    为了让这个例子更容易实现,这里省略了一些主要的技术特性,特别是安全性方面的一些特性。领域设计也尽量简化。(在现实生活

    中,银行业务的复杂性只会增加对LAYERED ARCHITECTURE的需求。)

    此外,这个例子中的基础设施只是为了使程序更简单和清楚一些而已

    ——我并不建议你使用这个设计。简化后的功能所要完成的任务将会

    按照图4-1来分层。

    注意,负责处理基本业务规则的是领域层,而不是应用层——在

    这个例子中,业务规则就是“每笔贷款必须有与其数目相同的借

    款”。

    这个应用程序没有设定转账请求的发起方。程序中假定包含了用

    户输入界面,界面中有账户号码和转账金额的输入字段以及一些命令

    按钮。但是也可以用基于XML的电汇请求来替换,这并不会影响应用层

    及其下面的各层。这种解耦至关重要,这并不是因为在项目中经常需

    要用电汇请求来代替用户界面,而是因为关注点的清晰分离可以使每

    一层的设计更易理解和维护。

    事实上,图4-1本身也略微说明了不分离领域层会出现的问题。这

    张图需要包含从请求到事务控制的所有方面,所以不得不简化领域层

    来保证整个交互过程简单易懂。如果我们专注于研究独立领域层的设

    计,就可以构思并绘制出更好地表达领域规则的模型,也许模型中会

    包含分类账、贷款和借款对象,或者是现金交易对象。图4-1 对象所执行的任务与其所在层一致,并且与同层其他对象的联系更为紧密

    4.1.1 将各层关联起来

    到目前为止,我们的讨论主要集中在层次划分以及如何分层才能

    改进程序各个方面的设计上,特别是集中在领域层上。但是显然,各

    层之间也需要互相连接。在连接各层的同时不影响分离带来的好处,这是很多模式的目的所在。

    各层之间是松散连接的,层与层的依赖关系只能是单向的。上层

    可以直接使用或操作下层元素,方法是通过调用下层元素的公共接

    口,保持对下层元素的引用(至少是暂时的),以及采用常规的交互

    手段。而如果下层元素需要与上层元素进行通信(不只是回应直接查

    询),则需要采用另一种通信机制,使用架构模式来连接上下层,如

    回调模式或OBSERVERS模式[Gamma et al.1995]。最早将用户界面层与应用层和领域层相连的模式是MODEL-VIEW-

    CONTROLLER(MVC,模型—视图—控制器)框架。它是为Smalltalk语

    言发明的一种设计模式,创建于20世纪70年代。随后出现的许多用户

    界面架构都是受到它的启发而产生的。Fowler在[Fowler 2002]中讨论

    了这种模式以及几个实用的变体。Larman也在MODEL-VIEW SEPARATION

    模式中探讨了这些问题,他提出的APPLICATION COORDINATOR(应用协

    调器)是连接应用层的一种方法[Larman 1998]。

    还有许多其他连接用户界面层和应用层的方式。对我们而言,只

    要连接方式能够维持领域层的独立性,保证在设计领域对象时不需要

    同时考虑可能与其交互的用户界面,那么这些连接方式就都是可用

    的。

    通常,基础设施层不会发起领域层中的操作,它处于领域层“之

    下”,不包含其所服务的领域中的知识。事实上这种技术能力最常以

    SERVICE的形式提供。例如,如果一个应用程序需要发送电子邮件,那

    么一些消息发送的接口可以放在基础设施层中,这样,应用层中的元

    素就可以请求发送消息了。这种解耦使程序的功能更加丰富。消息发

    送接口可以连接到电子邮件发送服务、传真发送服务或任何其他可用

    的服务。但是这种方式最主要的好处是简化了应用层,使其只专注于

    自己所负责的工作:知道何时该发送消息,而不用操心怎么发送。

    应用层和领域层可以调用基础设施层所提供的SERVICE。如果

    SERVICE的范围选择合理,接口设计完善,那么通过把详细行为封装到

    服务接口中,调用程序就可以保持与SERVICE的松散连接,并且自身也

    会很简单。

    然而,并不是所有的基础设施都是以可供上层调用的SERVICE的形

    式出现的。有些技术组件被设计成直接支持其他层的基本功能(如为

    所有的领域对象提供抽象基类),并且提供关联机制(如MVC及类似框架的实现)。这种“架构框架”对于程序其他部分的设计有着更大的

    影响。

    4.1.2 架构框架

    如果基础设施通过接口调用SERVICE的形式来实现,那么如何分层

    以及如何保持层与层之间的松散连接就是相当显而易见的。但是有些

    技术问题要求更具侵入性的基础设施。整合了大量基础设施需求的框

    架通常会要求其他层以某种特定的方式实现,如以框架类的子类形式

    或者带有结构化的方法签名。(子类在父类的上层似乎是违反常理

    的,但是要记住哪个类反映了另一个类的更多知识。)最好的架构框

    架既能解决复杂技术问题,也能让领域开发人员集中精力去表达模

    型,而不考虑其他问题。然而使用框架很容易为项目制造障碍:要么

    是设定了太多的假设,减小了领域设计的可选范围;要么是需要实现

    太多的东西,影响开发进度。

    项目中一般都需要某种形式的架构框架(尽管有时项目团队选择

    了不太合适的框架)。当使用框架时,项目团队应该明确其使用目

    的:建立一种可以表达领域模型的实现并且用它来解决重要问题。项

    目团队必须想方设法让框架满足这些需求,即使这意味着抛弃框架中

    的一些功能。例如,早期的J2EE应用程序通常都会将所有的领域对象

    实现为“实体bean”。这种实现方式不但影响程序性能,还会减慢开

    发速度。现在,取而代之的最佳实践是利用J2EE框架来实现大粒度对

    象,而用普通Java对象来实现大部分的业务逻辑。不妄求万全之策,只要有选择性地运用框架来解决难点问题,就可以避开框架的很多不

    足之处。明智而审慎地选择框架中最具价值的功能能够减少程序实现

    和框架之间的耦合,使随后的设计决策更加灵活。更重要的是,现在

    许多框架的用法都极其复杂,这种简化方式有助于保持业务对象的可

    读性,使其更富有表达力。架构框架和其他工具都在不断的发展。新框架将越来越多的应用

    技术问题变得自动化,或者为其提供了预先设定好的解决方案。如果

    框架使用得当,那么程序开发人员将可以更加专注于核心业务问题的

    建模工作,这会大大提高开发效率和程序质量。但与此同时,我们必

    须要保持克制,不要总是想着要寻找框架,因为精细的框架也可能会

    束缚住程序开发人员。

    4.2 领域层是模型的精髓

    现在,大部分软件系统都采用了LAYERED ARCHITECTURE,只是采

    用的分层方案存在不同而已。许多类型的开发工作都能从分层中受

    益。然而,领域驱动设计只需要一个特定的层存在即可。

    领域模型是一系列概念的集合。“领域层”则是领域模型以及所

    有与其直接相关的设计元素的表现,它由业务逻辑的设计和实现组

    成。在MODEL-DRIVEN DESIGN中,领域层的软件构造反映出了模型概

    念。

    如果领域逻辑与程序中的其他关注点混在一起,就不可能实现这

    种一致性。将领域实现独立出来是领域驱动设计的前提。

    4.3 模式:THE SMART UI“反模式”

    上面总结了面向对象程序中广泛采用的LAYERED ARCHITECTURE模

    式。在项目中,人们经常会尝试分离用户界面、应用和领域,但是成

    功分离的却不多见,因此,分层模式的反面就很值得一谈。

    许多软件项目都采用并且应该会继续采用一种不那么复杂的设计

    方法,我称其为SMART UI (智能用户界面)。但是SMART UI是另一种

    设计方法,与领域驱动设计方法迥然不同且互不兼容。如果你选择了SMART UI,那么本书中所讲的大部分内容都不适合你。我感兴趣的是

    那些不应该使用SMART UI的情况,这也是我半开玩笑地称其为“反模

    式”的原因。本节讨论SMART UI是为了提供一种有益的对比,其将帮

    助我们认清在本书后面章节中的哪些情况下需要选择相对而言更难于

    实现的领域驱动设计模式。

    假设一个项目只需要提供简单的功能,以数据输入和显示为主,涉及业务规则很少。项目团队也没有高级对象建模师。

    如果一个经验并不丰富的项目团队要完成一个简单的项目,却

    决定使用MODEL-DRIVEN DESIGN以及LAYERED ARCHITECTURE,那

    么这个项目组将会经历一个艰难的学习过程。团队成员不得不去掌

    握复杂的新技术,艰难地学习对象建模。(即使有这本书的帮助,这也依然是一个具有挑战性的任务!)对基础设施和各层的管理工

    作使得原本简单的任务却要花费很长的时间来完成。简单项目的开

    发周期较短,期望值也不是很高。所以,早在项目团队完成任务之

    前,该项目就会被取 ......

您现在查看是摘要介绍页, 详见PDF附件(21400KB,614页)