DDD领域驱动设计指导微服务实践

文章首发于公众号:松花皮蛋的黑板报
做者就任于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深刻的理解数据库

clipboard.png

文章来源:www.liangsonghua.me
做者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构架构

1、复杂性和规模增加的解决之道微服务

解决复杂和大规模软件的武器能够被粗略地归为三类:抽象、分治和知识工具

一、分治
把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题须要足够小,以便一我的单枪匹马就可以解决他们;其次,必须考虑如何将分割后的各个部分装配为总体。分割得越合理越易于理解,在装配成总体时,所需跟踪的细节也就越少。即更容易设计各部分的协做方式。评判什么是分治得好,即高内聚低耦合。以火车为例,每一个车箱都要符合承重要求,行李车箱承重能力要高于其余车箱,车箱间的链接要牢固且易于拆解,有足够的灵活性方便转弯。另外大件行李应该单独存放,避免占用普通车箱的空间,餐车应该在整个列车的中间,方便用餐测试

二、抽象
使用抽象可以精简问题空间,并且问题越小越容易理解。举个例子,从北京到上海出差,能够先理解为使用交通工具前往,但不须要一开始就想清楚究竟是高铁仍是飞机,以及乘坐他们须要注意什么this

三、知识
经过知识手段抽象出限界上下文以及如何去分治编码

2、DDD概览
DDD全称为“Domain Driven Design”,意思是领域驱动设计,基于业务知识设计系统,代码反映业务,它将真实业务概念和业务规则转换为软件系统中的概念和规则,在一个个有界上下文中发挥其做用,完成用户要求的功能,从而下降或隐藏业务复杂性,使系统有更好的扩展性,以拥抱变化。说白了你那套领域逻辑到底有没有效?逻辑上自洽为真,没有逻辑矛盾,是否是现实中也是为真?须要在有界上下文中验证spa

举个例子:假若有父亲和儿子这两个表,生成的 POJO 应该是翻译

public class Father{…}
public class Son{
  //son 表里有 fatherId 做为 Father 表 id 外键 
 private String fatherId;
 public String getFatherId(){
   return fatherId;
 }
 ……
}

这时候儿子犯了点什么错,老爸很是不爽地扇了儿子一个耳光,老爸手疼,儿子脸疼。Manager一般这么作设计

public class SomeManager{
 public void fatherSlapSon(Father father, Son son){
  // 假设 painOnHand, painOnFace 都是数据库字段
  father.setPainOnHand();
  son.setPainOnFace();
 }
}

这里,manager充当了上帝的角色,扇个耳光都得他老人家帮忙。可是现实世界应该是教训儿子是本身的事情,并不须要别人帮忙,上帝也不行

public class Father{
 public void slapSon(Son son){
   this.setPainOnHand();
   son.setPainOnFace();
 }
}

3、关注点
一、微服务关注点
1)、运行时进程间通讯,可以容错和故障隔离
2)、去中心化管理数据和冶理
3)、服务能够独立的开发、测试、构建、部署
4)、高内聚低耦合,职责单一
二、DDD关注点
1)、关注业务领域,创建边界并构建通用语言,高效沟通
2)、对业务进行抽象,和业务专家一块儿建模
3)、尽量维持代码和业务的低表示差别
一个供电系统中,合同部门关心的是电价,用电跟踪部门关心的是电量,到告终算部门则是关心购电量,他们只关心本身所辖边界内的重心,用不一样方言在讲话,没有造成统一语言。实际上购电量=电价+电量+用户时间段+用电规则,购电量才是业务模型中的核心方法,它应该是模型中的通用语言。有了统一语言后,能够减小将业务架构映射到系统架构时的层层翻译,避免组件划分过程当中的边界错位,加强系统对业务的响应速度

从上面你能够发现DDD强调的是逻辑划分,微服务强调的是隔离部署,系统架构的逻辑划分能够细于部署单元的物理隔离,较好的架构应该是演进式,避免过早微服务化带来的麻烦

4、DDD设计实践
一、按业务划分限界上下文
从业务能力的角度识别核心域、支撑域、通用域并去除二义性,好比电商业务中订单就是核心域,订单服务产生的其余业务则是支撑域,而通知中心、短信服务则是通用域

二、消除隐式数据依赖
通常状况下订单表会使用user_id做为用户标识,但若是系统未来要对接企业,咱们能够新增一个订单服务可是开发维护成本太高,能够新增一个企业员工记录可是员工离职交接维护成本太高,能够新增一个虚拟员工帐号可是无心间耦合了,正确作法是新增一个企业用户上下文,而后使用”用户ID加用户类型”标识购买者,消除隐式数据依赖

《架构整洁之道》这本书中说到,任何形式的共享数据行为都会致使强耦合,若是给服务之间传递的数据记录中增长一个新字段,那么每一个须要操做这个字段的服务都必需要作出相应的变动,服务间必须对这条数据的解读达成一致,那么他们是间接彼此耦合的

三、明肯定义依赖方向
这里不得不提到整洁架构(又名洋葱架构),整洁架构最主要原则是依赖原则,它定义了各层的依赖关系,越往里,依赖越低,代码级别越高。外圆代码依赖只能指向内圆,内圆不知道外圆的任何事情。通常来讲,外圆的声明(包括方法、类、变量)不能被内圆引用。一样的,外圆使用的数据格式也不能被内圆使用

四、下游的自我保护
对于下流来讲,须要根据本身的领域模型建立一个单独的防腐层,该层做为上游系统的代理向你的系统提供功能,它在你本身的模型和他方模型间进行概念对象和其行为进行翻译转换,好比当数据实体格式不符合系统要求时,只须要在防腐层中添加对应的转换器便可,领域模型可保持不变

6、DDD编码的意义
让代码体现业务,保持两者的低表示差别,难点在于对聚合根的实现

在DDD模式中将对象分为值对象和实体。实体对象是有生命周期的,能够惟一标识的(不是数据库中的ID),此对象只能属于某个业务。而值对象是没有生命周期的,好比订单领域上下文中,订单是实体、订单项是实体、订单状态是值对象。原来咱们系统划分单位一般是模块,可是粒度不够细,因此须要对实体和值对象等进行关联设计后,进行聚合的划分和聚合根的肯定,好比订单和订单项、订单和订单状态有关联,他们总体做为一个聚合,一般聚合中其余实体须要依赖聚合根,很显然订单应该是聚合根(生命周期最长,其余聚合项离开它没有任何意义)。DDD模式中对一个聚合中实体的访问或操做,必须经过这个聚合的聚合根开始,主要的目的是数据的最终一致性。另外聚合根之间不能创建关系,只能经过ID引用

好比

class Book {
      private @Id Long id;
      private Set<AutherRef> authors = new HashSet<>();
    
      public void addAuther(Author author) {
        authers.add(createAuthorRef(author));
      }
    
      private AuthorRef createAuthorRef(Author author) {
        AuthorRef authorRef = new AuthorRef();
        authorRef.author = author.id;
        return authorRef;
      }
    }

7、总结
传统开发模式是肯定需求后定义表结构,以数据库表做为系统导向,可是DDD建议应该先着眼于业务自己,减小对数据库表结构的依赖,避免业务发生变化,把咱们打得措手不及,它把你的思考起点从技术的角度拉到了业务上。不过在进行DDD设计时须要注意划分边界,注意定义边界间的关系,注意概念不要穿透边界

最后你会发现通篇都在谈论的“边界”划分,咱们知道微服务落地的难点之一就是如何正确折分,若是拆分后的服务出现互相调用或者须要高成本解决各个服务间的数据一致性,将得不偿失,因此正确的打开方式是在进行微服务拆分前应该先充分了解领域驱动设计

文章来源:www.liangsonghua.me做者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构

相关文章
相关标签/搜索