12/9-DDD中国峰会部分文章节选
DDD不是架构,而是一种方法论(Methodology)。根据维基百科:Methodology is the systematic, theoretical analysis of the methods applied to a field of study,DDD正是针对软件领域提供的系统与理论分析方法。Eric在创造性地提出DDD时,实则是针对当时项目中聚焦在Data(主要是DB Schema)为核心的系统建模方法的批判。这种面向数据的建模方式无法应对日渐复杂的业务逻辑,也无法更好地应用当时正沸沸扬扬的OO设计思想。这是设计观念的转变,蕴含了全新的设计思想、设计原则与设计过程。
坦白说,Eric Evans的DDD奠基之作《Domain-Driven Design》并没有非常清晰的系统脉络,战略设计与战术设计也未成体系。Eric的DDD其实没有解决三个问题:
- 如何进行领域建模
- 如何识别Bounded Context
- 如何在战术层面寻找对象
DDD不是架构(设计)方法,因此不能把每个设计细节具象化。DDD是一套体系,这就决定了它必须具有开放性,在这个体系中你可以用任何一种方法来解决这些问题。但如果这些关键问题如果没有具体落地的方法,可能会让团队无可适从。这其实也是DDD在许多项目中难以推行的部分原因。
DDD的战略建模与战术建模
战略建模-Strategic Modeling:
- 限界上下文(Bounded Context)
- 上下文映射图(Context Mapping)
战术建模-Tactical Modeling:
- 聚合-Aggregate
- 实体-Entity
- 值对象-Value Objects
- 资源库-Repository
- 领域服务-Domain Services
- 领域事件-Domain Events
- 模块-Modules
Bound Context(BC)
先引用一段文章内容DDD分层架构的三种模式:
UL(Ubiquitous Language,通用语言)是团队共享的语言,是DDD中最具威力的特性之一。不管你在团队中的角色如何,只要你是团队的一员,你都将使用UL。由于UL的重要性,所以需要让每个概念在各自的上下文中是清晰无歧义的,于是DDD在战略设计上提出了模式BC(Bounded Context,限界上下文)。UL和BC同时构成了DDD的两大支柱,并且它们是相辅相成的,即UL都有其确定的上下文含义,而BC中的每个概念都有唯一的含义。
一个业务领域划分成若干个BC,它们之间通过Context Map进行集成。BC是一个显式的边界,领域模型便存在于这个边界之内。领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。
从广义上来讲,领域即是一个组织所做的事情以及其中所包含的一切,表示整个业务系统。由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建一个单一的、内聚的和全功能式的模型。然而,这并不是我们使用DDD的目标。正好相反,领域模型存在于BC内。
关于BC,有一个很形象的类别,细胞和细胞膜的类比:
BC可以类比为细胞膜,大型系统由于其复杂性,对于一个对象如果采取统一建模方式,可能会产生不可预测的效果。例如书中提到的客户发票中的收费对象故障,其实类似问题也在很多地方可以看到。
大型系统领域模型的完全统一是不可行的,也不是一种经济有效的方法。任何一个大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现 bug ,变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。当不同的团队不得不共同工作于一个模型时,我们必须小心不要踩到别人的脚。每当要时刻意识到任何针对模型的变更都有可能破坏现有的功能。当使用多个模型时,每个人在自己的模型之上可以自由地工作。我们都知道自己模型的界限,都恪守在这些边界里。我们需要确保模型的纯洁、一致和统一。
因此明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现〈代码和数据库模式等〕来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
BC明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在 CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。
CONTINUOUS INTEGRATION(持续集成-CI)
定义完一个 BC后,必须让它持续保持合理化。当很多人在同一个BC中工作时.模型很容易发生分裂。团队越大,问题就越大,但即使是3、4个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的 CONTEXT,最终又难以保持集成度和一致性。
所以一个方法是需要经常保持BC的一致,以便当模型发生分裂时,可以迅速发现并纠正问题。像领域驱动设计中的其他方法一样,CI也有两个级别的操作:(1)模型溉念的集成;(2)实现的集成。团队成员之间通过经常沟通来保证概念的集成。团队必须对不断变化的模型形成一个共同的理解,最基本的方法是对 UL多加锤炼,在讨论模型和应用程序时要坚持使用UL。同时,实际工件是通过系统性的合并/构建/测试过程来集成的,这样的过程能够尽早暴露出模型的分裂问题。
建立一个经常把所有代码和其他实现工件合并到一起的过程,并通过自动测试来快速查明模型的分裂问题。严格坚持使用 UL,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识。
最后,不要在持续集成中做一些不必要的工作。CI只有在BC中才是重要的。相邻 CONTEXT中的设计问题(包括转换)不必以同一个步调来处理。
上下文图(Context Map)
多个系统之间会发生关系,存在交互,这也必然会在各自的BC上有所表现。在项目中创建一个所有模型上下文的全局视图,可以减少混乱。上下文图(Context Map)便是表示各个系统之间关系的总体视图。
建模的过程需要识别每个模型在项目中的作用,并定义其 BC,这包括非面向对象子系统的隐含模型。为每个 BC命名,并把名称添加到 UL。描述模型之间的接触点,明确每次交流所需的转换,并突出任何共享的内容。画出现有的范围。为稍后的转换做好准备。
在Context Map中可以有如下几种形式来表征限界上下文之间的关系,简介如下(具体可阅读原书):
1. 共享内核(Shared Kernel)
当不同团队开发一些紧密相关的应用程序时,团队之间需要进行协调,通常可以将两个团队共享的子集剥离出来形成共享内核(Shared Kernel),双方进行持续集成(Continuous Integration)。共享内核(Shared Kernel)是业务领域中公共的部分,同时也是团队间容易达成且必须达成共识的领域部分。
2. 客户/供应商(Customer/Supplier)
不同系统之间存在依赖关系时,下游系统依赖上游系统,下游系统是客户,上游系统是供应商,双方协定好需求,由上游系统完成模型的构建和开发,并交付给下游系统使用,之后进行联调、测试。这种模式建立在团队之间友好合作和支持的情况下。
当两个具有上游/下游关系的团队不归同一个管理者指挥时,Customer/Supplier这样的合作模式就不会奏效。勉强应用这种模式会给下游团队带来麻烦。
3. Conformist(追随者)
当两个开发团队具有上/下游关系时,如果上游团队没有动机来满足下游团队的需求,那么下游团队将无能为力。出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。下游项目只能被搁置.直到团队最终学会利用现有条件自力更生为止。下游团队不会得到根据他们的需求而量身定做的接口。
这时候“客户/供应商”模式就不凑效了,那么下游系统只能去追随上游系统,下游系统严格遵从上游系统的模型,简化集成。
通过严格遵从上游团队的模型,可以消除在 BC之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择 Conformist模式可以极大地简化集成。此外,这样还可以与供应商团队共享一种 UL。供应商处于驾驶者的位置上,因此最好使他们能够容易沟通。
4. 防腐层(Anticorruption Layer)
前面介绍了在两个BC之间集成时可以进行的各种合作,从高度合作的 Shared Kernel模式或 Customer/Supplier Team到单方面的Conformist模式。如果是一种更悲观的关系,假设一个团队既不可能与另一个团队合作也无法利用他们的设计时,该如何应对。
这时候我们需要使用防腐层(Anticorruption Layer)模式将上游系统的影响降低。
5. 公开主机服务(Open Host Service)
当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。如果一个子系统有某种内聚性,那么或许可以把它描述为一组 Service,这组 Service满足了其他子系统的公共需求。
公开主机服务(Open Host Service)能够允许系统将一组Service公开出去公其他系统访问。定义一个协议,把你的子系统作为一组 Service供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。
6. 各行其道(Separate Way)
当两个系统之间的关系并非必不可少时,两者完全可以彼此独立,各自独立建模,独立发展,互不影响。
Context Map例子
U表示上游(Upstream)的被依赖方,D表示下游(Downstream)的依赖方。防腐层(ACL)放在下游,将上游的消息转化为下游的领域模型。