[微服务与DDD]-什么是DDD

2,049 阅读12分钟

上章回顾:

随着软件系统越来越复杂,迭代速度要求越来越高,节奏越来越快,导致了软件退化,为了应对软件复杂度和迭代要求,微服务架构应运而生,通过微服务拆分治理来分层解决系统的复杂度问题,最终提出了,让微服务小而美的利器:DDD(领域驱动设计)

DDD是什么?

到目前为止,可能大家还不清楚DDD到底是个什么鬼?别急,听我慢慢道来. 作为一名合格的架构师,不管是新手架构师还是老油条架构师,一定知道软件架构设计七大原则:

软件架构设计七大原则

  • 单一职责原则(Single Responsibility Principle - SRP) 对于一个类而言,应该仅有一个引起它变化的原因。说白了就是,不同的类具备不同的职责,各施其责。这就好比一个团队,大家分工协作,互不影响,各做各的事情。这样设计的好处是可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。
  • 开放封闭原则(Open Closed Principle - OCP) 对扩展开放,对修改封闭。一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓开闭,也正是对扩展和修改两个行为的一个原则。开放封闭原则强调用抽象构建框架,用实现扩展细节,这样设计的好处可以提高软件系统的可复用性及可维护性。
  • 里氏替换原则(Liskov Substitution Principle - LSP) 一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。里氏替换原则下强调当需求有变化时,只需继承,而别的东西不会改变。由于里氏代换原则才使得开放封闭称为可能。这样使得子类在父类无需修改就可以扩展。这样设计的好处是约束继承泛滥,加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。
  • 接口隔离原则(Interface Segregation Principle - ISP) 一个类与另一个类之间的依赖性,应该依赖于尽可能小的接口。客户端不应该依赖它不需要的接口,类之间的依赖关系应该建立在最小的接口上。 接口隔离原则强调建立单一接口而不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。这样设计的好处是可以使类具有很好的可读性、可扩展性和可维护性。
  • 最少知识原则(Least Knowledge Principle - LKP) 有些地方也称为迪米特法则,一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合。 迪米特原则强调尽量减少对象之间的交互,从而减小类之间的耦合。这样设计的好处是很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系.
  • 依赖倒置原则(Dependence Inversion Principle - DIP) 实现尽量依赖抽象,不依赖具体实现。高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象.依赖倒置原则强调高层模块不应该依赖于底层模块,两者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。这样设计的好处是可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且可以降低修改程序所造成的的风险。
  • 合成复用原则(Composite/Aggregate Reuse Principle, CARP) 尽量使用对象组合(has-a)/聚合(contanis-a),而不是继承关系达到软件复用的目的。合成复用原则强调一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过这些对象的委派达到复用已有功能的目的。这样设计的好处是该原则可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。

回到正题,一个软件系统在最开始的时候可能就是最优的时候,随着功能的叠砌和代码的膨胀,不可避免的出现了软件退化的现象,如何让软件不退化呢?关键在于每次需求变更的设计,只有保证每次需求变更时做出正确的设计,才能保证软件以一种良性循环的方式不断维护下去。正确的设计,说白了就是做到软件架构高内聚低耦合,同时需要实现新的功能不尽可能不影响原有功能.这个说起来比较容易,但是面对不断的快速迭代变更,很多时候,第一次变更、第二次变更、第三次变更,这些事情还能想清楚;但经历了第十次变更、第二十次变更、第三十次变更,这些事情就想不清楚了,设计开始迷失方向。此时我们有没有一种方法,让我们在第十次变更、第二十次变更、第三十次变更时,依然能够找到正确的设计呢?有,那就是“领域驱动设计”。

DDD核心思想

软件的本质就是对真实世界的模拟,将软件设计与真实世界对应起来,真实世界是什么样子,那么软件世界就怎么设计。领域驱动设计就是建立软件世界和真实世界的对应关系.

  • 真实世界有什么事物,软件世界就有什么对象;
  • 真实世界中这些事物都有哪些行为,软件世界中这些对象就有哪些方法;
  • 真实世界中这些事物间都有哪些关系,软件世界中这些对象间就有什么关联。 真实世界主要就是以事物为中心,关注事物的行为,以及事物之间的关系关联.所以我们由此建立映射关系:
  1. 事物对应对象
  2. 行为对应方法
  3. 关系对应关联 所以领域驱动设计中,就将以上三个对应关系,做成一个领域模型,然后通过这个领域模型指导程序设计;在每次需求变更时,先将需求还原到领域模型中分析,根据领域模型背后的真实世界进行变更,然后根据领域模型的变更指导软件的变更,设计质量就可以得到提高。

如何理解DDD

DDD首先是一种设计思想,回答了“设计的本质是什么,主要逻辑是什么”这类大的问题。DDD强调要从业务视角思考怎么设计软件架构,设计一定要知道业务是什么样子的,业务的需求和问题是啥,有啥内在逻辑,而不是从软件技术技术本身出发,业务先于技术.

领域驱动设计首先提出问题域,我们想要解决的是什么具体的业务问题,解决这些问题有哪些挑战? 通过基于领域模型来进行复杂的领域设计,需要运用DDD不断的切入问题领域的核心,主要聚焦在领域和领域逻辑,从根本上找到解决方案.形成解决方案域

领域驱动设计强调从高层视角去观察软件系统,通过精准的划分领域和处理各个领域之间的关系,并最终通过技术层面来实现软件架构的落地.

DDD提供一套方法,软件设计是个高度逻辑化的工作,需要概念特别清晰,推导过程有章法可循。DDD提供了对业务描述的一套方法,先对业务实体进行抽象,定义了一些软件设计概念,如实体对象,值对象,聚合对象,服务等,也提供了对象关系之间的描述。根据业务逻辑理解透了,就可以利用软件概念把业务逻辑映射成软件架构,这种过程就是DDD.

DDD还是一种具体的实践指导,在实际工作中,组织跨部门讨论是很难的,软件开发和业务人员讨论就更困难了,大家思维模式差异比较大,沟通自然比较困难;所以DDD首先干的一个事情就是把业务人员拉在一起,澄清概念,互相理解对方做的工作,这个方法DDD称为统一语言。其次DDD还开发了事件风暴方法,一步一步指导大家操作得出结论,为了有好的效果,操作指导具体到讨论时用什么颜色的标签纸。

DDD的基本概念

实体(Entity) & 值对象(Value Object)

实体与面向对象中的概念类似,在这里再次提出是因为它是领域模型的基本元素。在领域模型中,实体应该具有唯一的标识符,从设计的一开始就应该考虑实体,决定是否建立一个实体也是十分重要的。

值对象和我们说的编程中数值类型的变量是不同的,它仅仅是没有唯一标识符的实体,比如有两个收获地址的信息完全一样,那它就是值对象,并不是实体。值对象在领域模型中是可以被共享的,他们应该是“不可变的”(只读的),当有其他地方需要用到值对象时,可以将它的副本作为参数传递。

服务(Services)

当我们在分析某一领域时,一直在尝试如何将信息转化为领域模型,但并非所有的点我们都能用Model来涵盖。对象应当有属性,状态和行为,但有时领域中有一些行为是无法映射到具体的对象中的,我们也不能强行将其放入在某一个模型对象中,而将其单独作为一个方法又没有地方,此时就需要服务.

服务是无状态的,对象是有状态的。所谓状态,就是对象的基本属性:高矮胖瘦,年轻漂亮。服务本身也是对象,但它却没有属性(只有行为),因此说是无状态的。

服务存在的目的就是为领域提供简单的方法。为了提供大量便捷的方法,自然要关联许多领域模型,所以说,行为(Action)天生就应该存在于服务中。

服务具有以下特点: a)服务中体现的行为不属于任何实体和值对象的,但它属于领域模型的范围内
b)服务的行为涉及其他多个对象
c)服务是无状态的

模块(Moudles)

对于一个复杂的应用来说,领域模型将会变的越来越大,以至于很难去描述和理解,更别提模型之间的关系了。模块的出现,就是为了组织统一的模型概念来达到减少复杂性的目的的。而另一个原因则是模块可以提高代码质量和可维护性,比如我们常说的高内聚,低耦合就是要提倡将相关的类内聚在一起实现模块化。

模块应当有对外的统一接口供其他模块调用,模块对外暴露提供操作模块内对象的接口。模块的命名也很有讲究,最好能够深层次反映领域模型。

聚合(Aggregates)

聚合被看作是多个模型单元间的组合,它定义了模型的关系和边界。每个聚合都有一个根,根是一个实体,并且是唯一可被外访问的。正是如此,聚合可以保证多个模型单元的不变性,因为其他模型都参考聚合的根。所以要想改变其他对象,只能通过聚合的根去操作。根如果没有了,那么聚合中的其他对象也将不存在。

工厂(Factories)

在大型系统中,实体和聚合通常是很复杂的,这就导致了很难去通过构造器来创建对象。工厂就决解了这个问题,它把创建对象的细节封装起来,巧妙的实现了依赖反转。当然对聚合也适用(当建立了聚合根时,其他对象可以自动创建)。工厂最早被大家熟知可能还是在设计模式中,的确,在这里提到的工厂也是这个概念。

仓库(Repository)

仓库封装了获取对象的逻辑,领域对象无须和底层数据库交互,它只需要从仓库中获取对象即可。仓库可以存储对象的引用,当一个对象被创建后,它可能会被存储到仓库中,那么下次就可以从仓库取。如果用户请求的数据没在仓库中,则会从数据库里取,这就减少了底层交互的次数。当然,仓库获取对象也是有策略的。

章节总结

在这个章节中,带大家回顾了软件架构设计原则,分享了DDD的核心思想软件架构是现实世界的映射,介绍了如何理解和实践DDD指导软件架构的落地,并初步介绍了DDD的基本核心概念,接下来的章节讲结合具体实际案例进行剖析.