Solo  当前访客:1 开始使用

CoderV的进阶笔记


Everybody wants go to heaven, but nobody wants to die.

存档: 2019 年 11 月 (9)

领域驱动设计DDD之上下文映射图

2019-11-29 23:23:54 valarchie
0  评论    0  浏览

上下文映射图的英文是Context Map其实这个翻译挺难理解的,上下文映射图其实就是不同上下文是如何进行交流的关系。由于上下文映射图内容比较少。以下内容摘自《领域驱动设计精粹》。

三种集成方式

  1. RPC方式
  2. 消息队列或者发布-订阅机制。
  3. RESTful方式

上下文映射的种类

  • 合作关系:
    合作关系存在于两个团队之间。每个团队各自负责一个限界上下文。两个团队通过互相依赖的一整套目标联合起来形成合作关系。一损俱损,一荣俱荣。由于相互之间的联系非常紧密,他们经常会对同步日程安排和相互关联的工作。他们还必须使用持续集成对保持集成工作协调一致。

  • 共享内核:
    两个或者多个团队之间共享着一个小规模但却通用的模型。团队必须就要共享的模型元素达成一致。有可能他们当中只有一个团队会维护,构建及测试共享模型的代码。

领域驱动设计DDD之限界上下文

2019-11-28 11:30:04 valarchie
0  评论    0  浏览

战术设计是从微观视角对单个微服务的编码设计,而战略设计是从宏观视角对多个微服务的交互设计。从人体学来说,一个器官的内部构造属于战术设计,多个器官之间的协作属于战略设计。好比心肺器官之间的协作配合。

为什么需要战略设计?

假设我们在设计订单模型时,下单操作涉及到会员等相关规则,比如增加积分等操作。(此时我们还未将订单子域和会员子域进行剥离)。其实一个用户的下单操作不需要与会员的积分产生强烈耦合。一旦会员的积分制度发生变动,又得需要在订单子域中进行修改,而订单子域又是我们的核心域,频繁的对核心域进行更改的风险较大。那如果将会员体系从订单核心域剥离出去呢?我们便使得两个子域的职能更为清晰并且解耦,这也体现了单一职责的设计原则。

假设一个开发小组既负责订单逻辑又要负责会员积分逻辑,则使得这个开发小组本身的职责变得复杂。如果使将订单和会员拆分开来,通过上下文之间的交互(即微服务之间的交互)使得订单开发小组成员、会员开发小组各自关心自己的领域(即各自只关心各自的业务逻辑)。

好比流水线上的工人,有利于分工,大家专注于自己负责的工序。

战略设计的元素主要有子域、限界上下文、上下文映射图。

领域驱动设计DDD之资源库

2019-11-21 12:47:59 valarchie
0  评论    0  浏览

为什么使用资源库?

如果完全按照领域模型的角度,完全通过遍历对象的方法来获取所有关联的对象。这种模型会过于错综复杂。对象嵌套的层级或者关联的层级非常深。例如通过Customer.order.product.price层层遍历来获取当时客户订单的商品的价格。

那如果完全按照数据库模型的角度,模型中的对象不需要完全连接起来,对象关系网就能保持在一个可控范围。但是这又会回到之前传统开发模式中,零散的使用各个DAO从各个表抽取数据自行拼凑出我们想要的模型。

这里就会出现一个问题,Customer类需要保持客户所有已订的Order,还是通过CustomerID在数据库中查找Order列表呢?

对象关联还是纯数据库?

客户需要一种有效的方式来获取已存在的领域对象的引用。如果基础设施提供了这方面的遍历,那么开发人员可能会增加很多可遍历的关联,这会使模型变得非常混乱。另一方面,开发人员可能使用查询从数据库中提取他们所需的数据,或是直接提取具体的对象,而不是通过聚合的根来得到这些对象。这样就导致领域逻辑进入查询和客户代码中,而实体和值对象变成了单纯的数据容器。采用大多数处理数据库访问的技术复杂性很快就会使客户代码变得混乱,这将导致开发人员简化领域层,最终使模型变得无关紧要。

我们需要找到一个恰当的方式。

我们不需要对那么很容易通过遍历来找到的持久对象进行查询访问,如不需要单独的去获取地址,而可以通过Person对象来获取地址,意味着当我们获取Person对象的时候,地址值对象已经是填充好的。我们不需要特地利用另外一个资源库的方法,获取地址再进行填充。但当获取Person的所有订单记录时,由于订单的数量有时候过于庞大,加载Person的时候其实并不一定需要查看订单记录,我们应该使用Repository的另外方法来获取Person的订单记录。

对值对象的全局搜索通常是没有意义的。如果确实需要在数据库中搜索一个已知的值对象,那么值得考虑一下,搜索结果可能实际上是一个实体,尚未识别出它的标识。

领域驱动设计DDD之工厂 有更新!

2019-11-19 23:34:03 valarchie
0  评论    0  浏览

为什么需要工厂

  1. 当创建一个复杂对象或聚合的过程很复杂并且暴露出了过多的内部结构时,我们则可以使用工厂进行封装。一个对象在它的生命周期中要承担大量的职责,如果再让复杂对象负责自身的创建,那么职责过载将会导致问题。

  2. 我们设计好领域模型供客户方调用,但如果客户方也必须使用如何装配这个对象,则必须知道对象的内部结构。好比你去驾校学车,却得先学会发动机的原理。对客户方开发来说这是很不友好的。其次,复杂对象或者聚合当中的领域知识(业务规则)需要得到满足,如果让客户方自己装配复杂对象或聚合的话,就会将领域知识泄露到客户方代码中去。

  3. 对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或聚合的封装,而且导致客户与被创建对象的实现之间产生过于紧密的耦合。

最重要的一点就是隐藏创建对象的细节

领域驱动设计DDD之聚合 有更新!

2019-11-19 23:37:06 valarchie
0  评论    0  浏览

为什么需要聚合?

当我们设计一个订单模块,用户下单时,我们需要确保用户的余额可供支付这笔订单,并且保存这个订单。通俗的理解就是当下单的时候,必须生成订单表记录,并且检查用户余额是否足够支付,并修改用户的余额表。再转换到我们领域驱动设计中,我们必须利用订单模型和账户模型联合来完成操作,并检查保证业务规则(余额可供支付)。那此时有两点缺陷,在保证事务的应用服务中,这些领域知识(业务规则)便从领域模型泄露应用服务层。代码中除了订单模型和账户模型在同一个应用服务中这点之外,客户程序员很难知道在下单请求中订单模型和账户模型之间必须在同一个事务中进行修改。

聚合描述

每个聚合都有一个根和一个边界,边界内定义了聚合的内部有什么。根则是聚合所包含的一个特定的实体。外部对象可以引用根,但不能引用聚合内部的其他对象,聚合内的对象之间可以相互引用,除了根实体外,其他实体拥有本地标识。

举个例子:

在汽修厂的软件中会使用到汽车的模型,这里的汽车就是根实体,因为它具有全局唯一的标识:汽车识别号。汽修厂想要跟踪每台汽车上四个轮胎的使用情况。轮胎在汽车里才是实体,它们拥有本地标识,如汽车的四轮分为左前轮,右前轮,左后轮,右后轮。当轮胎报废了之后我们便不再关心这些轮胎的生命周期了,我们也不会在系统中寻找某一个轮胎现在安在哪台车上。因此,汽车便是这个聚合的根实体,轮胎就在这个聚合的边界之内。

再举个例子:
在订单模块中,下单操作必须生成订单主表和购买商品附表,订单主表即是一个根,通过这个根我们可以找到当时这笔订单购买了哪些商品。但是我们并不会单独去查购买商品附表里的记录,脱离了订单主表,这些记录就没有了意义。

定义

我们应该将实体和值对象分门别类的聚集到聚合当中,并定义聚合的边界。在每个聚合当中,选择一个实体作为根。并通过根来控制边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保聚合中的对象满足所有固定规则,也可以确保在任何状态变化时聚合作为一个整体满足固定规则。

领域驱动设计DDD之领域服务 有更新!

2019-11-19 23:35:29 valarchie
0  评论    0  浏览

什么是领域服务?

在战术建模当中,并非所有模型都是事物。有些模型是对领域中的一些行为操作进行建模。此类模型我们称之为领域服务。当一些重要的领域操作无法放到实体、值对象或者聚合时,他们本质是行为而不是事物。如果我们不寻找一些对象来封装这些领域行为的话,又会演变成之前过程式的编程方式。我们希望在领域设计当中统一用模型对象进行交互。此时领域服务使用细粒度的领域对象如实体或者值对象进行交互,在服务内部描述领域知识得出结果并将其返回。

领域服务的参数和返回类型应该是领域对象。

三个特征:
  • 它是与领域相关的操作如执行一个显著的业务操作过程,但它又并不适合放入实体与值对象中。
  • 操作是无状态的。
  • 对领域对象进行转换,或以多个领域对象作为输入进行计算,结果产生一个值对象。

领域驱动设计DDD之值对象 有更新!

2019-11-19 23:24:34 valarchie
0  评论    0  浏览

值对象是什么?

之前我们讲到实体,它最主要的特征在于概念上的标识。而领域中存在一些不需要进行标识的对象,它们主要是对事物的描述。如何理解这段话呢?可以通俗的理解,之前我们认为实体是数据库中的一条记录,这条记录会发生更改,例如学生的信息表。而值对象对应到数据库中的什么呢?它们不会单独地映射一张表而是可能对应表中的几个字段。比如学生信息表中拥有省、市、区、地址四个字段构成学生的住址,这四个字段构成的是一个整体用来描述学生的详细地址。

为什么我们将学生详细地址建模成值对象呢?

因为我们并不需要追踪学生详细地址的变化,我们并不关系它的连续性。


在很多DDD的博客文章中讲解到值对象都会用到了地址这一概念,可能有的人会误解地址天然就是值对象。并非如此,它有时可能作为值对象,有时可能作为实体。这需要看我们的业务场景。

值对象的情形:

  1. 当你和你的舍友在淘宝上购买商品,你们在订单上填的地址是一样的,并不需要严格区分这两个地址,快递直接送到这个地址即可。(地址映射到订单表中的几个字段上,如:省市区地址)

实体的情形:
2. 当你们宿舍的宽带坏了,需要报修,这时候你和你的舍友都打了电话报修。但是宽带人员并不会派两个人来抢修宽带。因为宽带人员知道你们是同一个地址。(宿舍地址映射到宽带公司系统中的一条地址用户表,以地址来区别用户)


领域驱动设计DDD之实体 有更新!

2019-11-19 23:24:54 valarchie
0  评论    0  浏览

什么是实体?

实体最主要有两点特征,一是唯一标识,二是连续性。

  • 唯一标志:
    当一些对象不是由属性定义,而是由一个唯一标志定义的话,我们就可以认为它是一个实体。好比我们不能通过一个人的外在特征去唯一定位一个人,因为人从小到大,从年轻到衰老其外在特征都是在改变的。而身份证号码可以贯穿一个人的一生而不发生变化。而且唯一标识不一定仅有一个属性表示,有可能通过多个属性标识某一个唯一对象,就好比数据库中的联合外键(可能有根据电话以及姓名唯一确定一个实体的情况)。

  • 连续性:
    对象的连续性体现在对象是有生命周期的。在这个生命周期内,对象内的属性可能是变化着的。好比银行账户表,它就属于一个实体,用户的银行卡号可以唯一的确定一个人的账户,而账户的内的余额随着时间变化(利息)或者随着交易变化。

领域驱动设计DDD之概览 有更新!

2019-12-02 21:57:48 valarchie
0  评论    0  浏览

在从事开发多年之后,你是否会感觉自己只是一个业务CRUD Boy,并认为业务没有多少技术含量。你是否会陷入业务的泥潭中,各种复杂交错的业务规则使得代码开始腐烂开始失控,项目开始变得难以维护,迭代举步维艰。如果你开始意识到这个问题的话,那么我十分推荐你开始学习领域驱动设计面向领域建模的设计方式。

DDD是什么呢?

DDD即Domain Driven Design,翻译成中文的话就是领域驱动设计,首先我们应该先理解这里的领域是什么意思?假设公司内部正在开发一套电商平台,而电商平台中包含了库存、订单、商品等核心业务。这些核心业务逻辑其实呈现的就是电商平台领域。通俗的理解就是一整套体系的业务知识即代表了一个领域。好比在线教育平台,它需要有一套体系的业务,包括招生、线上教学、课程等内容。我们将这些业务抽象出领域模型,而这些领域模型表达了产品经理所阐述的业务需求,我们反复地用这些领域模型与产品经理进行讨论沟通最终确定初步的领域模型。再使用初步的领域模型指导代码设计开发。

简而言之就是:

业务知识 --> 领域模型 --> 项目设计与代码开发

不同的电商平台的核心业务逻辑大都是相似的,这部分领域知识是可以进行复用,区别在于不同公司使用的不同的编程语言,不同的前端控制框架,数据库框架。采用领域驱动设计的好处在于项目以领域模型为核心,而Spring MVC、Struts等前端控制框架或者Hibernate、Mybatis对象数据库框架属于外围技术基础,领域模型其实并不与这些基础技术产生耦合,所以在领域模型不变的情况下,我们是很容易对我们的基础设施进行更换的。

TOP