DDD从小白到入门

DDD实战篇二

代码模型(上):如何使用DDD设计微服务代码模型

DDD分层架构模型:
			1、用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层接口适配相关的功能。
			2、应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
			3、领域层:实现领域的核心业务逻辑,这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等
			领域对象,以及它们组合所形成的业务能力
			4、基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。
微服务一级目录结构:
			1、interfaces(用户接口层):它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用用过这一层的接口,向应用服务获取所需的数据。这一层主要用来处理用户发送的Restful请求,
			解析用户输入的配置文件,并将数据传递给aoolication层。数据的组装、数据传输格式以及Facade接口等代码都会放在这一层目录里
				facade接口:为子系统中的一组接口提供一个统一接口。Facade模式定义了一个更高层的接口,使子系统更加容易使用。
				使用一种比原有方式更简单的办法与系统交互。外观类通常是一个可配置、可复用的类。
				
				interfaces的代码目录结构有:
				1、assembler:实现dto与领域对象之间的相互转换和数据交换。一般来说assembler与dto总是一同出现。
				2、dto:它是数据传输的载体,内部不存在任何业务逻辑。我们可以通过dto把内部的领域对象与外界隔离。
				3、facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。	
			2、applicaiton(应用层):它主要存放应用层服务组合和编排的相关代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,
			向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
				
				application的代码目录结构有:
				1、event(事件):这层目录主要存放事件相关的代码,它包含两个子目录:publish和subscribe。
				publish主要存放事件发布相关代码,subscribe主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)
				
				注:微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。
				通过应用层调用领域层服务,来实现完整的时间发布和订阅处理流程。
			   
				2、service(应用服务):应用服务会对多个领域或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,
				是一段独立的业务逻辑。可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。

				
			3、domain(领域层):它主要存放在领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、
			方法、领域服务和事件等代码会放在这一层目录里。		
				
				domain的代码目录结构有:
				1、aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。
				聚合内实现内聚的业务逻辑,它的代码可以独立拆分为微服务。
				以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务业务之间聚合的重组。聚合之间清晰的代码边界,可以轻松地实现以聚合为单位的微服务重组,
				在微服务架构演进中有着重要作用。
					聚合内的代码模型是标准和统一的,包括:
					1、entitiy(实体):它存放聚合根、实体、值对象以及工厂模式相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
					2、event(事件):它存放事件实体以及事件活动相关的业务逻辑代码。
					3、service(领域服务):一个领域服务是多个实体组合出来的一段业务逻辑。可以将聚合内所有领域服务都放在一个领域服务类中,也可以把每一个领域服务设计为一个类。
					如果领域服务内的业务逻辑相对复杂,可以将一个领域服务设计为一个领域服务类,避免由于所有领域服务都放在一个领域服务类中,而出现代码臃肿的问题。
					领域服务封装多个实体或方法后向上层提供应用服务调用。
					4、repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,一个聚合只用一个仓储。
					
					注:按照DDD分层架构,仓储实现本来应该属于基础层代码,但为了在微服务架构演进	时,保证代码拆分和重组的便利性,把聚合仓储实现的代码放到了聚合包内。
					这样,如果需求或者设计发生变化导致聚合需要拆分或重组时,就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。

			4、infrastructure(基础层):它主要存放基础资源服务相关的代码,为其他各层提供的通用技术能力、三方软件、数据库服务、配置和基础资源服务于的代码都会放在这一层目录里。
				infrastructure的代码目录结构有:
					1、config:主要存放配置相关代码
					2、util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码。也可以为不同的资源类别建立不同的子目录。

聚合之间的代码边界一定要清晰,代码一定要有分层的概念。应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码。领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层,基于DDD分层架构模型的微服务就会演变成传统的三层架构模型了。以下为企业开发中某个模块的代码模型
DDD从小白到入门_第1张图片

代码模型(下):如何保证领域模型与代码模型的一致性?

DDD强调先构建领域模型然后设计微服务,以保证领域模型和微服务的一体性,因此我们不能脱离领域模型来谈微服务的设计与落地。但在构建领域模型时,往往是站在业务视角的,并且有些领域对象还带着业务语言。还需要将领域模型作为微服务设计的输入,对领域对象进行设计和转换,让领域对象与代码对象建立映射关系。

领域对象的整理:完成微服务拆分后,领域模型的边界和领域对象就基本确定了。一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。
从领域模型到微服务的设计:从领域模型到微服务落地,还需要做进一步的设计与分析。事件风暴提取的领域对象,还需要经过用户故事或领域故事分析,以及微服务设计,才能用于微服务系统开发。

领域层的领域对象产生过程:
			1、设计实体。
				大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的,但某些领域模型的实体在微服务设计时,可能被设计为多个数据实体,
				或者实体的某些属性被设计为值对象。
				
				在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为,
				比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。
				
				实体类放在领域层的entity目录结构下
			2、找出聚合根。
				聚合根来源于领域模型。在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地址、电话以及银行账号的生命周期。
				个人客户聚合根通过工厂和仓储模式,实现聚合内地址、银行账号等实体和值对象数据的初始化和持久化。
				
				聚合根是一种特殊的实体,它有自己的属性和方法。聚合根可以实现聚合之间的对象引用,还可以引用聚合内的所有实体。聚合根类放在代码模型的entity目录下。
				聚合根有自己的实现方法,比如生成客户编码,新增和修改客户信息等方法。
			3、设计值对象。
				将某些实体的某些属性或属性集设计为值对象。值对象放在代码模型的entity目录结构下。在个人客户聚合中,客户拥有客户证件类型,它是以枚举值的形式存在,
				所以把它设计为值对象。
			
				如果领域对象在其他聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,我们就可以将它设计为值对象。
				如果这个对象是多条且需要基于它做查询设计,应把它设计为实体。
			4、设计领域事件。
				如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。首先确定领域事件发生在微服务内还是微服务之间,
				然后设计事件实体对象,事件的发布和订阅机制,以及事件的处理机制,判断是否需要引入事件总线或消息中间件。

				领域事件实体和处理类放在领域层的event目录下。领域事件的发布和订阅类建议放在应用层的event目录结构下。
			5、设计领域服务。
				如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。
				可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
				
				按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才能被应用服务调用。
							  如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。

				个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。然后再被封装为创建个人客户信息应用服务,向前端应用暴露。
	
				领域服务类放在领域层的service目录结构下
			6、设计仓储。
				每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。仓储包括仓储的接口和仓储实现,通过依赖倒置实现业务逻辑与数据库资源逻辑的解耦。

				仓储代码放在领域层的repository目录结构下。

应用层的领域对象:应用层的主要领域对象是应用服务和事件的发布和订阅,在严格分层架构下,不允许服务的跨层调用,每个服务只能调用它的下一层服务,服务从下到上依次为:实体方法、领域服务和应用服务。如果需要实现服务的跨层调用,采用服务逐层封装的方式。

服务的封装和调用主要有以下几种方式:
				1、实体方法的封装
					实体方法是最底层的原子业务逻辑。如果单一实体的方法需要被跨层调用,可以将它封装成领域服务,这样封装的领域服务就可以被应用服务调用和编排了。
					如果它还需要被用户接口层调用,你还需要将这个领域服务封装成应用服务。经过逐层封装,实体方法就可以暴露给上面不同的层,实现跨层调用。

					封装时服务前面的名字可以保持一致,可以用*DomainService或*AppService后缀来区分领域服务或应用服务。
				2、领域服务的组合和封装
					领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。如果它需要暴露给用户接口层,领域服务就需要封装成应用服务。
				3、应用服务的组合和编排
					应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。
					
					多个应用服务可能对多个同样的领域服务重复进行同样业务逻辑的组合和编排,可以将这几个不断重复组合的领域服务合并到一个领域服务中实现,
					这样领域模型将会越来越精炼,更能适应业务的要求。

典型的领域模型:以个人客户领域模型中的个人客户聚合表格为例说明:

层:定义领域对象位于分层架构中的哪一层,比如接口层、应用层、领域层以及基础层等
			
领域对象:领域模型中领域对象的具体名称
			
领域类型:根据DDD知识体系定义的领域对象的类型,包括:限界上下文、聚合、聚合根、实体、值对象、领域事件、应用服务、
				领域服务和仓储服务等领域类型
			
依赖的领域对象:根据业务对象依赖或分层调用的依赖关系,建立的领域对象的依赖关系,比如:服务调用依赖、关联对象聚合等
			
包名:代码模型中的包名,对应领域对象的类名

方法名:代码模型中方法名,对应领域对象实现或操作的方法名。

本篇文章介绍了如何使用DDD设计对应的代码模型以及如何保证领域模型与代码模型的一致性,下篇文章将介绍微服务边界和视图相关概念和知识。

你可能感兴趣的:(DDD,领域驱动设计,微服务拆分,java)