【DDD】学习笔记-服务资源模型

在软件领域中,使用最频繁的词语之一就是“服务”。在领域驱动设计中,也有领域服务、应用服务之分。通常,一个对象被命名为服务,意味着它具有为客户提供某种业务行为的能力。服务与客户存在一种协作关系,协作的接口可以称之为“契约(Contract)”。

我们在这里探讨服务模型,指的是面向当前应用外部客户的远程服务,在分层架构中,属于扮演了“北向网关”角色的基础设施层。由于客户位于当前应用之外,意味着通信模式需要采用分布式通信,传递的对象也需要视通信协议与框架而选择支持序列化和反序列化的对象协议,如 XML、JSON 或 ProtocolBuffer 等。远程服务的消费者包括所有需要发起跨进程调用的前端 UI、下游服务或其他第三方消费者。因此,服务模型驱动设计实际上是以外部远程服务为建模视角进行的设计过程。

如果说数据模型驱动设计是自下而上的设计过程,那么服务模型驱动设计则可以认为是自上而下,或者自外而内,即需要站在外部消费者的角度去思考服务的设计。

服务分析模型

当我们从服务视角建立服务分析模型时,有两种不同的设计思想。一种思想是将服务视为一种资源,即 REST 架构风格的设计模式。通过这种方式获得的资源对象,一般认为是基础设施层“北向网关”的内容,通常被定义为资源(Resource)类或控制器(Controller)类,命名方式为 <Model>+Resource 或 <Model>+Controller。关于资源与控制器的差异,我会在后面讲解分层架构与对象模型之间的关系时详细阐述。采用这种设计思想建立的服务分析模型可以称之为“服务资源模型”。

服务模型驱动设计的另一种设计思想是将服务视为一种行为,体现了客户端与远程服务之间的行为协作。分析时,首先想到的不是服务,而是客户端需要什么样的操作,然后将该操作转换为职责,服务就是职责的履行者。从角色看,服务是一种行为能力的提供者(Provider),而调用服务的客户端就是消费者(Consumer)。消费者与提供者之间协作的关键在于如何确定消费请求,从而确定对应的服务契约,因而可以称这种服务分析模型为“服务行为模型”。

服务资源模型

REST(REpresentational State Transfer,表述性状态迁移)架构风格起源于 Web 的架构体系,在这个架构体系中,URI 和资源扮演了主要的角色。《REST 实战》认为 REST 服务设计的关键是从资源的角度思考服务设计。书中写道:

资源是基于 Web 系统的基础构建块,在某种程度上,Web 经常被称作是“面向资源的”。一个资源可以是我们暴露给 Web 的任何东西,从一个文档或视频片段,到一个业务过程或设备。从消费者的观点看,资源可以是消费者能够与之交互以达成某种目标的任何东西。

采用面向资源的架构设计思想,意味着服务模型驱动设计要从识别资源开始。首先要确认客户访问的资源是什么?这种面向资源的设计思想可以认为是对建模的一种约束。有时候,通过服务行为识别出资源对象是顺理成章之事,例如查询我的订单,那么 Order 就是客户要访问的资源。有时候,需求描述是面向行为的,例如执行一次统计分析,我们会习惯于从行为的角度去分析,例如将服务建模为 AnalysisService;但在 REST 架构风格的语境中,更应该识别出资源对象:执行一次统计分析,就是创建一个分析结果,由此获得资源对象 AnalysisResult。

REST 更丰富的内涵还体现在“HATEOAS(Hypermedia As The Engine Of Application State),超媒体作为应用状态的引擎”,它才是 REST 架构风格的核心原则。基于 HATEOAS,客户端与服务器端的交互其实代表的是一种状态的迁移。服务和客户端之间交换的并非应用的状态,而是资源状态的表述,这个表述通过链接指向下一个迁移的应用状态,链接的值就是另一个资源的 URI。例如当订单(Order)资源被成功创建后,假设订单的订单号为 1111,那么返回的资源表述中,就应该包含支付(Payment)资源的 URI,即 http://practiceddd.com/payments/orders/1111。

一个内嵌了链接的资源就是一个超媒体(Hypermedia),通过它可以改变应用的状态,这正是 HATEOAS 的含义。显然,HATEOAS 可以通过应用状态的迁移来表达一个业务流程。由于超媒体内部封装了状态迁移的规则,客户在访问资源时并不知道这些规则,使得客户和服务之间能够形成松散耦合的服务协议。因此,当我们基于 REST 架构风格对服务建模时,建立的服务模型应包含资源以及超媒体,如下图所示:

img

既然 REST 的核心思想将“超媒体作为应用状态的引擎”,因而在面向资源进行分析建模时,需要重点把握业务流程中资源状态的变化。状态的变更是针对资源的一个操作(Action)触发的,在满足某个业务规则之后,当前资源就会因为状态变更而链接到另外一个资源。为了体现资源状态的变化,以及资源与操作及链接资源之间的关系,我们可以针对业务流程绘制状态机。

以咖啡店为例,我们可以梳理出分别以顾客和咖啡师为视角的业务流程。顾客在选定了饮品之后,首先会点单和付款。在点单到付款之间,顾客还可以修改订单。顾客付款成功之后,订单就被确认,顾客会等待直到获得咖啡师制作的饮品。当顾客获取饮品后,当前订单就算完成:

59765062.png

咖啡师的业务流程是一个循环流程,他(她)会不断地接受下一个订单,然后在收取费用之后开始制作饮品,最后将制作好的饮品交到顾客手中:

39248012.png

状态机里的每一个状态迁移,都代表着与 Web 资源的一次交互。每一次迁移,都是用户针对资源的操作触发的。因此,利用状态图中的状态与触发状态迁移的操作可以帮助我们驱动出资源的定义。例如下订单操作与 OrderPlaced 状态可以驱动出 Orders 资源,付款操作与 Paid 状态可以驱动出 Payments 资源,制作饮品操作和 DrinkMade 状态可以驱动出 Drinks 资源:

img

仅仅识别出资源并不足以建立服务资源模型,因为建立服务资源模型的最终目的是设计 REST 服务。一个 REST 服务实际上是对客户端与资源之间交互协作的抽象,它利用了关注点分离原则分离了资源、访问资源的动作及表示资源的形式:

img

资源作为名词,是“到一组实体的概念上的映射”,动词是在资源上执行的动作,而表示形式则用来“捕获资源的当前或预期的状态,并在组件之间传递这种表示形式”。乍一看,动词正好体现了作用在资源之上的访问行为,那就代表了业务概念的一种业务行为;但是,REST 架构风格为了保证客户端与服务器端之间的松散耦合,对这样的访问动词提炼了统一的接口。这正是 Roy Fielding 推导 REST 风格时的一种架构约束。在那篇著名的论文《架构风格与基于网络的软件架构设计》中,他写道:

使 REST 架构风格区别于其他基于网络的架构风格的核心特征是,它强调组件之间要有一个统一的接口。通过在组件接口上应用通用性的软件工程原则,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了独立的可进化性。然而,付出的代价是,统一接口降低了效率,因为信息都使用标准化的形式来转移,而不能使用特定于应用的需求的形式。

为了满足“统一接口”的约束,REST 采用标准的 HTTP 协议语义来描述客户端和服务器端的交互,即 GET、POST、PUT、DELETE、PATCH、HEAD、OPTION、TRACE 八种不同类型的 HTTP 动词。在这些 HTTP 动词中,POST、PUT、DELETE 与 PATCH 对资源的操作都会导致资源状态的迁移;而 GET、HEAD、OPTION 和 TRACE 用于查看资源的当前状态,并不会引起状态迁移。例如,在前面所述的咖啡店案例中,下订单操作采用的是 POST 动词,订单状态从初始状态迁移到 OrderPlaced,修改订单操作采用的是 PUT 动词,它使订单状态从 OrderPlaced 迁移到 OrderUpdated;而查询订单状态、查询饮品等操作采用的是 GET 动词,由于它不会引起状态迁移,因而不曾在状态机中体现。

由于要遵循统一接口的架构约束,使得服务资源模型与服务行为模型之间的最大区别除了服务的建模思想不同之外,还在于对服务行为的认识。服务资源模型认为所有针对资源进行操作的服务行为都是统一的,这就抹去了服务的业务语义。如果要区分不同的服务行为,就需要结合 HTTP 动词、由资源组成的 URI 及请求和响应信息来共同分辨,如此才能将客户端的请求路由到正确的服务行为上。

假设服务 A 与服务 B 的 URI 皆为 https://cafe.org/orders/,但如果服务 A 的 HTTP 动词是 GET,服务 B 的 HTTP 动词是 POST,就能区分出两个不同的服务行为:查询所有订单与创建订单。又假设服务 C 与服务 D 的 URI 皆为 https://cafe.org/orders/12345,且 HTTP 动词皆为 PUT,这时仅靠 URI 和 HTTP 动词就无法分辨服务,需要再结合服务的客户端请求或响应。例如服务 C 是更新订单,它的请求定义是:

{
    "additions": "shot",
    "cost": 28.00 
}

服务 D 是确认订单,它的请求定义是:

{
    "status": "Confirmed"
}

客户端请求的定义差异可以说明这是两个完全不同的服务,使得在对请求进行路由时可以正确地完成对资源的操作,但由于请求自身缺乏业务语义,因此并不能直观体现该服务代表的行为到底是什么。因此,这些信息或许足以支持服务的路由,却不足以说明服务 API。

对于一个 REST 服务而言,设计服务的 API 自有其规矩,例如微软定义了《REST API 指南》,就规定了状态码的正确使用、URL 的结构、HTTP 动词的选择规范、请求头(Request Header)的定义、响应头(Response Header)的定义、请求与响应的格式、JSON 标准与服务版本管理等内容。Swagger 也定义了《OpenAPI 规格说明书》,对 API 的各个组成部分给出了设计约束。同时,Swagger 还提供了 SwaggerHub 工具管理 API,下图就是一个 REST 服务 API 的管理页面(来自 Swagger 默认创建的 Demo):

81331873.png

通过以上文档可以看到 REST 服务为“添加库存项(adds an inventory item)”,HTTP 动词为 POST,URI 为 /inventory,客户端请求的协议则被定义在 components 的schemas中:

  schemas:
    InventoryItem:
      type: object
      required:
        - id
        - name
        - manufacturer
        - releaseDate
      properties:
        id:
          type: string
          format: uuid
          example: d290f1ee-6c54-4b01-90e6-d701748f0851
        name:
          type: string
          example: Widget Adapter
        releaseDate:
          type: string
          format: date-time
          example: '2016-08-29T09:12:33.001Z'
        manufacturer:
          $ref: '#/components/schemas/Manufacturer'
    Manufacturer:
      required:
        - name
      properties:
        name:
          type: string
          example: ACME Corporation
        homePage:
          type: string
          format: url
          example: 'https://www.acme-corp.com'
        phone:
          type: string
          example: 408-867-5309
      type: object

这个 Schema 定义了请求消息中各个属性的类型、结构、必备性和格式等,图的右下角还提供了对应请求的一个样例:

{
  "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "name": "Widget Adapter",
  "releaseDate": "2016-08-29T09:12:33.001Z",
  "manufacturer": {
    "name": "ACME Corporation",
    "homePage": "https://www.acme-corp.com",
    "phone": "408-867-5309"
  }
}

服务端响应的定义规定了三种不同场景返回的状态码,即成功创建时返回 201,请求无效返回 400,库存项已经存在返回 409:

responses:
    '201':
      description: item created
    '400':
      description: 'invalid input, object invalid'
    '409':
      description: an existing item already exists

我通常将组成 REST 服务 API 的请求和响应都认为是消息对象。请求消息分为命令消息和查询消息。命令消息往往伴随着 POST、PUT、DELETE 与 PATCH 动词,这些操作往往是不安全的,会对资源产生副作用。此外,PUT 与 DELETE 动词是幂等的,即一次或多次执行该操作产生的结果是一致的。查询消息常常使用 GET 动词,对应的操作是安全的,也是幂等的。由于 URI 也可以包含请求参数,有的查询操作并不需要定义额外的请求消息。

如果请求为命令操作,由于它会导致资源状态的迁移,因此在对应的响应消息中还需要定义链接,以指向下一个迁移的应用状态。倘若请求为查询操作,返回的响应消息还将包含查询结果。不管是命令操作,还是查询操作,返回的响应消息都应该包含标准的 HTTP 状态码。状态码(Status Code)的使用必须正确,要符合状态码的语义,例如 200 和 201 都是操作成功,但后者意味着资源的创建成功。

我们也可以利用可视化的方式来表现服务资源模型。Thomas Erl 等人的著作《SOA 与 REST》建议使用圆形代表服务,而用包含了三角形标记的圆形代表 REST 服务资源,如:

img

可视化的三角形标记表示该服务遵循了 REST 风格的设计约束,模型中包含了服务 API 的主要构成:资源、HTTP 动词与 URI。若需建立直观的服务资源模型,可以考虑采用这样的建模形式。

REST 服务的设计属于 REST 服务规范的一部分,但对于服务模型驱动设计而言,更关注由服务资源开始由外向内的设计驱动力,即将远程服务作为设计的起点,逐步从接口到实现向内层层推进。因此,在服务模型驱动设计过程中,需要明确在接口内部的实现中需要哪些对象进行协作,以支持远程服务提供给客户端的功能。这一设计过程与服务模型的类型无关,无论是服务资源模型还是后面要讲的服务行为模型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/375611.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PCMag评选“小企业优选CRM软件”——Zoho Bigin上榜

当我们谈论企业怎样选择CRM管理系统时&#xff0c;大型企业、中型企业以及小型或初创等不同规模的企业需求各不相同&#xff0c;很难用一套软件来适配所有公司。以小企业为例&#xff0c;大多面临预算紧张、没有专业的IT部门或支持团队、暂时用不到高级定制功能等现状。基于这个…

Java面向对象 构造器的重载

目录 创建类测试构造器的重载分析 创建类 public class Person02 {//属性&#xff1a;String name;int age;double height;//空构造器public Person02(){}public Person02(String name,int age,double height){//当形参名字和属性名字重名的时候&#xff0c;会出现就近原则&am…

Excel——重复项处理

一、高亮重复项 选择需要高亮重复项的列/单元格 选择【数据】——【重复项】—— 【高亮重复项】 如果高亮重复项的数据较长&#xff0c;例如&#xff1a;身份证号、银行卡&#xff0c;可以勾选下列选项&#xff0c;能够精准检查重复项。 结果如下所示 如果想要清除高亮的重复…

项目02《游戏-10-开发》Unity3D

【完成本集功能后共享1-10集整套代码】 基于 项目02《游戏-09-开发》Unity3D &#xff0c; 任务&#xff1a;传送至其他场景&#xff0c; 首先在场景中加入传送门&#xff0c; 设置人物标签&#xff0c; using UnityEngine; using UnityEngine.SceneManagement; u…

12.0 Zookeeper 数据同步流程

在 Zookeeper 中&#xff0c;主要依赖 ZAB 协议来实现分布式数据一致性。 ZAB 协议分为两部分&#xff1a; 消息广播崩溃恢复 消息广播 Zookeeper 使用单一的主进程 Leader 来接收和处理客户端所有事务请求&#xff0c;并采用 ZAB 协议的原子广播协议&#xff0c;将事务请求…

docker 运行jar包 指定配置文件

要在Docker中运行JAR包并指定配置文件&#xff0c;你可以创建一个Dockerfile来定义你的容器环境&#xff0c;并在其中指定如何运行JAR包和配置文件。下面是一个简单的例子&#xff0c;展示了如何在Dockerfile中设置这些配置&#xff1a; 第一步&#xff1a;创建 Dockerfile文件…

RCS系统之:机器人状态

在设计RCS系统平台时&#xff0c;机器人总共设计状态有&#xff1a; 离线模式&#xff1b; 如图&#xff0c;18号机器人呈灰黑色&#xff0c;表示机器人没有上电状态 工作模式&#xff1b; 如图&#xff0c;10号机器人成绿色&#xff0c;表示机器人处于工作模式&#xff0c;等…

SQL--约束

概述 概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确、有效性和完整性。 分类: 约束演示 上面我们介绍了数据库中常见的约束&#xff0c;以及约束涉及到的关键字&#xff0c;那这些约束我们…

IDEA创建Java类时自动添加注释(作者、年份、月份)

目录 IDEA创建Java类时自动添加注释&#xff08;作者、年份、月份&#xff09;如图&#xff1a; IDEA创建Java类时自动添加注释&#xff08;作者、年份、月份&#xff09; 简单记录下&#xff0c;IDEA创建Java类时自动添加注释&#xff08;作者、年份、月份&#xff09;&#…

Mysql MGR搭建

一、架构说明 1.1 架构概述 MGR(单主)VIP架构是一种分布式数据库架构&#xff0c;其中数据库系统采用单主复制模式&#xff0c; 同时引入虚拟IP(VIP)来提高可用性和可扩展性。 这种架构结合了传统主从复制和虚拟IP技术的优势&#xff0c;为数据库系统提供了高可用、 高性能和…

Python HTTP隧道在远程通信中的应用:穿越网络的“魔法门”

在这个数字化时代&#xff0c;远程通信就像是我们日常生活中的“魔法门”&#xff0c;让我们可以随时随地与远方的朋友、同事或服务器进行交流。而在这扇“魔法门”的背后&#xff0c;Python HTTP隧道技术发挥着举足轻重的作用。 想象一下&#xff0c;你坐在家里的沙发上&…

python二维数组初始化的一个极其隐蔽的bug(浅拷贝)

初始化一个三行三列的矩阵 m n 3初始化方式1 a [[0 for i in range(m)] for j in range(n)]初始化方式2 b [] row [0 for i in range(0,m)] for i in range(0,n):b.append(row)分别输出两个初始化的结果 for row in a:print(row) for row in b:print(row)当前的输出为…

龙测科技荣获2023年度技术生态构建奖

本月&#xff0c;由极客传媒举办的“有被Q到”2024 InfoQ 极客传媒合作伙伴年会顺利举办&#xff0c;龙测科技喜获2023年度技术生态构建奖。 InfoQ是首批将Node.js、HTML5、Docker等技术全面引入中国的技术媒体之一&#xff0c;秉承“扎根社区、服务社区、引领社区”的理念&…

【element-ui】el-select下拉框el-date-picker弹出框定位问题解决方案

问题描述&#xff1a; 项目开发过程中发现el-select和el-date-picker弹出框显示时候&#xff0c;滚动屏幕&#xff0c;导致弹出框定位出现问题。 首先考虑到看一下element-ui官网提供的api&#xff0c;如下图 1、select提供了popper-append-to-body属性的配置 代码如下&#x…

百面嵌入式专栏(面试题)进程管理相关面试题1.0

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍进程管理相关面试题 。 一、进程管理相关面试题 进程是什么?操作系统如何描述和抽象一个进程?进程是否有生命周期?如何标识一个进程?进程与进程之间的关系如何?Linux操作系统的进程0是什么?Linux操…

C++函数分文件编写之VScode版

VScode实现函数的分文件编写 1.下载插件创建项目2.分文件编写内容3.修改主函数文件名 我在分享内容时经常用的软件是VScode&#xff0c;相信有些内存敏感或需要VScode便利性的小伙伴也是更愿意使用VScode。那么接下来我们就盘一盘怎样使用VScode实现分文件编写。 1.下载插件创建…

解决maven 在IDEA 下载依赖包速度慢的问题

1.idea界面双击shift键 2.打开setting.xml文件 复制粘贴 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sc…

linux centos安装neofetch

简介 neofetch是一个命令行工具&#xff0c;可以用来显示系统的基本信息和硬件配置。它支持多种操作系统&#xff0c;包括Linux、macOS和Windows等。 安装 增加yum源 curl -o /etc/yum.repos.d/konimex-neofetch-epel-7.repo https://copr.fedorainfracloud.org/coprs/konime…

golang windows 环境搭建 环境配置

golang windows 环境搭建 环境配置 Golang学习之路一环境搭建 MacBook Linux 树莓派raspberrypi安装Golang环境 官网下载地址: https://go.dev/dl/ https://golang.google.cn/dl/ 下载对应系统版本&#xff0c;例如windows 64位系统&#xff0c;下载&#xff1a;xxx.window…

如何在苹果Mac上进行分屏,多任务处理?

Apple 在 macOS Catalina 中引入了 Split View&#xff0c;让您可以同时查看两个应用程序。如果同时处理多个应用程序&#xff0c;但在它们之间切换时感到沮丧&#xff0c;小编教给大家在 Macbook Pro/Air 或 iMac 上使用分屏功能流畅地进行多任务处理。 注意&#xff1a;您可…