为什么域模型不应在RESTAPI中用作资源?

为什么域模型不应在RESTAPI中用作资源?,api,rest,domain-driven-design,domain-model,Api,Rest,Domain Driven Design,Domain Model,我遇到了一个声明,即根据DDD设计的域模型不应被用作RESTAPI()中的资源 很明显,REST API是应用程序的契约,而域模型是实现的一部分,因此最好将这两件事分开,这样域模型的更改就不会自动意味着REST API的更改 然而,我认为在小型项目中(RESTAPI只有一个使用者——由一个团队开发的javascript前端),拥有单独模型的好处并不能证明分离模型(不同类——域模型和资源表示以及模型之间的映射代码)的成本是合理的。显然,域层不能引用REST特定的基础结构代码(以保持关注点的分离)

我遇到了一个声明,即根据DDD设计的域模型不应被用作RESTAPI()中的资源

很明显,REST API是应用程序的契约,而域模型是实现的一部分,因此最好将这两件事分开,这样域模型的更改就不会自动意味着REST API的更改

然而,我认为在小型项目中(RESTAPI只有一个使用者——由一个团队开发的javascript前端),拥有单独模型的好处并不能证明分离模型(不同类——域模型和资源表示以及模型之间的映射代码)的成本是合理的。显然,域层不能引用REST特定的基础结构代码(以保持关注点的分离)


域和REST模型应该分开吗?

使用DDD时,REST API应该始终与域模型分开。

这样做的主要原因是简化—您不希望通过API将域模型的复杂性泄露给客户端。否则,客户需要了解域的细微差别和复杂性,这很可能使API难以使用

使用DDD的主要驱动因素是一个复杂的问题域,因此这始终是一个问题。

然而,我认为在小项目的情况下(…)拥有独立模型的好处并不能证明分离模型的成本是合理的(…)


我同意有些项目中分离的域模型和RESTAPI过于工程化。然而,这些病例并不是DDD的候选对象,因为DDD并不能从中获益,从而证明其成本是合理的。

< P>我想另一件事是谁使用您的REST API。如果你正在开发一个应用程序的前端,那么你可以说一切仍然在一个有限的上下文中发生。只有一部分存在于客户机/javascript中。在这种情况下,我认为在RESTAPI中公开模型是有意义的

在这种情况下,RESTAPI可能只是与域服务通信的一种手段

为什么域模型不应在RESTAPI中用作资源

因为web与您的核心域层是完全不同的世界。实体中的方法尤其难以翻译,因为HTTP只有少数动词。如果你想通过REST公开你的应用程序,你必须把你的域进程塞进HTTP,这通常意味着做出妥协,设计不同于你的域实体的资源

当然,如果您正在执行HATEOAS,那么您应该在HTTP客户端和服务器之间交换的消息以及域应用程序协议中找到通用语言中的术语,但是web必然会扭曲您的域表示


REST的重点不是重新创建域及其流程的高保真模型,而是以符合HTTP的方式交付它们,同时在转换过程中尽可能少地丢失。但它仍然是一种翻译。

您可以根据域模型将业务逻辑连接到REST资源中。例如,每当有人将is_published=1设置为1时,您可以通过连接到事件或变量来通知管理员、进行额外验证等。有时事情可能太复杂和怪异,无法以这种方式完成,因此您可以将某些属性标记为不可修改,然后创建自定义操作来修改您公开的属性,如果这样做有意义的话。我认为,如果你设计得当,你甚至不需要这些“自定义操作”。Facebook没有使用任何图形API,我不认为。我正在考虑开发一个基于模型层的框架,我仍然认为这是一个好主意。

我认为REST API的主要好处是为第三方REST客户端(通常是服务器端而不是SPA)提供服务。如果您使用HATEOAS和其他自描述性消息解决方案(如RDF),那么REST客户机将由于REST API中的更改而变得更加困难。对于小型项目——“RESTAPI只有一个使用者——由一个团队开发的javascript前端”——我认为不值得为拥有一个合适的RESTAPI付出努力。大多数人使用它的简化版本,我称之为CRUDAPI,这可能对这些项目有好处

CRUD资源和贫血域模型的域对象之间可以有1:1的映射。如果我们谈论的是真实对象(而不是数据结构),而不仅仅是CRUD方法,那么您必须在resource.verb和object.method之间进行转换,例如:

POST /dogs/{id}/barking
 -> domain.dog.bark()
POST /dogs/{id1,id2,..}/barking
 -> dogService.multiDogBark(...)
 -> UnitOfWork{domain.dogs[ids[i]].bark()}
如果我们讨论的是涉及多个域对象和工作单元(事务)的更复杂的事情,那么您需要为应用程序服务添加另一层,否则您将把整个复杂操作(包括事务处理)移动到客户端。在这些情况下,您可以在resource.verb和applicationService.operation之间进行转换,例如:

POST /dogs/{id}/barking
 -> domain.dog.bark()
POST /dogs/{id1,id2,..}/barking
 -> dogService.multiDogBark(...)
 -> UnitOfWork{domain.dogs[ids[i]].bark()}
我认为大多数开发人员将这种CRUD服务+贫乏的域模型方法与REST服务+域模型方法相混淆,这就是为什么会提出这个问题,这就是为什么有许多“REST”框架添加了1:1域对象-CRUD资源映射,甚至可能是ORM实体-CRUD资源映射。我发现这一趋势非常具有破坏性,我认为主要原因是开发人员只从短文或问答网站上肤浅地学习某些技术,而不是阅读书籍和论文,在这些书籍和论文中他们可以获得实际主题的深入知识。我认为这是一个Y+代的问题,因为数字技术的使用,我们正在失去阅读长文本的能力。我们习惯于即时奖励,而不是长文本所给予的延迟奖励