DDD的全称为Domain-driven Design,即领域驱动设计;
分层架构:UI层、应用层、领域层、基础设施层;
User Interface
负责向用户展现信息,并且会解析用户行为,即常说的展现层。
Application Layer
应用层没有任何的业务逻辑代码,它很简单,它主要为程序提供任务处理。
Domain Layer
这一层包含有关领域的信息,是业务的核心,领域模型的状态都直接或间接(持久化至数据库)存储在这一层。
Infrastructure Layer
为其他层提供底层依赖操作。
层结构的划分是很有必要的,只有清晰的结构,那么最终的领域设计才宜用,比如用户要预定航班,向Application Layer的service发起请求,而后Domain Layler从Infrastructure Layer获取领域对象,校验通过后会更新用户状态,最后再次通过Infratructure Layer持久化到数据库中。
1、Java动物对象实例
public class Dog {
private int age;
private String color;
public int getAge() {
return age;
} //吃
public void eat(Object food) {
System.out.println("吃...");
} //睡
public void rest() {
System.out.println("睡...");
}
}
2、贫血模型的例子
public class Dog {
private int age;
private String color;
private String status;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public class DogService {
//吃
public void eat(Dog dog, Object food) {
dog.setStatus("吃");
System.out.println(dog.toString() + food.toString());
}
//睡
public void rest(Dog dog) {
dog.setStatus("睡觉");
System.out.println(dog.toString() + " 睡觉觉...");
}
public Dog getDog(){
return new Dog();
}
}
public class DogController {
public void eat() {
Object food = new Object();
DogService ds = new DogService();
Dog dog = ds.getDog();
ds.eat(dog, food);
}
public void rest() {
DogService ds = new DogService();
Dog dog = ds.getDog();
ds.rest(dog);
}
}
Dog类只有针对属性的get,set操作,行为被搬到service类了.这种风格是贫血模型的代表。
缺点:根据个人代码风格,有的controller类很重,有的service类很重。
最重要的是如果不具备一定的领域分析的知识,往往建出来的类似Dog的领域类是错误的,甚至会根据经验先建数据库再建领域类。
Domain层是具体的业务领域层,是发生业务变化最为频繁的地方,是业务系统最核心的一层,这一层包含了如下一些domain object:entity、value object、domain event、domain service、factory、repository等。
1.实体类:( Entity)
领域实体是domain的核心成员,domain entity具有以下三个特征:
唯一业务标识
持有自己的业务属性和业务行为
属性可变,有着自己的生命周期,故实体对象可能和它之前的状态不一样,但有同样的唯一标识,是同一个实体。
例:狗
2.值对象( ValueObject)
内部值是不变的,不存在生命周期;
例:color、point、money、address
对象是根据值来确定的,可以在不同实体中使用,值对象通常不可变的
3.Service
无状态对象
当一个属性或行为放在实体、值对象中模棱两可或不合适的时候就需要以Service的形式来呈现;
例:转账、领域逻辑是动词
三种模型的复杂度是不一样的,在领域建模选Model模棱两可时,优先选择简单模型原则。模型复杂度顺序 Service > Entity > ValueObject
1.聚合-对象的依赖关系;
2.设计聚合时要考虑一致性.意味着一个客户请求只在一个聚合实例上执行一个命令方法;
3.聚合设计原则:设计小聚合。大的聚合即便能保证事务的一致性,也依然可能限制系统的性能可伸缩性;
4.通过唯一标识引用其它聚合。
聚合之间有依赖关系时不要直接写依赖对象,通过唯一标识来引用。通过标识引用可以将不同限界上下文的分布式领域模型关联起来。
5,在边界之外使用最终一致性。当一个聚合执行命令方法时,还需要在其它聚合上执行任务,使用最终一致性。一种实用的方法可以支持最终一致性,即一个聚合的命令方法发布的领域事件及时地发布给异步的订阅方。
6.不要在聚合中注入资源库和领域服务。
1.啥是领域事件?
领域专家所关心的发生在领域中的一些事件。
将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示…领域事件是领域模型的组成部分,表示领域中所发生的事情;
首先是解决领域的聚合性问题。DDD中的聚合有一个原则是,在单个事务中,只允许对一个聚合对象进行修改,由此产生的其他改变必须在单独的事务中完成。如果一个业务跨多个聚合对象,领域事件会是一个不错的工具来解决这个问题。通过领域事件的方式可以达到各个组件之间的数据一致性,通过最终一致性取代事务一致性。
其次领域事件也是一种领域分析的工具,有时从领域专家的话中,我们看不出领域事件的迹象,但是业务需求依然有可能需要领域事件。动态流的事件模型加上结合DDD的聚合实体状态和BC,可以有效进行领域建模。
2.领域事件的技术实现
领域事件的技术实现实质上观察者模式的实现。技术的实现都好讲,关键是理解观察者模式在领域建模中的应用场景。
3.领域事件需要关注的类容。
一,消息设施最终一致性。
二,事件存储:
1),将事件存储作为一个消息队列使用。
2),检查由模型命令方法的所产生的所有结果的记录
3),使用事件存储中的数据进行业务预测和分析。、
4),通过事件存储重一个聚合。
5),撤销对聚合的操作
4,转发存储的架构风格。
1、领域服务表示一个无状态的操作,强调一个无状态的操作,状态应该在实体中维护,领域服务处理是无状态的逻辑过程;
2、实现某个领域的任务,即做的也是领域内的事情,是通用语言的表达。而不是应用服务,应用服务是领域服务的客户方,比如api聚合服务,不应该做领域内的事情。也不是基础设施服务,比如DB或消息基础组件。特别是不能跟现在常用的mvc+s架构中的s(service)层混淆,这种情形下的s,很多时候是持久层接口组装,更像是DDD中的资源库的概念。
3、先考虑聚合或值对像的建模,不适合,然后才使用领域服务。聚合(实体)和值对像才是最重要的DDD建模对象,如果反而首先使用领域服务,容易导致“贫血领域模型”。既然不适合直接在实体或值对像上建模,也基本说明很多时候涉及到多个实体或值对像。
那什么时候该使用领域服务呢?
1.执行一个显著的业务操作过程
2.对领域对象进行转换
3.以多个领域对象作为输入进行计算,结果产生一个值对像基本就是跟上面对领域服务概念的理解是一致的。
领域服务实现是否需要独立接口?
优点:使用接口表达领域概念,而技术实现可以比较随意,比如放在基础实施层,或者在依赖倒置原则中,放在应用层实现也可以;独立接口有利于解耦,通过依赖注入或工厂可以完全解耦客户端与具体实现
缺点:得写两个对象代码,特别对于java,还得分两个文件,阅读代码也增加点难度,而很多时候一个接口也只有一个实现;另外一个命名问题,在DDD中领域对象名称(对应语言实现的类)和操作名称(对应函数名)是很重要的,是需要表达通用语言的概念的。但如果定义独立接口,也就是会XXXservice的名字来定义接口,但服务实现用什么命名呢?如果用XXXserviceImpl,那其实也说明可以不需要定义独立接口了。测试领域服务其实测试方面,我觉得没有很多需要关注的,或者说我比较少测试方面的需要。但在测试领域服务一节有句话却比较有意思“我们希望对领域服务进行测试,并且希望从客户端的角度对领域服务进行建模,同时我们希望测试能够反映查领域服务的使用方式”,即通过测试代码,告诉客户端怎么使用领域服务。这其实是测试代码的一个重要的作用,但也经常被我们忽略的。
注意:领域服务不能过多,那变成贫血模型了。
应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。
应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。
一, 架构部分:
二,架构风格:
《实现领域驱动设计》关于架构一章,章节名虽然叫架构,但应该理解成架构风格。就象传统的三层架一样,是一种架构风格。
1)六边形架构
它并不是真的有六条边.它也叫端口+适配器.端口可以理解成http,socket自定义传输协议、或者某个RPC调用协议等。六边形的内部代表了application和domain层。外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一种协议,以API呈现。
一个例子:
2) Rest
RESTful风格的架构将‘资源’放在第一位,每个‘资源’都有一个URI与之对应,可以将‘资源’看着是ddd中的实体;RESTful采用具有自描述功能的消息实现无状态通信,提高系统的可用性;至于‘资源’的哪些属性可以公开出去,针对‘资源’的操作,RESTful使用HTTP协议的已有方法来实现:GET、PUT、POST和DELETE。
3) CQRS
CQRS就是平常大家在讲的读写分离,通常读写分离的目的是为了提高查询性能,同时达到读/写的解耦。
CQRS适用于极少数复杂的业务领域,如果不是很适合反而会增加复杂度;另一个适用场景为获取高性能的服务
4)事件驱动架构
事件可能有不同类型,这里主要只关心领域事件。
象不象微服务架构中引入消息中间件来解耦和最终事务一致?
5)管道和过滤器风格.
管道过滤器来源于linux命令类似 ps -ef | grep tomcat . | 即管道。ps 处理的结果经过管道符| 作为下一个命令的输入。
《实现领域驱动设计》中举了个基于事件的发布、订阅的例子来实现管道和过滤器架构风格。事件的发布订阅中心作为管道。事件的发布、订阅方作为过滤器。
推而广之,考虑基于消息中间件的管道过滤器。
采用三层结构的架构风格就没有领域相关的类容了吗?
答案当然是有的,但是不象DDD这样有明确的区分。往往因为程序员没有相关概念或多多思考就容易引发问题。
public class SearchController {
private SearchService service;
@RequestMapping(value="search")
public List search(String searchStr) {
service.search(searchStr);
}
}
public class SearchService{
public List search(String str){
if(系统变量=="solor"){
solorService.search(str)
}else {
dbService.search(str);
}
}
}
功能很强大,支持数据检索和solor检索。但是再看,solor检索和数据检索明显不是一个玩意,不应该同时出现在SearchService里。从DDD观点来看,也明显属于不同的领域实现模型。即使在同一个子域里,划分微服务那它也应该是两个微服务实现。显明扩展性不好。
3.作为六边型架构风格的实现,看看一个开发包的结构图