红帽白皮书新鲜出炉!点击获取,让你的云战略更胜一筹! 了解详情
写点什么

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

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

关注

评论

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

利用极狐GitLab DevSecOps 功能检测 log4j 的多种方式

极狐GitLab

蓝格赛(中国)用TDengine落地聚合查询场景,效果如何?

TDengine

数据库 tdengine 后端

喜提双奖 | 旺链科技彰显综合硬实力!

旺链科技

区块链 产业区块链 供应链

孩子,你为什么要上学?

Tiger

28天写作

一文带你梳理Clang编译步骤及命令

华为云开发者联盟

编译 LLVM Clang编译 Clang 编译命令

重装上阵——Graviton2提升Aurora性价比

亚马逊云科技 (Amazon Web Services)

Data

java开发之SSM开发框架

@零度

Java ssm

「山东城商行联盟」数据库准实时数据采集系统上线,DataPipeline助力城市商业银行加快数字化转型

DataPipeline数见科技

数据库 中间件 数据同步 数据融合 数据管理

如何将Amazon RDS与Amazon Aurora数据库迁移至Graviton2?

亚马逊云科技 (Amazon Web Services)

Data

发布你的开源软件到 Ubuntu PPA

hedzr

#Ubuntu Debian packaging ppa

​使用 Amazon Neptune 通过数据仓库构建知识图谱,借此补充商务智能体系

亚马逊云科技 (Amazon Web Services)

Data

内核干货不容错过,龙蜥内核的Load Averages剖析直播回顾上线了

OpenAnolis小助手

Linux Kenel 内核 龙蜥社区

(转)前端开发之MySQL分区表中的性能BUG

@零度

MySQL 前端

前沿干货!深度揭秘TDSQL新敏态引擎Online DDL技术原理

腾讯云数据库

tdsql 国产数据库

解析Redis操作五大数据类型常用命令

华为云开发者联盟

数据库 redis string 数据类型 getset

鲲鹏HCIA认证之初识鲲鹏

桥哥技术之路

鲲鹏

一文详解TDSQL PG版Oracle兼容性实践

腾讯云数据库

tdsql 国产数据库

一个简单的单体服务流量标记demo

zuozewei

Java 性能测试 全链路压测 12月日更

轻松驾驭EB级千万QPS集群,TDSQL新敏态引擎元数据管控与集群调度的演进之路

腾讯云数据库

tdsql 国产数据库

Go编译原理系列2(词法分析&语法分析基础)

书旅

Go 后端 编译原理

MySQL 中 blob 和 text 数据类型详解

Simon

MySQL

盘点2021 | 技术十年-记录十年技术经历

高性能架构探索

技术人 工作经历 经历分享 盘点2021

JAVA 开发常用工具汇总

编程江湖

java编程

JDK ThreadPoolExecutor核心原理与实践

vivo互联网技术

jdk ThreadPoolExecutor Java 开发

webpack打包过程如何调试?

Jerry Wang

前端 前端开发 webpack 28天写作 12月日更

又拿奖了!腾讯云原生数据库TDSQL-C斩获2021PostgreSQL中国最佳数据库产品奖

腾讯云数据库

tdsql 国产数据库

DM 分库分表 DDL “悲观协调” 模式介绍丨TiDB 工具分享

PingCAP

盘点 2021|不忘初心,扬风起航

小鲍侃java

盘点2021

XEngine:深度学习模型推理优化

华为云开发者联盟

深度学习 模型推理 显存优化 计算优化 XEngine

云图说|初识数据库和应用迁移UGO

华为云开发者联盟

数据库 华为云 UGO 异构迁移

跟着动画学Go数据结构之堆排序

宇宙之一粟

golang 数据结构 排序算法 Go 语言 12月日更

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