design\project\学习 DDD 读书笔记

16 篇文章 0 订阅

DDD

原文链接:Structure your microservice using a hexagonal architecture by Fredrik Lindgren
原文链接:What is DDD - Eric Evans
原文链接:DDD and Microservices: At Last, Some Boundaries!

第一部分:六边形架构

原文链接:Structure your microservice using a hexagonal architecture by Fredrik Lindgren

Hexagonal Architecture 又名:端口和适配器模式

是一种对测试友好的服务架构模式

定义

allow an application to equally be driven by users, programs, automated test or scripts, and to be developed and tested in isolation from its eventual run-time devices and databases

允许 application 研发过程由,用户驱动、旧有程序驱动、自动化测试驱动或脚本驱动

开发和测试与最终运行时设备和数据库隔离

为什么使用六边形架构

  • 提出外部与内部的概念,对比传统分层架构

    • 传统的分层架构
      传统分层架构
    • 六边形架构

    六边形架构

  • 提出对外暴露多个端口,区别与传统分层架构的向上提供接口,向右提供接口

    • 多个端口,为嘛叫六边形架构?因为懒(゜ー゜)。对比分层架构的矩形,画个六边形最省事

六边形架构小实栗

需求

浏览器 ——> 报名参加一项课程 ------> 检查课程报名条件,存储报名信息

浏览器 ——> 报名参加一项课程 ------> 发送报名确认邮件,发送通知给授课老师

使用传统的分层架构

POST 请求 ——》 报名服务 ——》通知服务

遇到问题:应用程序在一直变化

考察报名服务 参见:API Design:业务一直在变化

  • 业务需求在变化

  • 使用的技术在变化

    • 要求使用协作 sheet 存储数据

    • 协作 sheet 的版本在升级

  • 尽管使用的技术在一直变化,但是核心业务中的一些基础概念(basic business concepts)一直沿用下来

有人说:六边形架构,将常规MVC三层架构反转,以业务为主导,自顶向下

这里“业务”指的是能一直沿用下去的,经过提炼的,核心业务中的基础概念。

遇到问题:自动化测试不好使了
  • 每一次提交都需要做测试,而且是集成测试

    • 新技术协作 sheet 不便于自动化测试验证

    • 我们可以内网 模拟一套 协作 sheet 的 API 来进行测试,还不能保证准确性

所以,我们引入了端口和适配器
驱动方(Driver Side)应用程序核心(Application Core)被驱动方(Driven Side)
主动请求系统的一方为驱动者应用程序核心不需要知道实现细节被系统调用的一方为被驱动着
所有的业务逻辑必须在核心中定义非核心功能(secondary)
核心使用端口(java 中叫接口中的方法)(ports)主动定义系统边界。具体的实现通过端口和核心的双向交互
具体的实现使用适配器(Adapters)和核心做消息转换
驱动方的适配器(Adapters)通过驱动端口(driver ports)调用核心
核心通过驱动端口(driver ports)调用被驱动方。被驱动方s实现驱动端口(driver ports)
为提供测试专用适配器(Adapters)
发出 eventcqrsxxx

六边形架构——端口(ports)

端口分类
  • 交互端口(Purpose of Interaction)

    • 一组和(应用程序)外部“用户”交互的端口(接口)

    • 关键目标是交互,具有技术无关性

      问题:给学生发消息,与给老师发消息是否需要分开来?

      这是一个 trade-off,在本系统中,提供一个高层抽象,发送 Notification

  • 应用逻辑端口

    端口不应该和技术相关,而应该和应用程序逻辑相关

    接口使用业务命名,而不是 saveXxx 之类的

    • 端口属于某个应该程序

    • 这些端口定义了该应用的边界

整体架构

ports ================ components =================== adapters

自动化测试解决方案

  • 使用 生产-测试 双重适配器,用以测试业务逻辑

  • Mock 框架,模拟外部系统

  • 通过外部系统 adapter 测试外部系统,外部系统的集中测试从业务逻辑中独立出来

  • 最后做一些交付测试

设计原则
  • 隔离外部依赖关系

  • 从简单开始,如果需要,再添加额外的东西,例如单独的模块

  • 简单的 API,宽而浅的 API,不要使用错综复杂的调用层次很深的 API。

    • 尽量将 ports 封装到单个接口中,且接口中的方法全部是需要 exposed 的方法

    • 避免接口里返回接口

  • ports 中的参数使用不可变对象

第二部分:DDD 基础——是什么?为什么?怎么做?

最有效的设计软件核心逻辑的方法是什么?Models 或者是获得 Models 的方法

原文链接:What is DDD - Eric Evans

直接举个栗子

考虑集装箱运输货物的物流问题。集装箱装上船飘洋过海,到达港口,使用转运大卡车,搬到火车站或者其他航线

假设我们要从天津运到上海,我们走老秦开挖的“京杭大运河”到杭州,然后换火车送到上海

天津 ==== 京杭大运河 ====> 杭州 ====== 火车 =====> 上海

我们还不能开始设计 Model 和 API
  • 首先考虑问题域的其他旧系统,考虑系统集成 context-Mapping
  • 其次……
先 look look 高层架构
名称属性行为
货物id,始发地,重点,重量x
运输服务货物ID,航线ID,上货地点,卸货地点,船ID,时间x
  • 问题1:从软件的实现者,开发人员的角度来说

    • 可读性,可扩展性…
    • 运输服务太大了,里头逻辑太多,太复杂
    • 🤦‍♂️ 因为有一条老的软件原则,不要使用 update 数据库 state 来实现业务逻辑 🤷‍♂️
    • 你这运输表得多少状态字段啊……
  • 问题2:在非开发人员,需求定义角度来说,整体的领域模式 Model 也有问题

    • 同物流专家的角度探讨一下,基本流程,栗如把这个存储到数据库
    • 天津 ==== 京杭大运河 ====> 杭州港 = ??? => 杭州火车站 ==== 火车 ====> 上海
    • 杭州港到杭州火车站好像挺远的………………
    • ━┳━ ━┳━ 怎么和专家解释呢?这时候我们需要一种共通语言(DDD 的重要组成部分)
所以,我们的 application core ,缺少哪些基本概念(concept)?
  • 一种方案

    名称属性行为
    运输服务货物ID,…,出发时间,到达时间x
    站点是否始发站,是否终点站
  • 第二种方案

    我们还需要对比其他方案,找到最佳方案。做软件前多想想替代方案

    名称属性行为
    运输服务货物ID,…,出发时间,到达时间x
    脚程上货地点,卸货地点x
    航线上货地点,卸货地点x
  • 站点和航线除了名字,好像没有什么区别嘛!

    • 将脚程 替换 成 航线

    • 斟酌词汇,获得更好的设计

  • 一种更好的方案

    运输服务 -------> 行程 --------》站点

    名称属性行为
    运输服务货物ID,…,出发时间,到达时间x
    行程xx
    站点上货地点,卸货地点x
  • 当需求变异,不要犹豫不前,这是完善领域模型的好机会

  • 当需求编译,不要吹毛求疵,不要使用错误的 Model,换下一个

  • 文档?我们只得到了三个词汇:运输服务,行程,站点

选择一个 Model 方案

使用 UML 背后的 Model,背后的基础概念(concept) 而不是 UML 本身

不同的 Model 适用不同的目标问题,所以这个问题本身就有问题

名称属性哪个更好?
航线起点,终点,运输方式
站点位置,上/卸货地
站在不同的角度,对一个问题有不同的看法
  • 使用一种最适用于目标问题的 Model 来表述

  • 能最清晰的表述目标问题

  • 栗如地图:你是要用地图导航?还是用地图测绘?不同的问题适用不同的地图

一个 Model 由一系列 Statements 组成
  1. xxxx(abstraction)

  2. xxxx

  3. xxxx(assertion)

Modle 的定义
  • Domain 是信息和活动的集合(一个界定)

  • Model 是 Domain 的某个方面(aspect)的抽象,

  • Model 细化了 Domain

  • Model 建立在一定的假设的前提下

  • 过多考虑现实使人郁闷(Realism is a distraction),不一定有利于目标问题

Model 应该缩小关注点
  • 建议从不同的方面(aspect)建立多个 model 来,处理一些大的 Domain

  • 不要用一个 Model 代表所有

哪个 Model 更有用?用这个 Model 来解决 xxx 问题?
名称属性对于当前的问题,哪个更好
航线起点,终点,运输方式✔ 我们只需要让上一个航线终点对应到这个航线的起点就可以,所以这个 Model 更方便
站点位置,上/卸货地

对于当前问题:

天津 ==== 京杭大运河====> 杭州港 = ??? => 杭州火车站 ==== 火车 ====> 上海

所以,最终方案
名称属性行为
货物id,始发地,重点,重量x
运输服务货物ID,…,出发时间,到达时间x
行程xx
航线起点,终点,运输方式x
  • 我们的运输服务,不再直接操作数据库修改状态 ✅

  • 我们只需要让上一个航线终点对应到这个航线的起点就能连接航线

  • 主要任务是定义一些词汇:货物……

为什么要定义这些 Model

  • 因为有的业务非常复杂,有助于理解 Domain

  • 能避免,造出比现实更复杂的设计

现在业务扩展了,我们重新需要站点,用于到站计费啥的
  • 修改原有系统,替换航线为站点

  • 使用一个新的子系统,引入站点的概念

  • 使用航线的起点代表站点

怎么做?参考上文

  • 如果 model 不再适用与目标问题,不管 Model 设计的多好,弃之食草

第三部分:DDD与微服务

原文链接:DDD and Microservices: At Last, Some Boundaries!

MicroService
  • 自主的团队,隔离的实现

  • 管理大型团队带来的管理问题(acknowledge the rough and tumble of enterprises)

  • 各个部分业务编码水平不一致(cattle not pets)

  • 带来领域驱动的机遇

  • 等等其他……

两个服务之间的交互如何定义?

如何做服务集成?

  • 在交流中的上下文:语义环境,决定其含义

  • 在软件中的限界上下文:

    • 为了简化计算机间不同上下文的整合映射

    • 指定一个模块内,特定 Model 的含义一致

    • 有时候,划分模块很复杂,程序一堆乱麻

交互遇到的问题:上下文映射(Context-Map)越来越复杂

在 DDD 中,我们引入了一种新技术 Context-Map,上下文映射,假设一个没有边界的系统如下:

  • A ---- partner ---- B

    • 意味着 A 和 B 之间存在一个 translator ,使得 A 和 B 之间能相互理解
  • 假设现在 A 和 B 交互,B 和 A 交互,A 和 C 交互

    • C 是 A 的下位系统

    • C – conforming --> A <–> B:C 对 A 具有一致性(conforming)

    • 也就是说 C 中的 Context (使用相同的消息定义,或者说是相同的语言)需要保持和 A 一致,C 为了和 A 集成,放弃一部分自主技术选型的机会

    • 人们一般会这么处理下位系统 C

  • 假设 D 也需要 和 A 交互,D --> A <–>,且 D 不愿意适用和 A 一致的上下文

    • D 与 A 之间需要定义一个更复杂的 translator
  • E --> A ,A 需要从 E 获得数据

    • 这意味着,尽管是从 E 指向 A,但是是数据流,A 依旧是驱动方,E 是被驱动方
    • 这时候 A 不能很好的完成工作,因为现在 A 具有很高的耦合(C,D,E都和 A 交互),而 A 本身的 Model 是为了处理复杂逻辑而设计的,而不是处理数据转换而设计的
  • 现在 D 也需要订阅 E 发出的消息

  • F 需要订阅 B ,然后 C 确认从 B 过来的消息

    • 这时,C 需要确认 F 和 B,这是不可行的。
交互遇到的问题:我们总是有不同的 Model
  • 旧系统

  • 不同的 team 会定义不同的 model

交互遇到的问题:应用集成是,不同的系统业务不一致
  • Model 必须小清晰

    • Model 必须有清晰的定义

    • 定义清晰的上下文

    • 需要有简单的断言

      • 栗如,不存在没有订单的客户,但不是所有的系统都有这样的假设
    • 所以我们需要边界来支持业务断言

一种解决方案

让 A、C、D、E 都和新引入的 I 交互。I = 一个内部交换上下文(interchange context)

不改变原有的交互方式,只改变得到消息的方式。

内部交换上下文

  • 为了更好的执行,我们引入了一个 Domain Language

  • 以服务接口/消息的形式表示

  • 不同于服务内部的对象/功能

  • 防止限界上下文的早期-扭曲/冻结

  • 当我们有许多服务时,提供全局性的理解

  • 和企业级集成不同,我们会有不止一个交换上下文(为边界内频繁交互的服务做交换上下文)

为什么不使用逻辑边界
  • 为啥不做系统的逻辑划分?(模块划分)

  • 因为我们做逻辑划分做了几十年了,也没有看到效果。

    • 逻辑边界不是很清晰,不会有明确的物理边界

    • 规范会所有东西不现实

    • 如果是传统的单一项目,这是可行的

  • 实际经验表明,大部分的项目,逻辑划分无法经受风吹雨打

用别的方式来划分边界
  • DDD 提出了具体的(concrete boundaries)边界,微服务恰好提供这些特性

  • 服务的激增重现了一些旧问题

    • 当太多的服务进行交互时,很容易出现 “意大利面代码”(一团乱麻)
  • 有助于构建高内聚的微服务集群

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值