learn.lianglianglee.com/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html
2022-05-11 19:04:14 +08:00

1146 lines
47 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>044 服务资源模型.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/领域驱动设计实践(完)/001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html">001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html">002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/003 领域驱动设计概览.md.html">003 领域驱动设计概览.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/004 深入分析软件的复杂度.md.html">004 深入分析软件的复杂度.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/005 控制软件复杂度的原则.md.html">005 控制软件复杂度的原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/006 领域驱动设计对软件复杂度的应对(上).md.html">006 领域驱动设计对软件复杂度的应对(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/007 领域驱动设计对软件复杂度的应对(下).md.html">007 领域驱动设计对软件复杂度的应对(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/008 软件开发团队的沟通与协作.md.html">008 软件开发团队的沟通与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/009 运用领域场景分析提炼领域知识(上).md.html">009 运用领域场景分析提炼领域知识(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/010 运用领域场景分析提炼领域知识(下).md.html">010 运用领域场景分析提炼领域知识(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/011 建立统一语言.md.html">011 建立统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/012 理解限界上下文.md.html">012 理解限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/013 限界上下文的控制力(上).md.html">013 限界上下文的控制力(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/014 限界上下文的控制力(下).md.html">014 限界上下文的控制力(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/015 识别限界上下文(上).md.html">015 识别限界上下文(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/016 识别限界上下文(下).md.html">016 识别限界上下文(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/017 理解上下文映射.md.html">017 理解上下文映射.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/018 上下文映射的团队协作模式.md.html">018 上下文映射的团队协作模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/019 上下文映射的通信集成模式.md.html">019 上下文映射的通信集成模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/020 辨别限界上下文的协作关系(上).md.html">020 辨别限界上下文的协作关系(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/021 辨别限界上下文的协作关系(下).md.html">021 辨别限界上下文的协作关系(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/022 认识分层架构.md.html">022 认识分层架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/023 分层架构的演化.md.html">023 分层架构的演化.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/024 领域驱动架构的演进.md.html">024 领域驱动架构的演进.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/025 案例 层次的职责与协作关系(图文篇).md.html">025 案例 层次的职责与协作关系(图文篇).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/026 限界上下文与架构.md.html">026 限界上下文与架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/027 限界上下文对架构的影响.md.html">027 限界上下文对架构的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/028 领域驱动设计的代码模型.md.html">028 领域驱动设计的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/029 代码模型的架构决策.md.html">029 代码模型的架构决策.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/030 实践 先启阶段的需求分析.md.html">030 实践 先启阶段的需求分析.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/031 实践 先启阶段的领域场景分析(上).md.html">031 实践 先启阶段的领域场景分析(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/032 实践 先启阶段的领域场景分析(下).md.html">032 实践 先启阶段的领域场景分析(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">033 实践 识别限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html">034 实践 确定限界上下文的协作关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">035 实践 EAS 的整体架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html">036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/037 「战术篇」开篇词:领域驱动设计的不确定性.md.html">037 「战术篇」开篇词:领域驱动设计的不确定性.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/038 什么是模型.md.html">038 什么是模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/039 数据分析模型.md.html">039 数据分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/040 数据设计模型.md.html">040 数据设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/041 数据模型与对象模型.md.html">041 数据模型与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/042 数据实现模型.md.html">042 数据实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">043 案例 培训管理系统.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html">044 服务资源模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">045 服务行为模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/046 服务设计模型.md.html">046 服务设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/047 领域模型驱动设计.md.html">047 领域模型驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/048 领域实现模型.md.html">048 领域实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/049 理解领域模型.md.html">049 理解领域模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/050 领域模型与结构范式.md.html">050 领域模型与结构范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/051 领域模型与对象范式(上).md.html">051 领域模型与对象范式(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/052 领域模型与对象范式(中).md.html">052 领域模型与对象范式(中).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/053 领域模型与对象范式(下).md.html">053 领域模型与对象范式(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/054 领域模型与函数范式.md.html">054 领域模型与函数范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/055 领域驱动分层架构与对象模型.md.html">055 领域驱动分层架构与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/056 统一语言与领域分析模型.md.html">056 统一语言与领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/057 精炼领域分析模型.md.html">057 精炼领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/058 彩色 UML 与彩色建模.md.html">058 彩色 UML 与彩色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/059 四色建模法.md.html">059 四色建模法.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/060 案例 订单核心流程的四色建模.md.html">060 案例 订单核心流程的四色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/061 事件风暴与业务全景探索.md.html">061 事件风暴与业务全景探索.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/062 事件风暴与领域分析建模.md.html">062 事件风暴与领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/063 案例 订单核心流程的事件风暴.md.html">063 案例 订单核心流程的事件风暴.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/064 表达领域设计模型.md.html">064 表达领域设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/065 实体.md.html">065 实体.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/066 值对象.md.html">066 值对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/067 对象图与聚合.md.html">067 对象图与聚合.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/068 聚合设计原则.md.html">068 聚合设计原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/069 聚合之间的关系.md.html">069 聚合之间的关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/070 聚合的设计过程.md.html">070 聚合的设计过程.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/071 案例 培训领域模型的聚合设计.md.html">071 案例 培训领域模型的聚合设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/072 领域模型对象的生命周期-工厂.md.html">072 领域模型对象的生命周期-工厂.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/073 领域模型对象的生命周期-资源库.md.html">073 领域模型对象的生命周期-资源库.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/074 领域服务.md.html">074 领域服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/075 案例 领域设计模型的价值.md.html">075 案例 领域设计模型的价值.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/076 应用服务.md.html">076 应用服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/077 场景的设计驱动力.md.html">077 场景的设计驱动力.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">078 案例 薪资管理系统的场景驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html">079 场景驱动设计与 DCI 模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">080 领域事件.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/081 发布者—订阅者模式.md.html">081 发布者—订阅者模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/082 事件溯源模式.md.html">082 事件溯源模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/083 测试优先的领域实现建模.md.html">083 测试优先的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/084 深入理解简单设计.md.html">084 深入理解简单设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/085 案例 薪资管理系统的测试驱动开发(上).md.html">085 案例 薪资管理系统的测试驱动开发(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">086 案例 薪资管理系统的测试驱动开发(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">087 对象关系映射(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">088 对象关系映射(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">089 领域模型与数据模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/090 领域驱动设计对持久化的影响.md.html">090 领域驱动设计对持久化的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/091 领域驱动设计体系.md.html">091 领域驱动设计体系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/092 子领域与限界上下文.md.html">092 子领域与限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/093 限界上下文的边界与协作.md.html">093 限界上下文的边界与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/094 限界上下文之间的分布式通信.md.html">094 限界上下文之间的分布式通信.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/095 命令查询职责分离.md.html">095 命令查询职责分离.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/096 分布式柔性事务.md.html">096 分布式柔性事务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/097 设计概念的统一语言.md.html">097 设计概念的统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/098 模型对象.md.html">098 模型对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/099 领域驱动设计参考过程模型.md.html">099 领域驱动设计参考过程模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/100 领域驱动设计的精髓.md.html">100 领域驱动设计的精髓.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/101 实践 员工上下文的领域建模.md.html">101 实践 员工上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/102 实践 考勤上下文的领域建模.md.html">102 实践 考勤上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/103 实践 项目上下文的领域建模.md.html">103 实践 项目上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/104 实践 培训上下文的业务需求.md.html">104 实践 培训上下文的业务需求.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/105 实践 培训上下文的领域分析建模.md.html">105 实践 培训上下文的领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/106 实践 培训上下文的领域设计建模.md.html">106 实践 培训上下文的领域设计建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/107 实践 培训上下文的领域实现建模.md.html">107 实践 培训上下文的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/108 实践 EAS 系统的代码模型.md.html">108 实践 EAS 系统的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/109 后记:如何学习领域驱动设计.md.html">109 后记:如何学习领域驱动设计.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>044 服务资源模型</h1>
<p>在软件领域中使用最频繁的词语之一就是“服务”。在领域驱动设计中也有领域服务、应用服务之分。通常一个对象被命名为服务意味着它具有为客户提供某种业务行为的能力。服务与客户存在一种协作关系协作的接口可以称之为“契约Contract”。</p>
<p>我们在这里探讨服务模型,指的是面向当前应用外部客户的<strong>远程服务</strong>,在分层架构中,属于扮演了“北向网关”角色的基础设施层。由于客户位于当前应用之外,意味着通信模式需要采用分布式通信,传递的对象也需要视通信协议与框架而选择支持序列化和反序列化的对象协议,如 XML、JSON 或 ProtocolBuffer 等。远程服务的消费者包括所有需要发起跨进程调用的前端 UI、下游服务或其他第三方消费者。因此服务模型驱动设计实际上是以外部远程服务为建模视角进行的设计过程。</p>
<p>如果说数据模型驱动设计是自下而上的设计过程,那么服务模型驱动设计则可以认为是自上而下,或者自外而内,即需要站在外部消费者的角度去思考服务的设计。</p>
<h3>服务分析模型</h3>
<p>当我们从服务视角建立服务分析模型时,有两种不同的设计思想。一种思想是将服务视为一种资源,即 REST 架构风格的设计模式。通过这种方式获得的资源对象一般认为是基础设施层“北向网关”的内容通常被定义为资源Resource类或控制器Controller命名方式为 <code>&lt;Model&gt;+Resource</code><code>&lt;Model&gt;+Controller</code>。关于资源与控制器的差异,我会在后面讲解分层架构与对象模型之间的关系时详细阐述。采用这种设计思想建立的服务分析模型可以称之为“<strong>服务资源模型</strong>”。</p>
<p>服务模型驱动设计的另一种设计思想是将<strong>服务视为一种行为</strong>体现了客户端与远程服务之间的行为协作。分析时首先想到的不是服务而是客户端需要什么样的操作然后将该操作转换为职责服务就是职责的履行者。从角色看服务是一种行为能力的提供者Provider而调用服务的客户端就是消费者Consumer。消费者与提供者之间协作的关键在于如何确定消费请求从而确定对应的服务契约因而可以称这种服务分析模型为“服务行为模型”。</p>
<h3>服务资源模型</h3>
<p>RESTREpresentational State Transfer表述性状态迁移架构风格起源于 Web 的架构体系在这个架构体系中URI 和资源扮演了主要的角色。《REST 实战》认为 REST 服务设计的关键是<strong>从资源的角度思考服务设计</strong>。书中写道:</p>
<blockquote>
<p>资源是基于 Web 系统的基础构建块在某种程度上Web 经常被称作是“面向资源的”。一个资源可以是我们暴露给 Web 的任何东西,从一个文档或视频片段,到一个业务过程或设备。从消费者的观点看,资源可以是消费者能够与之交互以达成某种目标的任何东西。</p>
</blockquote>
<p>采用面向资源的架构设计思想,意味着服务模型驱动设计要从识别资源开始。首先要确认客户访问的资源是什么?这种<strong>面向资源</strong>的设计思想可以认为是对建模的一种约束。有时候,通过服务行为识别出资源对象是顺理成章之事,例如查询我的订单,那么 Order 就是客户要访问的资源。有时候,需求描述是面向行为的,例如执行一次统计分析,我们会习惯于从行为的角度去分析,例如将服务建模为 AnalysisService但在 REST 架构风格的语境中,更应该识别出资源对象:执行一次统计分析,就是创建一个分析结果,由此获得资源对象 AnalysisResult。</p>
<p>REST 更丰富的内涵还体现在“HATEOASHypermedia As The Engine Of Application State超媒体作为应用状态的引擎”它才是 REST 架构风格的核心原则。基于 HATEOAS客户端与服务器端的交互其实代表的是一种状态的迁移。服务和客户端之间交换的并非应用的状态而是资源状态的表述这个表述通过链接指向下一个迁移的应用状态链接的值就是另一个资源的 URI。例如当订单Order资源被成功创建后假设订单的订单号为 1111那么返回的资源表述中就应该包含支付Payment资源的 URI即 http://practiceddd.com/payments/orders/1111。</p>
<p>一个内嵌了链接的资源就是一个<strong>超媒体Hypermedia</strong>,通过它可以改变应用的状态,这正是 HATEOAS 的含义。显然HATEOAS 可以通过应用状态的迁移来表达一个业务流程。由于超媒体内部封装了状态迁移的规则,客户在访问资源时并不知道这些规则,使得客户和服务之间能够形成松散耦合的服务协议。因此,当我们基于 REST 架构风格对服务建模时,建立的服务模型应包含资源以及超媒体,如下图所示:</p>
<p><img src="assets/17013010-8b27-11e9-8761-5be5e930d8ab" alt="img" /></p>
<p>既然 REST 的核心思想将“超媒体作为应用状态的引擎”因而在面向资源进行分析建模时需要重点把握业务流程中资源状态的变化。状态的变更是针对资源的一个操作Action触发的在满足某个业务规则之后当前资源就会因为状态变更而链接到另外一个资源。为了体现资源状态的变化以及资源与操作及链接资源之间的关系我们可以针对业务流程绘制状态机。</p>
<p>以咖啡店为例,我们可以梳理出分别以顾客和咖啡师为视角的业务流程。顾客在选定了饮品之后,首先会点单和付款。在点单到付款之间,顾客还可以修改订单。顾客付款成功之后,订单就被确认,顾客会等待直到获得咖啡师制作的饮品。当顾客获取饮品后,当前订单就算完成:</p>
<p><img src="assets/340f8ad0-8b27-11e9-8ada-d1fb8c4d56bd" alt="59765062.png" /></p>
<p>咖啡师的业务流程是一个循环流程,他(她)会不断地接受下一个订单,然后在收取费用之后开始制作饮品,最后将制作好的饮品交到顾客手中:</p>
<p><img src="assets/3c770810-8b27-11e9-a76b-fb08a6f11f67" alt="39248012.png" /></p>
<p>状态机里的每一个状态迁移,都代表着与 Web 资源的一次交互。每一次迁移,都是用户针对资源的操作触发的。因此,利用状态图中的状态与触发状态迁移的操作可以帮助我们驱动出<strong>资源</strong>的定义。例如下订单操作与 OrderPlaced 状态可以驱动出 Orders 资源,付款操作与 Paid 状态可以驱动出 Payments 资源,制作饮品操作和 DrinkMade 状态可以驱动出 Drinks 资源:</p>
<p><img src="assets/59337240-8b27-11e9-8761-5be5e930d8ab" alt="img" /></p>
<p>仅仅识别出资源并不足以建立服务资源模型,因为建立服务资源模型的最终目的是设计 REST 服务。一个 REST 服务实际上是对客户端与资源之间交互协作的抽象,它利用了<strong>关注点分离</strong>原则分离了资源、访问资源的动作及表示资源的形式:</p>
<p><img src="assets/636639a0-8b27-11e9-8ada-d1fb8c4d56bd" alt="img" /></p>
<p>资源作为名词是“到一组实体的概念上的映射”动词是在资源上执行的动作而表示形式则用来“捕获资源的当前或预期的状态并在组件之间传递这种表示形式”。乍一看动词正好体现了作用在资源之上的访问行为那就代表了业务概念的一种业务行为但是REST 架构风格为了保证客户端与服务器端之间的松散耦合,对这样的访问动词提炼了统一的接口。这正是 Roy Fielding 推导 REST 风格时的一种架构约束。在那篇著名的论文《架构风格与基于网络的软件架构设计》中,他写道:</p>
<blockquote>
<p>使 REST 架构风格区别于其他基于网络的架构风格的核心特征是,它强调组件之间要有一个统一的接口。通过在组件接口上应用通用性的软件工程原则,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了独立的可进化性。然而,付出的代价是,统一接口降低了效率,因为信息都使用标准化的形式来转移,而不能使用特定于应用的需求的形式。</p>
</blockquote>
<p>为了满足“统一接口”的约束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 动词,由于它不会引起状态迁移,因而不曾在状态机中体现。</p>
<p>由于要遵循统一接口的架构约束,使得服务资源模型与服务行为模型之间的最大区别除了服务的建模思想不同之外,还在于对服务行为的认识。服务资源模型认为所有针对资源进行操作的服务行为都是统一的,这就抹去了服务的业务语义。如果要区分不同的服务行为,就需要结合 HTTP 动词、由资源组成的 URI 及请求和响应信息来共同分辨,如此才能将客户端的请求路由到正确的服务行为上。</p>
<p>假设服务 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 是更新订单,它的请求定义是:</p>
<pre><code class="language-json">{
&quot;additions&quot;: &quot;shot&quot;,
&quot;cost&quot;: 28.00
}
</code></pre>
<p>服务 D 是确认订单,它的请求定义是:</p>
<pre><code class="language-json">{
&quot;status&quot;: &quot;Confirmed&quot;
}
</code></pre>
<p>客户端请求的定义差异可以说明这是两个完全不同的服务,使得在对请求进行路由时可以正确地完成对资源的操作,但由于请求自身缺乏业务语义,因此并不能直观体现该服务代表的行为到底是什么。因此,这些信息或许足以支持服务的路由,却不足以说明服务 API。</p>
<p>对于一个 REST 服务而言,设计服务的 API 自有其规矩,例如微软定义了《<a href="https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md.html">REST API 指南</a>就规定了状态码的正确使用、URL 的结构、HTTP 动词的选择规范、请求头Request Header的定义、响应头Response Header的定义、请求与响应的格式、JSON 标准与服务版本管理等内容。Swagger 也定义了《<a href="https://swagger.io/specification">OpenAPI 规格说明书</a>》,对 API 的各个组成部分给出了设计约束。同时Swagger 还提供了 SwaggerHub 工具管理 API下图就是一个 REST 服务 API 的管理页面(来自 Swagger 默认创建的 Demo</p>
<p><img src="assets/18317d40-8b28-11e9-8761-5be5e930d8ab" alt="81331873.png" /></p>
<p>通过以上文档可以看到 REST 服务为“添加库存项adds an inventory itemHTTP 动词为 POSTURI 为 /inventory客户端请求的协议则被定义在 components 的schemas中</p>
<pre><code class="language-json"> 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
</code></pre>
<p>这个 Schema 定义了请求消息中各个属性的类型、结构、必备性和格式等,图的右下角还提供了对应请求的一个样例:</p>
<pre><code class="language-json">{
&quot;id&quot;: &quot;d290f1ee-6c54-4b01-90e6-d701748f0851&quot;,
&quot;name&quot;: &quot;Widget Adapter&quot;,
&quot;releaseDate&quot;: &quot;2016-08-29T09:12:33.001Z&quot;,
&quot;manufacturer&quot;: {
&quot;name&quot;: &quot;ACME Corporation&quot;,
&quot;homePage&quot;: &quot;https://www.acme-corp.com&quot;,
&quot;phone&quot;: &quot;408-867-5309&quot;
}
}
</code></pre>
<p>服务端响应的定义规定了三种不同场景返回的状态码,即成功创建时返回 201请求无效返回 400库存项已经存在返回 409</p>
<pre><code class="language-json">responses:
'201':
description: item created
'400':
description: 'invalid input, object invalid'
'409':
description: an existing item already exists
</code></pre>
<p>我通常将组成 REST 服务 API 的请求和响应都认为是消息对象。请求消息分为命令消息和查询消息。命令消息往往伴随着 POST、PUT、DELETE 与 PATCH 动词这些操作往往是不安全的会对资源产生副作用。此外PUT 与 DELETE 动词是幂等的,即一次或多次执行该操作产生的结果是一致的。查询消息常常使用 GET 动词,对应的操作是安全的,也是幂等的。由于 URI 也可以包含请求参数,有的查询操作并不需要定义额外的请求消息。</p>
<p>如果请求为命令操作,由于它会导致资源状态的迁移,因此在对应的响应消息中还需要定义链接,以指向下一个迁移的应用状态。倘若请求为查询操作,返回的响应消息还将包含查询结果。不管是命令操作,还是查询操作,返回的响应消息都应该包含标准的 HTTP 状态码。状态码Status Code的使用必须正确要符合状态码的语义例如 200 和 201 都是操作成功,但后者意味着资源的创建成功。</p>
<p>我们也可以利用可视化的方式来表现服务资源模型。Thomas Erl 等人的著作《SOA 与 REST》建议使用圆形代表服务而用包含了三角形标记的圆形代表 REST 服务资源,如:</p>
<p><img src="assets/6010b090-8b28-11e9-8ada-d1fb8c4d56bd" alt="img" /></p>
<p>可视化的三角形标记表示该服务遵循了 REST 风格的设计约束,模型中包含了服务 API 的主要构成资源、HTTP 动词与 URI。若需建立直观的服务资源模型可以考虑采用这样的建模形式。</p>
<p>REST 服务的设计属于 REST 服务规范的一部分,但对于服务模型驱动设计而言,更关注由服务资源开始由外向内的设计驱动力,即将远程服务作为设计的起点,逐步从接口到实现向内层层推进。因此,在服务模型驱动设计过程中,需要明确在接口内部的实现中需要哪些对象进行协作,以支持远程服务提供给客户端的功能。这一设计过程与服务模型的类型无关,无论是服务资源模型还是后面要讲的服务行为模型,设计的驱动力都是完全一样的,都属于服务设计模型的一部分。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997e59fcb33cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>