写点什么

为什么说要用 DDD 替代 CRUD 来设计 API

  • 2017-09-18
  • 本文字数:2187 字

    阅读完需:约 7 分钟

来自亚马逊的高级工程师 James Hood 以简单明了的例子说明了为什么要用 DDD 替代 CRUD 来设计 REST API。

REST 以资源为中心,这些资源以 URI 的形式呈现。在调用 HTTP 时,通过指定一个 HTTP 动词和一个资源 URI 对某个特定的资源进行操作。大部分 REST 框架都提供了生成器,你只要指定一个资源的名字,框架就会为你生成脚手架(scaffold)。不过,这些生成器默认使用的是 CRUD 模型(Create、Read、Update、Delete),它们把资源看成是一系列属性的集合,使用 JSON 或与特定语言相关的数据对象来表示资源,并生成用于对资源进行创建、读取、更新和删除操作的方法。

虽然这给开发者带来了便利,但我觉得这样是有问题的。我不喜欢 CRUD 这样的说法,尤其不喜欢当中的 U。一般的更新操作允许客户端更新资源的任何一个字段,并使用新版本覆盖已有的版本。但如果你允许客户端这么做,那么你的服务 API 就失去了应有的价值。服务层的一个关键价值在于为底层的数据增加业务约束,因此,资源最终都需要带上业务约束。

那么,难道我们就不能给更新操作增加业务约束吗?让我们以最简单的银行账户为例。首先,不能让客户通过调用 API 来随意更新他们的账户余额。另外,账户或许需要最小余额的限制。你在更新操作里做了一些检查,账户余额的变动必须发生在一个指定的范围内。那么这样问题就解决了吗?当然没有。任何一次余额的调整都需要与某种事务相对应,不是吗?是存入、取出,还是转账?如果客户要更改账户该怎么办?这样做是被允许的吗?这样做会不会破坏与其他数据之间的关系?不难看出,你的更新操作很快会让这一切变得像意大利面条一样混乱不堪。我曾经看着一些团队走上了这条不归路,他们试图从更新的字段里去推测客户的意图,结果代码变得像团乱麻。

那么该如何解决这个问题,有其他更好的方案吗?我个人更喜欢基于领域驱动设计(DDD)来设计 API。DDD 的基本思想是说,软件的建模应该发生在真实世界的问题得到解决之后。DDD 使用实体(Entity)和聚合(Aggregate)来描述业务对象,还定义了服务(Service)、值对象(Value Object)和仓库(Repository)等术语,用以解决业务领域或 DDD 边界上下文问题。DDD 不一定非要与 REST 绑定在一起,不过我发现 DDD 与 REST API 近乎天然地合拍,因为 REST 的资源可以很好地与 DDD 的实体映射起来。

那么这意味着什么呢?这意味着,你的 API 应该要以领域对象以及这些对象所提供的业务操作为中心。业务操作是对常规更新操作最好的替代品。我们继续以之前的银行账户为例。

对于银行的 API 来说,账户就是一个领域对象(DDD 里的实体)。这次我们不再使用 CRUD 来为账户建模,而是为账户定义一组业务操作。以下是一系列写入操作:

  • 开户(Open)——新开一个账户。
  • 销户(Close)——注销一个已有的账户。
  • 取出(Debit)——从账户里扣掉一些钱。
  • 存入(Credit)——往账户里存入一些钱。

这些操作都带有一定的业务约束。例如,往一个已经注销的账户里存钱是不被允许的,而在取钱的时候要强制检查最小余额。至于读取操作,我们可以为客户提供一些有用的查询。

  • 加载(Load)——通过账户 ID 加载相应的账户信息。
  • 交易历史——列出账户的交易历史。
  • 客户的账户列表——列出指定客户的所有账户。

在定义好业务操作之后,就可以将它们与 REST API 映射起来。

  1. POST /account ——新开一个账户。
  2. PUT /account/ /close ——注销一个已有的账户。
  3. PUT /account/ /debit ——从账户里扣掉一些钱。
  4. PUT /account/ /credit ——往账户里存入一些钱。
  5. GET /account/ ——通过账户 ID 加载相应的账户信息。
  6. GET /account/ /transactions ——列出账户的交易历史。
  7. GET /accounts/query/customerId/ ——列出指定客户的所有账户。

这些看起来与一般的 CRUD API 非常的不一样,关键在于这些操作具有良好的定义。不管对于服务提供方还是客户端来说,这样的体验都更好。服务提供方不再需要根据更新字段来推测业务操作的意图,业务操作清晰明了,这样的代码更简单,也更容易维护。而对于客户端来说,它们能执行或不能执行哪些操作也是一目了然的。如果 API 具有良好的文档化,比如使用了 Swagger ,那么就可以很清楚地了解到 API 都具有哪些约束。

定义这样的 API 需要做一些前期思考,这不同于使用简单的 CRUD 生成器。如果你打算将 API 暴露成公共端点,就需要在很长的一段时间内为 API 提供支持,最好还是把它看成是一个永久性的事项。我总是建议人们在前期多花一点时间,因为有些东西到了后面就很难修改,而 API 就是一个很好的例子。

所以,在进行 API(REST 或其他)设计时,请停止使用 CRUD 模型。相反,可以通过 DDD 来定义 API,包括领域对象和它们的业务操作。

如果你想看到更多关于领域对象的例子,可以参考 Amazon Web Services 的 API。在 AWS API 开发者指南里,每一个服务都有对应的“关键概念”一节,用以描述领域对象。例如,S3 里定义了 Bucket、Object 和 Permission 等领域对象,Kinesis 里定义了流(stream)和分片(shard)。先了解一个服务的领域对象,再查看 API 参考,然后浏览服务的 API 清单。你会发现,基于这些领域对象构建的 API 在理解和使用上都更加直观。

查看英文原文: There is No U in CRUD


感谢雨多田光对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-09-18 16:596440
用户头像

发布了 322 篇内容, 共 152.6 次阅读, 收获喜欢 148 次。

关注

评论

发布
暂无评论
发现更多内容

一文解读机密容器的崛起和发展

云布道师

龙蜥

教你用JavaScript实现背景图像滑动

小院里的霍大侠

JavaScript 编程入门 实战案例 图像滑动

问答知识库快速构建技术解析及行业实践

中关村科金

人工智能 大数据 AI 知识图谱 技术实践

Nginx学习笔记(一)HTTP核心配置指令

Starry

TiCDC 源码解读(2)-- TiKV CDC 模块介绍

TiDB 社区干货传送门

不止于大,如何打造优秀的折叠屏应用体验

HarmonyOS开发者

HarmonyOS

YonBuilder移动开发平台AVM框架 封装滑动单元格组件

YonBuilder低代码开发平台

开发者 后端 移动开发 avm.js AVM

What's new in Dubbo 3.1.4 and 3.2.0-beta.3

Apache Dubbo

Java 开源 微服务 dubbo RPC

户外LED显示屏技术

Dylan

LED显示屏 户外LED显示屏 led显示屏厂家

Coinbase或在不久使用Zebec发放工资,并对Web3支付赛道发展寄予厚望

西柚子

阿里云联合“产学研媒”发起BizDevOps共促计划,助力企业提升组织效能

阿里技术

研发效能 数字化转型

低代码开发平台YonBuilder移动开发,开发阅读APP教程

YonBuilder低代码开发平台

App 低代码 移动开发 阅读 开发.

深度!用“极速统一”,开启金融行业数据分析新范式

StarRocks

我和 TiDB 的故事 | TiDB 仓库 commit TOP 1,PR 数超过 1000 的阿毛哥

TiDB 社区干货传送门

人物访谈

V8的垃圾回收和内存限制

千锋IT教育

解读Java内存模型中Happens-Before的8个原则

华为云开发者联盟

Java 开发 华为云 12 月 PK 榜

TiDB Operator配置TiDB集群故障自动转移

TiDB 社区干货传送门

集群管理 管理与运维 故障排查/诊断 安装 & 部署 扩/缩容

作为程序员,12款常用的软件!

千锋IT教育

MySQL的数据类型和存储引擎介绍

C++后台开发

MySQL 数据库 中间件 后端开发 存储引擎

Coinbase或在不久使用Zebec发放工资,并对Web3支付发展寄予厚望

股市老人

ShineScrum捷行2022年最受欢迎的五篇文章回放

ShineScrum

PO 用户故事 项目经理 PRD 团队工作效率

教你用JavaScript实现随机点名器

小院里的霍大侠

JavaScript 初学者 入门实战

践行开源理念,统信软件开放服务器操作系统UOS V20使用授权

统信软件

操作系统 服务器 国产化 免费

消除视觉Transformer与卷积神经网络在小数据集上的差距

华为云开发者联盟

人工智能 华为云 12 月 PK 榜

Coinbase或在不久使用Zebec发放工资,并对Web3支付赛道发展寄予厚望

鳄鱼视界

数据报告 | 非洲电商市场分析报告

前嗅大数据

数据报告 电商报告

Java开发如何通过IoT边缘ModuleSDK进行进程应用的开发

华为云开发者联盟

开发 数据处理 华为云 12 月 PK 榜

网易云音乐用户画像资产治理及业务赋能

网易数帆

数据仓库 数据治理 数据资产 12 月 PK 榜

DM 实践手册

TiDB 社区干货传送门

迁移 管理与运维

Go 中这么多创建 error 的方式,你真的了解它们各自的应用场景吗

Go学堂

golang 开源 程序员 个人成长 12月月更

为什么说要用DDD替代CRUD来设计API_语言 & 开发_James Hood_InfoQ精选文章