Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《领域驱动设计》——Eric Evans #14

Open
thzt opened this issue Nov 14, 2016 · 0 comments
Open

《领域驱动设计》——Eric Evans #14

thzt opened this issue Nov 14, 2016 · 0 comments

Comments

@thzt
Copy link
Owner

thzt commented Nov 14, 2016

很多因素可能会导致项目偏离轨道,如官僚主义,目标不清,资源缺乏,等等。
但真正决定软件复杂性的是设计方法。
当复杂性失去控制时,开发人员就无法很好的理解软件,因此无法轻易、安全的更改和扩展它。
而好的设计则可以为开发复杂特性创造更多机会。

很多应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。
当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也是无济于事。

极限编程承认设计决策的重要性,但强烈反对预先设计。
相反,它将相当大的精力投入到促进沟通和提高项目快速变更能力的工作中。
具有这种反应能力之后,开发人员就可以在项目的任何阶段只利用『最简单而管用的方案』,
然后不断进行重构,一步一步做出小的设计改进,最终得到满足客户真正需要的设计。

这种极端的简约主义是解救那些过度追求设计的执迷者的良方。
那些几乎没有价值的繁琐文档只会为项目带来麻烦。
项目受到『分析瘫痪症』的困扰,团队成员十分担心会出现不完美的设计,这导致它们根本没法取得进展。
这种状况必须得到改变。

遗憾的是,这些有关过程的思想可能会被误解。
每个人对『最简单』都有不同的定义。
持续重构其实是一系列小规模的重新设计,没有严格设计原则的开发人员将会创建出难以理解或修改的代码,
这恰好与敏捷的精神相悖。
而且,虽然对以外需求的担心常常导致过度设计,但试图避免过度设计又可能走向另一个极端,
不敢做任何深入的设计思考。

XP最适合那些对设计的感觉很敏锐的开发人员。
XP过程假定人们可以通过重构来改进设计,而且可以经常、快速的完成重构。
但重构本身的难易程度取决于先前的设计选择。

模型是一种知识形式,它对知识进行有选择的简化和有目的的结构化。
适当的模型可以使人理解信息的意义,并专注于问题相关的信息。

软件的核心是其为用户解决领域相关的问题的能力。
所有其他特性,不管有多么重要,都要服务于这个基本目的。
当领域很复杂时,这是一项艰巨的任务,要求高水平技术人员的共同努力。
开发人员必须钻研领域以获取业务知识。
他们必须练习建模技巧,并精通领域设计。

然而,在大多数软件项目中,这些问题并未引起足够的重视。
大部分有才能的开发人员对学习与他们的工作领域有关的知识不感兴趣,
更不会下力气去扩展自己的领域建模技巧。
技术人员喜欢那些能够练习技巧的可量化问题。
领域工作很繁杂,而且要求掌握很多复杂的新知识,而这些新知识看似对提高计算机科学家的能力并无裨益。

相反,技术人员更愿意从事精细的框架工作,试图用技术来解决领域问题。
他们把学习领域知识和领域建模的工作留给别人去做。
软件核心的复杂性需要我们直接面对和解决,如果不这样做,必将导致工作重点的偏离。

技术人员通常认为业务专家最好不要接触领域模型,他们认为:
领域模型对他们来说太抽象了,他们不理解抽象,这样我们就不得不用他们的术语来收集需求。
过于抽象?那你怎么知道抽象是否合理?你是否像他们一样深入的理解领域?
如果连经验丰富的领域专家都不能理解模型,那么模型一定是有问题的。

务必要记住模型不是图,图的目的是帮助表达和解释模型。
代码可以充当设计细节的存储库。
清晰的Java代码与UML具有同样的表达能力。

文档不应再重复表示代码已经明确表达出的内容。
代码已经含有各个细节,它本身就是一种精确的程序行为说明。
文档应努力寻求生存之道,并保持最新。

设计文档的最大价值是解释模型的概念,帮助在代码的细节中指引方向,或许还可以帮助人们深入了解模型的使用风格。

那些压根儿就没有领域模型的项目,仅仅通过编写代码来实现一个又一个的功能,
它们无法利用模型进行知识的消化和沟通,如果涉及复杂的领域就会使项目举步维艰。
另一方面,许多复杂的项目确实在尝试使用一些领域模型,但是并没有把代码的编写与模型紧密联系起来。
这些项目所设计的模型,在项目初期还可能用来做一些探索工作,但是随着项目的进展,
这些模型与项目渐行渐远,甚至还会起到误导作用。
所有在模型上花费的精力都无法保证程序设计的正确性,因为模型和设计是不同的。

程序代码就是模型的表达,修改代码可能就是改变模型。

人们总是把软件开发比喻成制造业。
通过这个比喻可以推断出一个结论:经验丰富的工程师做设计工作,而技能水平较低的劳动力负责组装产品。
这种做法使许多项目陷入困境,原因很简单,在软件开发中设计是无处不在的。
开发团队中的每个成员都有自己的职责,但是将分析,建模,设计和编程工作完全分离会产生不良影响。

开发人员-领域模型-领域专家

任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。
任何负责修改代码的人员,则必须学会用代码来表达模型。
每一个开发人员都必须不同程度的参与模型讨论并且与领域专家保持联系。

给复杂的应用程序分层。
在每一层内分别进行设计,使其具有内聚性并且只依赖于它的下层。
采用标准的架构模式,只与上层进行松散连接。
将所有与领域模型相关的代码放在一个层中,并把它与用户界面层,应用层以及基础设施层的代码分开。
领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,
也无需管理应用任务等内容。
这使得模型的含义足够丰富,结构足够清晰,可以捕捉到基本的业务知识,并有效的使用这些知识。

用户界面层:负责向用户显示信息和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人。
应用层:定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。
领域层(或模型层):负责表达业务概念,业务状态信息以及业务规则。领域层是业务软件的核心。
基础设施层:为上面各层提供通用的技术能力。为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持四个层次间的交互模式。

注意,负责处理基本业务规则的是领域层,而不是应用层。

各层之间是松散连接的,层与层的依赖关系只能是单向的。
上层可以直接使用或操作下层元素,方法是通过调用下层元素的公共接口,保持对下层元素的引用,以及采用常规的交互手段。
而如果下层元素需要调用上层元素进行通信(不只是回应直接查询),则需要采用另一种通信极致,使用架构模式来连接上下层,比如回调模式或观察者模式。

软件的最终目的是为用户服务。但首先它必须为开发人员服务。
在强调重构的软件开发过程中尤其如此。
随着程序的演变,开发人员将重新安排并重写每个部分。
当具有复杂行为的软件缺乏一个良好的设计时,重构或元素的组合会变得很困难。
一旦开发人员不能十分肯定的预知计算的全部含意,就会出现重复。
当设计元素都是整块的而无法重新组合的时候,重复就是一种必然的结果。

如果软件没有一个条理分明的设计,那么开发人员不仅不愿意仔细的分析代码,
而且修改代码也可能会产生问题,要么加重了代码的混乱状态,要么由于某种未预料到的依赖性而破坏了某个结构。
在任何一种系统中(除非是一些非常小的系统),这种不稳定性使我们很难开发出丰富的功能,而且限制了重构和迭代式的精化。

为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,
设计必须要让人们乐于使用,而且易于做出修改。
这就是柔性设计。(supple design)

很多过度设计借着柔性设计的名义而自认为是正当的,但是过多的抽象层和间接设计常常成为项目的绊脚石。
看一下真正为用户带来强大功能的软件设计,你会发现它们通常有一些非常简单的部分。
简单并不容易做到,为了把创建的元素装配到复杂系统中,而且装配之后仍然能够理解它们,
必须坚持模型驱动的设计方法,与此同时还要坚持适当严格的设计风格。

早起设计版本通常达不到柔性设计的要求,由于项目的时间限制和预算的缘故,很多设计一直就是僵化的。
我也从未见过哪个大型程序自始至终都是柔性的。
但是,当复杂性阻碍了项目的前进时,就需要仔细修改最关键,最复杂的地方,
使之变成柔性设计,这样才能突破复杂性带给我们的限制,而不会陷入遗留代码维护的麻烦中。

如果开发人员为了使用一个组件而必须要去研究它的实现,那么就失去了封装的价值。
当某个人开发的对象或操作被别人使用时,如果使用这个组件的新的开发者不得不根据其实现来推测其用途,
那么他推测出来的可能并不是那个操作或类的主要用途。
如果这不是那个组件的用途,虽然代码暂时可以工作,但设计的概念基础已经被误用了,两位开发人员的意图也是背道而驰。

在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。
这样可以使客户开发人员不必去理解内部的细节,以便团队成员可以迅速推断出它们的意义。
在创建一个行为之前先为它编写一个测试,这样可以促使你站在客户开发人员的角度来思考它。

所有复杂的机制都应该封装到抽象接口的后面,接口只表明意图,而不表明方式。

多个规则或计算组合的相互作用所产生的结果是很难预测的。

声明式设计,对于不同的人来说具有不同的意义,但通常是指一种编程方式,
把程序或程序的一部分写成一种可执行的规格。
使用声明式设计时,软件实际上是由一些非常精确的属性描述来控制的。

声明式语言并不足以表达一切所需的东西,它把软件束缚在一个由自动部分构成的框架之内,使软件很难扩展到这个框架之外。
代码生成技术破坏了迭代循环,它把生成的代码合并到手写的代码中,使得重新生成的破坏作用变得更大。
许多声明式设计的尝试带来了意想不到的后果,由于开发人员收到框架局限性的约束,为了交付工作职能先处理重要问题,
而搁置其他一些问题,这导致模型和应用程序的质量严重下降。

很多声明式方法被开发人员有意或无意忽略之后会遭到破坏。
当系统很难使用或限制过多时,就会发生这种情况,为了获得声明式程序的利益,每个人都必须遵守框架的规则。
声明式设计的最大价值是用一个范围非常窄的框架来自动处理设计中某个特别单调且容易出错的方面,
例如持久化和对象关系映射。
最好的声明式设计,能够使开发人员不必去做那些单调乏味的工作,同时又完全不限制他们的设计自由。

特定于领域的语言,是一种有趣的方法,它有时也是一种声明式语言。
采用这种编码风格时,客户代码是用一种专门为特殊领域的特殊模型定制的语言而编写的。
例如,运输系统的语言可能包括cargo(货物)或route(路线)这样的术语,以及一些用于组合这些术语的语法。
然后,程序通常会被编译成一种传统的面向对象语言,由一个类库为这些术语提供实现。

在这样的语言中,程序可以具有极强的表达能力。
特定于领域的语言,是一个令人振奋的概念。但在我所见过的基于面向对象技术的方法中,这种语言也存在自身的缺陷。
为了精化模型,开发人员需要修改语言。这可能涉及修改语法声明和其他语言解释功能,以及修改底层的类库。
另一个缺点是,当模型被修改时,很难对客户代码进行重构,使之与修改之后的模型及与其相关的特定于领域的语言保持一致。
当然,有人认为可以通过技术修复来解决重构问题。

这种技术可能在非常成熟的模型中能够发挥出最大作用,在这样的模型中,客户代码可能是由另一个不同的团队编写的。
通常,这样的设置会导致产生一种有害的结果,团队被分成两部分,框架由那些技术水平较高的人来构建,而应用程序则由那些技术水平较差的人来构建了,
但也并不是非得如此。

我个人最喜欢的框架是数学,数学的强大功能令人惊奇,它可以用基本数学概念把一些复杂的问题提取出来。
很多领域都涉及数学,我们要寻找这样的部分,并把它挖掘出来。
专门的数学很整齐,可以通过清晰的规则进行组合,并且很容易理解。

如果一直等到完全证明了修改的合理性之后才去修改,那么可能要等待太长时间了。
项目正在承受巨大的耗支,推迟修改将使修改变得更难执行,因为要修改的代码已经变得更加精细,并更深的嵌入到其他代码中。
持续重构渐渐被认为是一种『最佳实践』,但大部分项目团队仍然对它抱有很大的戒心。

人们虽然看到了修改代码会有风险,还要花费开发时间;
却不容易看到的是维持一个拙劣设计也有风险,以及迁就这种设计也要付出代价。
想要重构的开发人员,往往被要求证明其重构的合理性。虽然这看似合理,但这使得一个本来就很难进行的工作变得几乎不可能完成,
而且会限制重构的进行(或者人们只能暗地里进行)。
软件开发并不是一个可以完全预料到后果的过程,人们无法准确的计算出某个修改会带来哪些好处,或者是不做某个修改会付出多大代价。

在探索领域的过程中、在培训开发人员的过程中,以及在开发人员与领域专家进行思想交流的过程中,
必须始终坚持把『通过重构得到更深层理解』作为这些工作的一部分。
因此,当发生以下情况时,就应该进行重构了:
设计没有表达出团队对领域的最新理解;
一些重要的概念被隐藏在设计中了,而且你已经发现了把它们呈现出来的方法;
发现了一个能令某个重要的设计部分变得更灵活的机会。

大型系统领域模型的完全同意是不可行的,也不是一种经济有效的做法。

细胞之所以会存在,是因为细胞膜定义了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。

任何一个大型项目都会存在多个模型。
而当基于不同模型的代码被组合到一起后,软件就会出现bug,变得不可靠和难以理解。
团队成员之间的沟通变得混乱。
人们往往弄不清楚一个模型不应该在哪个上下文中使用。

因此,明确定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式)等设置模型的边界。
在这些边界中严格保持模型的一致性,而不要受到外界之外问题的干扰和混淆。

建立一个经常把所有代码和其他实现工件合并到一起的过程,并通过自动测试来快速查明模型的分裂问题。
以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能打成一个共识。

如果下游团队对变更更具有否决权,或请求变更的程序太复杂,那么上游团队的开发自有就会受到限制。
由于担心破坏下游系统,上游团队甚至会受到抑制。
同时,由于上游团队掌握优先权,下游团队有时也会无能为力。
因此,在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。
根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。
两个团队一起开发自动验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。
这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。

当两个开发团队具有上下游关系时,如果上游团队没有动机来满足下游团队的需求,那么下游团队将无能为力。
由于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。
下游团队由于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。
下游项目只能被搁置,直到团队最终学会利用现有条件自力更生为止。
下游团队不会得到根据他们需求而量身定做的接口。

通过严格遵从上游团队的模型,可以消除转换复杂性。
尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型。
此外,这样还可以与供应商团队共享模型术语,供应商处于驾驶者的位置上,因此最好使他们能够容易沟通。
他们从利他主义的角度出发,会与你分享信息。
人们在主观上不愿意这样做,因此有时本应该这样选择时,却没有这样选择。

在设计大型系统时,有很多有用的组件,它们都很复杂而且绝对有必要把它们做好,
这导致真正的业务资产,领域模型,被掩盖和忽略了。

一个严峻的现实是我们不可能对所有设计部分进行同等的精化,而是必须分出优先级。
为了使模型领域成为有价值的资产,必须整齐的梳理出模型的真正核心,并完全根据这个核心来创建程序的功能。
但本来就稀缺的高水平开发人员往往会把工作重点放在技术基础设施上,或者只是去解决那些不需要专门领域知识就能理解的领域问题(这些问题都已经有了很好的定义)。

在制定项目规划的时候,必须把资源分配给模型和设计中最关键的部分。
要想达到这个目的,在规划和开发期间每个人都必须识别和理解这些关键部分。
这些部分是应用程序的标志性部分,也是目标应用程序的核心目标,它们构成了核心领域(core domain)。
核心邻域是系统中最优价值的部分。

因此,对模型进行提炼,找到核心邻域,并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。
最有价值和最专业的概念要轮廓分明,尽量压缩核心领域。
让最有才能的人来开发核心领域,并相应的招募新人来补充这些人空出来的位置。
在核心领域中努力开发能够确保实现系统蓝图的深层模型和柔性设计。
仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的核心。

软件设计往往非常抽象且难于掌握,开发人员和用户都需要一些切实可行的方式来理解系统,并共享系统的一个整体视图。

制定战略设计决策的6个要点:
(1)决策必须传达到整个团队,
(2)决策过程必须收集反馈意见,
(3)计划必须允许演变,
(4)架构团队不必把所有最好、最聪明的人员都吸收进来
这样往往会把那些技术能力较差的人留下来实际构建应用程序,但要想开发出优秀的应用程序,是需要设计技巧的,因此这样的安排注定会失败。
即使战略团队建立了一个伟大的战略设计,应用程序开发团队也没有能力把它实现出来。
所有应用程序团队都应该有一些技术能力很强的设计人员,而且任何从事战略设计的团队也都必须具有领域知识,这两者都是非常重要的。
(5)战略设计需要遵守简约和谦逊的原则
(6)对象的职业要专一,而开发人员应该是多面手

有一种态度肯定会使框架流于失败——不要编写『傻瓜式』的框架。
在划分团队时,如果认为一些开发人员不够聪明,无法胜任设计工作,而让他们去做开发工作,
那么这种态度可能会导致失败,因为他们低估了应用程序开发的难度。
如果这些人在设计方面不够聪明,就不应该让他们来开发软件。
如果他们足够聪明,那么用『傻瓜式』的框架来应付他们只会为他们造成障碍,使他们得不到所需的工具。

变更是软件的固有性质。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant