写点什么

为什么说要用 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:596196
用户头像

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

关注

评论

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

2021年是意义非凡的一年,2021阿里+头条+腾讯等大厂Java笔试题分享

Java 面试 后端

2021年最新Java面试点梳理,阿里P7大牛整理

Java 面试 后端

40万奖池 + 顶级云服务资源,云计算大赛系列公开课正式开播

亚马逊云科技 (Amazon Web Services)

人工智能 云计算 创新大赛

2021最新京东商城亿级并发架构设计!推荐每一位Java开发者学习

Java 编程 架构 后端 计算机

QOE 驱动下的分布式实时网络构建:Agora SD-RTN 的演进

声网

Java 人工智能 分布式 网络

Flink 实时 metrics

GrowingIO技术专栏

大数据 flink Grafana 流式计算框架

docker 命令备忘(qbit)

qbit

Docker 容器 存储

2021年最新Java大厂面试笔试题分享,Java入门教程免费视频

Java 面试 后端

2021年腾讯Java高级面试题及答案,百度笔试题百度校招面试经验

Java 面试 后端

2021年这些高频面试知识点最后再发一次,面试官突击一问

Java 面试 后端

说下你可能没用过的EventBus

艾小仙

2021年阿里Java面试题及答案,多图详解CLH锁的原理与实现

Java 面试 后端

2021我的Java路要怎么走,Java应用性能优化

Java 面试 后端

2022高频前端面试题汇总之React篇

buchila11

React

区块链技术的核心、发展与未来

CECBC

2021年阿里Java面试题及答案,Java高级进阶学习资料

Java 面试 后端

2021我是如何拿到小米、京东、字节的offer,持久化数据安全RDB、AOF

Java 面试 后端

2021年春招Java面试题,大厂Java核心面试题出炉

Java 面试 后端

2021年阿里Java高级面试题分享,【MySQL

Java 面试 后端

阿里大佬竟然真的把Java基础核心知识整理成了PDF版

Java 程序员 架构 面试 计算机

Centos7 部署 Zabbix 5.4 高可用集群

Se7en

网络安全必学知识点之XSS漏洞

网络安全学海

黑客 网络安全 信息安全 WEB安全 漏洞挖掘

2021年抓住金三银四涨薪好时机,腾讯Java社招面试流程

Java 面试 后端

2021年最新腾讯Java面经,Java面试高级题目

Java 面试 后端

GraphQL 快速入门「3」GraphQL 架构

码语者

Rest GraphSL

百度联手清华大学出版社 打造国内首套产教融合人工智能系列教材

百度大脑

人工智能

如何看待游戏世界的未来:解析去中心化区块链游戏的优缺点

CECBC

2021年最新Java面试经历,别再说自己不会了

Java 面试 后端

“工业互联 在云之洲”丨“5G+AR”远程协助作业解决方案 赋能装备更智能

云计算,

2021年网易Java岗面试必问,Java开发面试准备

Java 面试 后端

解析实现区块链互操作性的方法及现有开发项目

CECBC

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