
文章插图
【为什么说要用DDD替代CRUD来设计API】来自亚马逊的高级工程师 James Hood 以简单明了的例子说明了为什么要用 DDD 替代 CRUD 来设计 REST API 。他提到“DDD 与 REST API 近乎天然地合拍,因为 REST 的资源可以很好地与 DDD 的实体映射起来” 。REST 以资源为中心,这些资源以 URI 的形式呈现 。在调用 HTTP 时,通过指定一个 HTTP 动词和一个资源 URI 对某个特定的资源进行操作 。大部分 REST 框架都提供了生成器,你只要指定一个资源的名字,框架就会为你生成脚手架(scaffold) 。
不过,这些生成器默认使用的是 CRUD 模型(Create、Read、Update、Delete),它们把资源看成是一系列属性的集合,使用 JSON 或与特定语言相关的数据对象来表示资源,并生成用于对资源进行创建、读取、更新和删除操作的方法 。
虽然这给开发者带来了便利,但我觉得这样是有问题的 。我不喜欢 CRUD 这样的说法,尤其不喜欢当中的 U 。
问题:CRUD 中的 U一般的更新操作允许客户端更新资源的任何一个字段,并使用新版本覆盖已有的版本 。但如果你允许客户端这么做,那么你的服务 API 就失去了应有的价值 。
服务层的一个关键价值在于为底层的数据增加业务约束,因此,资源最终都需要带上业务约束 。
那么,难道我们就不能给更新操作增加业务约束吗?让我们以最简单的银行账户为例 。首先,不能让客户通过调用 API 来随意更新他们的账户余额 。另外,账户或许需要最小余额的限制 。
你在更新操作里做了一些检查,账户余额的变动必须发生在一个指定的范围内 。那么这样问题就解决了吗?当然没有 。任何一次余额的调整都需要与某种事务相对应,不是吗?是存入、取出,还是转账?如果客户要更改账户该怎么办?这样做是被允许的吗?这样做会不会破坏与其他数据之间的关系?
不难看出,你的更新操作很快会让这一切变得像意大利面条一样混乱不堪 。我曾经看着一些团队走上了这条不归路,他们试图从更新的字段里去推测客户的意图,结果代码变得像团乱麻 。
解决方法:DDD那么该如何解决这个问题,有其他更好的方案吗?我个人更喜欢基于领域驱动设计(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)——往账户里存入一些钱 。
- 加载——通过账户 ID 加载相应的账户信息 。
- 交易历史——列出账户的交易历史 。
- 客户的账户列表——列出指定客户的所有账户 。
- POST /account ——新开一个账户 。
- PUT /account//close ——注销一个已有的账户 。
- PUT /account//debit ——从账户里扣掉一些钱 。
- PUT /account//credit ——往账户里存入一些钱 。
- GET /account/——通过账户 ID 加载相应的账户信息 。
- GET /account//transactions ——列出账户的交易历史 。
- GET /accounts/query/customerId/——列出指定客户的所有账户 。
推荐阅读
- 金庸小说什么武功最高 降龙十八掌在金庸里面排第几
- macOS 10.15功能升级,和32位应用说再见
- 聪明的女人结婚不领证 女方为什么要先办婚礼再领证
- 去环球影城的搞笑说说 去北京环球影城怎么发朋友圈
- 中国文学史上第一部长篇历史小说 中国第一部文学史著作
- 蚕茸柱天胶囊(黄色包装)的说明书
- 生力雄丸的说明书
- 疏肝益阳胶囊的说明书
- 东阿阿胶 阿胶补血膏的说明书
- 健脾益肾颗粒(无蔗糖型)的说明书
