In my experience, its best to throw exceptions at the point where the errors occur. You do this because it's the point where you know the most about why the exception was triggered.
As the exception unwinds back up the layers, catching and rethrowing is a good way to add additional context to the exception. This can mean throwing a different type of exception, but include the original exception when you do this.
Eventually the exception will reach a layer where you are able to make decisions on code flow (e.g a prompt the user for action). This is the point where you should finally handle the exception and continue normal execution.
With practice and experience with your code base it becomes quite easy to judge when to add additional context to errors, and where it's most sensible to actually, finally handle the errors.
Catch → Rethrow
Do this where you can usefully add more information that would save a developer having to work through all the layers to understand the problem.
Catch → Handle
Do this where you can make final decisions on what is an appropriate, but different execution flow through the software.
Catch → Error Return
Whilst there are situations where this is appropriate, catching exceptions and returning an error value to the caller should be considered for refactoring into a Catch → Rethrow implementation.
Working with Spring worthwhile to stick to its features and capabilities. Unless they were incompatible with your requirements, in which case the usage of Spring would be questionable.
1. Error Handling
You already have mentioned the approach proposed by Spring. @ControllerAdvice. You can implement a dedicated component for error handling or use the annotations directly in the controllers.
This will reduce significantly the duplicity of try/catch blocks in all the Controllers.
2. Throwing errors
It's a common question: Do I delegate the data integrity control only to the DB?
I'm usually against. To me, the DB constraints are the last line of defence, not the only. Usually, the constraints reflect business rules so I'm in favour of making these rules explicit and readable along the code.
Don't get me wrong, I'm not suggesting to remove DB constraints. They must be. If there were more systems accessing to the DB, they will prevent you from unexpected data integrity violations.
Whether you use Spring's Data Integrity Violation Exceptions, business exceptions or domain model exceptions, if you have implemented #1, the error handling makes these differences irrelevant.
The place where the exceptions should be thrown depends on you. Due to these sort of exceptions are related to the business, I would not throw them from the Controllers. They should be as agnostic to the business as possible.
I would suggest doing the validations and the error throw from the domain layer. The layer closer to the DAL. One important thing is to be consistent with the implementation. Wherever you decide to do the validation, stick to such strategy.
3. The validation
Instead of retrieving all the collection
classRepo.findListByOrganizationName(course.organization?.name)
.filter { it.name == course.name
it.section == course.section
it.shift == course.shift }
.isEmpty() .not()
Why don't you simply do a count of the rows?
classRepo.countByOrganizationName(...)
or
classRepo.existByOrganizationName(...)
4. The question
how I suppose to propagate the massage to the other layer or to the
client side ?
Depends. If upper layers are just going to catch it for throwing its own exception, then just let it go up to the @ControllerAdvice. Feeding the exception stacktrace with more traces won't make your code better.
But, if the upper layers can deal with it and there's a Plan B for the failed execution, then catch it.
You will find that Spring DAO's exceptions are RuntimeException for a good reason ;-).
Best Answer
Our Spring project is designed to have a top level handler (with
@ControllerAdvice
and@ExceptionHandler
) that makes sure that all kinds of exceptions are handled in some sane way. This allows us to have specific exceptions that map to HTTP codes, such asUnrecoverableException
which will bubble up and return a500 Internal Server Error
.Low level exception handlers can then decide whether an exception from a 3rd party or other code means that the server is most likely incapable of servicing requests, or whether it might be a temporary issue (at which point a
RecoverableException
->503 Temporarily Unavailable
is thrown).So the general exception handling becomes straight-forward, if we can't do anything about an exception, we map it to a suitable "supertype" and let it go upwards. The supertype exceptions can then contain a lot of additional information for things like logging purposes, method parameters that caused the exception, the current state of things etc.
It works well in our architecture, and keeps exception handling localized. Either you handle the exception, or you send it to the top. But you avoid sending it to the direct parent in the classic "well I can't handle this, but I hope my caller can" way (well not strictly, but it's not so much of a guess where a specific exception might get handled).
Spring also provides a way to map an exception directly to a HTTP status, with
@ResponseStatus
in the exception class, but what if we're not using HTTP? With our solution you can create a gateway specific@ExceptionHandler
because the exception only describes the state of the system and intent of the error, not what the end result should be.This is of course Spring specific, but it may be achievable in other languages / frameworks.