验证应位于哪一层?


18

我正在使用Spring Boot创建一个Rest API,并且正在使用Hibernate Validation来验证请求输入。

但是我还需要其他类型的验证,例如,当需要检查更新数据时,如果公司ID不存在,我想抛出一个自定义异常。

该验证应该位于服务层还是控制器层?

服务层:

 public Company update(Company entity) {
    if (entity.getId() == null || repository.findOne(entity.getId()) == null) {
        throw new ResourceNotFoundException("can not update un existence data with id : " 
            + entity.getId());
    }
    return repository.saveAndFlush(entity);
}

控制器层:

public HttpEntity<CompanyResource> update(@Valid @RequestBody Company companyRequest) {
    Company company = companyService.getById(companyRequest.getId());
    Precondition.checkDataFound(company, 
        "Can't not find data with id : " + companyRequest.getId());

    // TODO : extract ignore properties to constant

    BeanUtils.copyProperties(companyRequest, company, "createdBy", "createdDate",
            "updatedBy", "updatedDate", "version", "markForDelete");
    Company updatedCompany = companyService.update(company);
    CompanyResource companyResource = companyAssembler.toResource(updatedCompany);
    return new ResponseEntity<CompanyResource>(companyResource, HttpStatus.OK);
}

Answers:


8

控制器层和服务层都公开某些接口。接口定义了有关应如何使用接口的协定。合同通常意味着需要哪些参数(及其类型和值),可以抛出哪些异常,产生哪些副作用等。

现在,您的验证实质上是执行控制器update()方法和服务层update()方法的合同。他们两个都有非常相似的合同,因此如果验证(合同的执行)也很普遍,那将是很自然的。

一种可行的方法是分离该合同的验证,并在两层中进行调用。这通常是最清楚的-每个类/方法都强制执行自己的合同,但由于性能(访问数据库)或其他原因,通常不切实际。

另一种可能性是将这种验证委派给服务层,同时在服务层合同中验证失败的情况下显式定义行为。服务层通常会返回一些通用的验证错误(或引发异常),而控制器层将希望以某种特定的方式对错误做出反应-在这种情况下,我们将返回400 Bad请求以信号通知该传入请求无效。

在这种设计中,存在服务层中的业务逻辑(应该非常通用)与控制器(处理集成逻辑)之间过多耦合的危险。

无论如何,这是一个很有争议的问题,有100个人将回答100个答案。这只是我的看法。


1

输入应在服务层中检查。

而“找不到ID”是逻辑错误条件。所以应该从控制器层抛出。

这又取决于您的分层/设计。
服务层应该做什么以及控制器层应该做什么。


答案不应该是对该问题的进一步澄清。如果问题需要澄清,则应该对其进行评论,如果不清楚的话,可以标记为结束。是的,我的确意识到您没有任何一种声誉。

“输入检查”是不明确的。例如,我可能在字段上放置了Required属性以指示必须填写它,但是我可能会放置一个复杂的自定义属性,以检查例如一个字段的值大于另一个字段的值。恕我直言,Compare验证比控制器层更多地“嗅”业务服务层。
JustAMartin

1

休眠验证是对数据完整性的检查。为了避免来自bbdd的RuntimeExceptions。它们几乎与应该使用Constrains控制的验证相同。因为只有业务层才应提供持久层,所以您可能(或不取决于您)信任来自业务层的数据的正确性

我没有在DAO中进行验证。我期望来自上层的有效数据。如果发生错误,我将bbdd的责任委托给bbdd。

然后是业务层的验证。所有业务验证都着眼于保持数据的一致性而不是完整性

最后,我在控制层上进行了先前的验证。那些只与这样的层有关。

您将很快看到打算在业务层进行的验证。最常见:id控件。这一层可以很容易地在两层上实现。如果您希望有许多控制器或客户端在使用您的业务层,那么与其在每个地方重复进行相同的验证,不如将其放在业务层上。

有时,控制器具有自己的规则和条件,不会在任何其他外观中复制。然后将其放入这样的控制器中。

考虑一下您要验证的内容,以及无论如何都希望将其应用于所有人。或者,如果它是上下文验证(“验证仅在特定控件/视图外观上发生的事情”)。


0

在Java商店中,我们有意将Web小部件验证分为三个独立的操作。

  1. 基本格式-数字必须是数字;date必须是有效日期等。通常,此验证是免费的-将小部件内容绑定到模型时,Web框架会为您完成验证。
  2. 单窗口小部件验证-日期必须是过去的日期;整数必须在1到100之间;customerId必须存在于数据库等中。在大多数情况下,这属于控制器层,但可能需要数据存储库的支持。
  3. 跨小工具验证-退房日期必须晚于入住日期;死亡日期不能早于出生日期等。这绝对是业务规则验证。我们也倾向于将其放在控制器层中,但是您可能希望将其转移到业务验证器中,以便可以重用。

如果第1层失败,我们将不检查2或3。类似地,如果第1层成功而2失败,则我们不进行3。这将停止生成虚假错误消息。

您是在询问REST调用中的值,而不是窗口小部件内容,但是适用相同的原理。


-1

毕竟,没有控制器,您必须选择其他选项,这是测试驱动的方法的一个亮点。显然,商务规则应该放在一个地方,这是您决定中的另一个约束。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.