31

I'm creating a Rest API using Spring Boot and I am using Hibernate Validation to validate request inputs.

But I also need other kinds of validation, for example when update data needs to checked, if the company id doesn't exist I want to throw a custom exception.

Should this validation be located at the service layer or the controller layer?

Service Layer :

 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);
}

Controller Layer :

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);
}
Desolate Planet
  • 6,038
  • 3
  • 29
  • 38
fdarmanto
  • 411
  • 1
  • 4
  • 3

6 Answers6

17

Both controller layer and service layer expose certain interfaces. Interfaces define contracts on how the interface should be used. Contract usually means which arguments (and its types and values) are expected, which exceptions can be thrown, which side effects are created etc.

Now, your validation is essentially enforcement of the contract of controller update() method and service layer update() method. Both of them have very similar contract so it would be natural if the validation (enforcement of the contract) would be common too.

One possible way to do that is to separate the validation of this contract and have it called in both layers. This is usually most clear - each class/method enforces their own contract, but is often unpractical because of performance (accessing database) or other reasons.

Other possibility is to delegate this validation to the service layer while explicitly defining the behavior in case of failed validation in the service layer contract. Service layer will typically return some generic validation error (or throw exception) and controller layer will want to react in some specific way to the error - in this case we will return 400 Bad request to signal, that incoming request was invalid.

In this design, there's a danger of too much coupling between business logic in service layer (which should be quite generic) and controller (which handles integration logic).

Anyway, this is quite controversial question and 100 people will answer with 100 answers. This is just my take on it.

qbd
  • 2,876
  • 1
  • 12
  • 16
6

Hibernate validations are checks over the integrity of the data. In order to avoid RuntimeExceptions from DB. They are pretty much the same validations you should control with DB constraints, because only the business layer should be feeding the persistence layer.

I don't put validations in DAOs because it's not its business. I expect valid data from upper layers. In case of an error, I delegate to the DB the responsibility to check its constraints.

Say my DAO receives a Date for it to be stored somewhere. The DAO won't validate if this Date is valid (because it involves business knowledge). Its job is to store the Date, no to decide if it's ok or not.

Then comes validations at the business layer. All business validations focused on keeping the consistency of the data. This way DAO's remains simpler, overall when there're validations involving different entities or boundaries.

You will see which validations are intended to be implemented at the business layer. The most common: id control. Ideally, the business layer there's only one, but controllers there could be many and of the all sort. Instead of duplicating validations in each controller, do it in the business layer. Make it reusable.

Back to the Dateexample, this is the place where to check if this `Date' is valid, if it complies with the business rules.

Finally, I do validations on the control layer, but only those involving input formats, required inputs, etc. In other words, I validate the "contract".

For example, I don't want the business layer validating date formats. The business layer has nothing to do with it. It needs a Date and someone has to provide it with the proper representation. It doesn't care how outer layers managed to transform an input into a Date.

The same way the persistence layer trust in the business layer, the business can trust in the controller. To a point. As I commented, there could be many and different controllers each of which with its own particularities. Say one of these is buggy and it's informing Date but in the wrong zone.

Laiv
  • 14,283
  • 1
  • 31
  • 69
4

There is no 100% right or wrong answer of this question, in my opinion. But the convension I follow is,

  • Putting all sorts of validation logic (semantic or business) in service layer in order to make the service layer independant of any client. To achieve that, we can use some metadata driven semantic validations around method parameter (being from Java world, I use @Valid etc.). The primary reason is that, there might be different clients of a particular service - may be a controller layer or even another service or any other type of client (e.g. RMI or a batch job). Hence keeping the validation logic in servie itself, will help in all cases.

  • In addition to that, we can optionaly add constraint validations in controller layer also, just to restrict service layer invocation all together in case of invalid input. But, the controllers main responsibility should be translating the exceptions (thrown by service validation) into proper message and response code.

3

In our Java shop, we have intentionally split web widget validation onto three separate operations.

  1. Basic formatting - numbers must be numbers; dates must be valid dates etc. Usually this validation comes for free - the web framework will do it for you when binding widget contents to the model.
  2. Single widget validation - date must be in the past; an integer must be between 1 and 100; customerId must exist in the database etc. This belongs in the controller layer in most cases, but may need support from the data repository.
  3. Cross-widget validation - checkout date must be after check-in date; date of death cannot be before date of birth etc. This is definitely business rule validation. We tend to put this in the controller layer as well, but you may want to shift it into a business validator so it can be reused.

If layer 1 fails, we don't check 2 or 3. Similarly if 1 succeeds and 2 fails we don't do 3. This stops spurious error messages being generated.

You are asking about values in a REST call rather than widget contents, but the same principles apply.

kiwiron
  • 2,278
  • 10
  • 13
1

Input should be checked in service layer.

And "Can't find id" is logical error condition. So should be thrown from controller layer.

This again depends on your layering / design.
What a service layer is supposed to do and what is expected from controller layer.

Paperless
  • 11
  • 1
  • An answer shouldn't be seeking additional clarification from the question. If the question needs clarification it should commented on and possibly flagged for closure if it is too unclear. Yes, I do realize you don't have the reputation for either of those actions. –  Feb 02 '16 at 14:04
  • "Input checking" is ambiguous. For example, I might put a Required attribute on a field to indicate that it must be filled in, but also I might put a complex custom attribute that checks, for example, that one field value is greater than another. IMHO, the Compare validation "smells" much more of business service layer than controller layer. – JustAMartin Jul 24 '19 at 13:56
-1

Test driven aproach shade a light on this,, after all there is no controller and you must choose another option. Obviously bussines rules should be in one place, and this is another constraint in your decisssion.

Hans Poo
  • 101
  • 1