在开发一个微服务之前,我们要设计微服务。设计微服务和领域驱动设计(DDD)有密切的关系,DDD有助于我们设计微服务,所以我们这一节主要讲下领域驱动设计的基本概念、建模方法、架构等,以对我们设计微服务提供指导。
领域驱动设计DDD是目前比较火的一个软件架构术语,在我看来,这个其实是业务驱动IT的一个具体体现。DDD的核心思想是在做IT设计之前,要对业务有深入的理解。DDD告诉了我们领域建模的方法并理论化。
1. DDD的基本概念
1)领域:领域就是一个组织所做的事情以及其中包含的一切,每个组织都有它的做事方式和业务范围,这个业务范围及在其中所进行的活动便是领域。对领域最了解的是业务专家,而不是IT人员。
2)模型:对领域的抽象和提炼,是对领域问题的思考过程的总结,由业务分析人员完成。模型承上启下:向上,业务分析人员需要用模型和业务专家沟通;向下,业务分析人员用模型指导软件设计师进行设计并开发软件。模型的首要要求是一致性,同一模型必须是一致的、保持不变的。
3)上下文:可以认为是模型的势力地盘,我的地盘我做主,大家要各司其职,不要越界。使用一个模型之前要进行哪些操作,使用一个模型之后要进行哪些善后。每个模型都有缺省的上下文,当业务比较复杂,特别是要和存续系统对接时,模型的上下文边界就很重要。
2. 领域驱动设计的战略建模(原则)
也可以说是原则,但我更愿意称之为战略建模,就是在项目开始要从宏观的层面划分清楚业务领域,各业务领域能各自为政(限界上下文),独立进化,对外又能形成一个统一的有机体(上下文映射)
1)限界上下文:
把模型的上下文限定在一定范围,模型负责哪些事情,不负责哪些事情有清晰的定义。界定模型的上下文的原因是保持模型的一致性,不会受外界因素的干扰。在《领域驱动设计》一书里,将限界上下文比喻成细胞膜,很形象:”细胞膜不仅仅能把细胞内部和外部区分开,而且还能决定通过的物质“。
一般根据下面的因素界定模型的上下文:团队的组织结构、现有的代码库模块的划分、数据库的Schema等。推荐的做法是为每个领域创建一个独立的模型,该模型的上下文是该领域或该领域的子集。
2) 上下文映射:
描述不同的上下文之间的交互关系。业务领域之间不是老死不相往来,相反,业务领域之间存在各种各样交互。交互有下面的类别:
a)共享内核:两个或多个限界上下文共享同一模型子集及模型相关联的逻辑、代码。团队之间需要统一共享,并且对共享内核的修改要征得另外团队的同意。
b)客户-供应商关系:两个限界上下文是上下游的关系。处于下游的限界上下文(客户)严重依赖于处于上游的限界上下文(供应商)的输出,比如在电商领域,报表限界上下文严重依赖于在线购物限界上下文的输出(输出会保存到数据库里“客户”和“供应商”应定时安排会议,”客户“提交自己的需求,”供应商“对”客户“的需求进行排期开发。“供应商”会开发一些自己不需要但是“客户“需要的功能和接口。接口是联系“客户”与“供应商”的纽带,需要好好维护。
c)顺从者:在客户-供应商关系里,当供应商没有意愿满足客户的需求时,客户只能被动接受供应商提供的模型和接口。供应商毕竟有自己要处理的事情,不可能靠“利他“理念推动供应商一直满足客户的需求。”利己“是人的天性。对于“客户”,可以走的一条路是自力更生,创建自己的模型满足自己的需求,并逐步减少对“供应商”的依赖。一旦”客户“有了自己的模型,需要防崩溃层进行对”供应商“提供的接口进行翻译转换。
d)防崩溃层/反腐层/缓冲层:当客户-供应商关系变为”顺从者“关系,或者需要和存续系统进行交互时,需要防崩溃层对上游提供的信息和数据做出翻译,类似于《设计模式》里的Adapter。“客户”的防崩溃层通过调用“供应商”提供的服务来获取所需的数据或信息,经过防崩溃层的翻译、转换、适配,转变成和“客户”端内模型一致的数据。
e)隔离通道:当两个系统之间没有或者很少交集,或者没有上下游关系时,或者系统之间集成的难度很大而价值很小时,就可以考虑隔离通道。每个系统都可以用如一个大型企业里的人力资源领域、供应链领域、社区领域。当我们拿到一个新的需求时,我们应该思考可否把它划分为两个或多个没有相通之处的部分。如果可以,我们可以创建独立的限界上下文,并独立建模。
f)开发主机服务:当有上下游关系,不论是在客户-供应商或者顺从者关系下,作为供应商需要将和外面限界上下文交互的实体、逻辑包装成服务开发出去。应该定义一种协议,并将该协议开放出去,这样其它上下文都可以通过该协议访问。值得注意的是,当有特殊的需求,需要客户端自己采用防崩溃层进行翻译,而不是扩展协议,以保证协议的简单和连贯性。
g)提炼:抓住问题的本质(主要矛盾),提炼业务的核心领域,围绕核心领域投入重兵,其它的为支撑领域为核心领域提供支撑。业务分析人员、软件设计人员应对核心领域的细节多加关注,这些细节是系统成功的关键。
3. DDD的战术建模
战术建模是指将不同的领域对象进行分类,并为之建立模型
1)实体:拥有唯一的标识符;标识符在生命周期里不会发生变化;其它属性在生命周期内可以发生变化;需要被跟踪
实体是DDD里一个很重要的概念,在DDD的开始就要定义实体,围绕实体开展工作。
实体可以采用普通旧式Java对象(PO JO,Plain Old Java Object)描述。
2)值对象:Value Object,在该对象的生命周期内,属性不会有任何改变的对象;值对象是不可变的,是可以共享的。值对象应该很小,也可以很简单。
从实现上来讲,一般是值对象设置为类的私有属性,通过构造函数传入值对象具体的值,当需要一个不同的值时,重新在构造函数里传入不同的值创建新的对象。
3)服务及服务对象:在领域建模的过程中,我们会发现,有些活动不属于任一个实体或值对象的职责,反而实体或值对象是这些活动操作的对象,这些活动对整个领域来说有很重要,把这些活动安排到任何一个被操作的对象里,都会破环该实体或值对象。从面向对象编程的视角,我们会把这些活动归类到一些对象里,这些对象叫服务对象,这些活动叫服务。
服务对象不具备内部的状态,它的唯一目的是提供操作领域内实体或值对象的活动。实体或值对象属于服务的被操作对象,从语意上讲,实体或值对象属于宾语,而不是主语。例如:生成报告,这个活动里,报告属于宾语,属于被操作的对象,因而生成报告不属于报告的职责,要单独创建一个服务对象来执行这一活动。
服务也属于领域模型的一部分,大部分的服务都会在DDD架构模型里的领域层。
4) 模块:通过高内聚、低耦合的原则将领域划分为不同的模块
实际操作中,一般把操作相同数据的或完成同一业务功能的部件划分到一个模块
模块的划分对于设计来说粒度还是太粗,所以需要将模块继续细分到聚合和聚合根
5) 聚合和聚合根:聚合用来定义对象的所有权和边界。
聚合:针对数据变化可以考虑成一个单元的一组关联的实体和值对象。比如,要删除时,会一起被删除;要创建时,会一起被创建。
聚合使用聚合根将内部和外部的对象划分开来,聚合根是一个实体,是外部可以访问的唯一对象。
划分聚合时要考虑的原则:
a)数据的一致性:针对数据变化可以考虑成一个单元。执行上,聚合外部的对象只持有对根的引用,而不能直接对聚合内的实体进行访问和更改。对聚合内部的实体和对象的访问必须通过根对象。在一个聚合内部,根可以修改其它的实体,但这是可控的。如果根从内存中被删除,聚合内的其他对象也将被删除。如果一个聚合中的对象被保存在数据库里,通过查询来获得的只有根对象,其他的对象只能通过从根对象出发的导航关联关系获取
b)聚合应尽量小,尽量简单和容易理解,而不是尽量完整。所以,对于聚合,一个工作思路就是对聚合内实体的关系进行简化,尽量把关系对应成1对1的关系,比如通过删除非基本的关联关系、增加约束等方式
6) 工厂:创建对象的方法,将聚合作为一个整体来进行创建。可以参见《设计模式》里的工厂模式或者抽象工厂模式。
工厂也是领域设计中的一类对象,工厂提供一个接口封装复杂的封装过程。
当聚合里的实体组装很简单,或者一个对象的创建不会涉及到其它对象的创建,可以采用根实体的构造函数创建。
7) 资源库:也是模型设计里的一个对象,用来保存可用的聚合,并返回聚合根的引用。注意资源库与存储区的区别。资源库属于领域区,封装了对数据操作的细节,对外部提供一个接口,隐藏了对数据操作的细节。而存储区指数据库、文件等持久化存储设备,处于基础架构区。
工厂和资源库、存储区的关系:
4. DDD的多层架构
同其它架构方法类似,DDD也是多层架构。DDD的多层架构分为了:1)UI层;2)应用层;3)领域层;4)基础架构层。理解这几个层级,对于下面开展微服务的工作很重要,所以要搞清楚这几个层级的定位。
1)UI层,顾名思义,就是用户界面层,用以提供和和用户交互的界面。
2)应用层:这个比较容易和其它架构里提到的应用混淆。在DDD里的应用层,主要负责工作流程的编排工作。用服务的语言来讲,应用层可以称为是服务编排(传统的SOA)层或者服务调度层(微服务)。
3)领域层是领域驱动设计中的一个很重要的层级,在这几个层级中,只有领域层负责领域模型。领域层包括实体和业务逻辑。领域层实现了服务(实现业务逻辑)、资料库(对数据实体进行CRUD操作)等
4)基础架构层:这个也和其它架构里提到的基础架构有区别。这里的基础设施不仅仅包含计算、网络、存储等硬件设施,还包括应用的基础架构,如操作系统、数据库、消息队列等。基础架构为上层逻辑的实现提供支撑,并存储数据
对DDD的回顾就到这里,下一节我们会基于DDD的思想设计一个网上叫车的微服务,开发并注册到第一节开发的Eureka上,敬请期待