RESTAPI输入模型过于通用

RESTAPI输入模型过于通用,rest,api-design,Rest,Api Design,我有一个典型的SpringBootRESTAPI来对用户对象执行crud操作 Class User{ long id String displayName String email String passwordHash String passwordSalt Instant passwordExpiryDate boolean locked Instant version (sql timestamp for optimistic

我有一个典型的SpringBootRESTAPI来对用户对象执行crud操作

Class User{
    long id
    String displayName
    String email
    String passwordHash
    String passwordSalt
    Instant passwordExpiryDate
    boolean locked
    Instant version (sql timestamp for optimistic locking)
}
  • 发布用户/
    :创建用户
  • 获取用户/{id}
    :获取用户详细信息
  • PUT users/{id}
    :更新用户名和电子邮件
  • PUT users/{id}/password
    :更新用户密码
  • PUT users/{id}/unlock
    :将锁定状态设置为false
  • 删除用户/{id}
困扰我的是,我的完整模型用户类目前被用作输入或输出,尽管只有一些属性是重要的。一些例子:

  • POST users/
    :您不需要提供id、密码、salt/hash或版本,它们是生成的
  • GET users/{id}
    :无需返回id,请求者已经知道了
  • PUT users/{id}
    :只需要新的电子邮件、名称和版本
  • PUT users/{id}/password
    :只需要新密码和版本
  • PUT users/{id}/unlock
    :只需要版本
为了解决这个问题,我开始创建几个“请求”类:

  • 新用户详细信息
  • 无ID用户
  • UserWithOnlyPassword
  • UserWithOnlyPersonalDetails
  • 用户版本
这确保了我的输入和输出仅包含所需的内容,并且无需阅读任何文档即可知道哪些属性是必需的/使用的。缺点是这很愚蠢,因为它们都代表同一个实体,只是它的不同方面。此外,任何验证(我使用javaxcontaints注释)都会声明多次

我正在考虑删除所有requestbody,而使用requestparams(查询参数)只提供必要的细节,但这将导致我在没有requestbody的情况下执行put和post操作


对于api的客户端和内部结构来说,似乎没有一个解决方案是干净的。这种API设计的最佳实践是什么?

我认为为每个操作创建不同的类是错误的,因为业务逻辑围绕同一实体运行。对所有操作使用相同的对象本身(域对象模型)。您可以基于这些类创建不同的验证。

我强烈建议您为Web API创建模型,而不是公开域(或持久性)模型,这样您就可以对API生成和使用的数据进行细粒度控制

域(或持久性)模型是复杂的,通常包含您不想(或不能)公开的关系和属性。随着时间的推移,字段可能会被添加到您的域模型、删除、重命名、修改等等。 如果您公开这样的模型,您可能会发现在不破坏客户机的情况下,很难协调多个版本的API

通过引入映射框架,您当然可以减少将API模型映射到域模型的样板代码

验证应该在服务层的域模型上执行。但是,没有什么可以阻止您在控制器层对Web API模型执行某种级别的验证



相关:本文讨论了一个类似的主题。

验证在类内部,使用javax,因此不起作用。例如,对于updatepassword,密码是必需的,但是对于unlock,密码实际上应该为null。这就是为什么我不想只使用一个类。大多数企业应用程序都使用域对象模型,因为它们不想在最后把不同的类搞得一团糟。在您的情况下,只需不传递可选字段并相应地进行验证。