T
Without claiming the truth in the last instance, I will simply feed my thoughts on this subject on the basis of personal experience, as the issue is sufficiently general and does not depend heavily on the type of application or language.First, on the question, if we want to follow the principles of a clean architecture, we should not know the details of access to the database and other online services. It means our service or repository interface must be defined in Domain's folder. Since the exceptions that are also abandoned are an unmarked part of the signature of this interface, they must be defined in Domain, too. All exceptions to the undesirable layer, whether online or errors in validation of the library we use, should be intercepted and depreciated.I like it personally more to deal with the hexogonal architecture, because there are ports and adapters that show better points of nucleus and external systems.Now what kind of exemptions are and what we should decide. This is an example of my project:ApplicationException(Exception)InvalidOperationException(ApplicationException)NotFoundException(ApplicationException)ConflictException(ApplicationException)TimeoutException(ApplicationException)PermissionException (ApplicationException) - SOLID is not fully respected because it's from a copying layer that doesn't belong to the house, but it's so much easier for us.The key is that we can quickly understand our mistake or not. If something's gone wrong for our reason, it's usually all we can and we're going to go down with the words, excuse the sappport will fix everything. The maper's code is always the last one. If for our reason, we use a handler in which, for a particular type, we define how to mop properly in response to a particular client. Because we can have a lot of clients. For example: REST API for web tape, API for asynchronous queries or a desktop of the annex. We're gonna have to figure out for every client a mapper. If we want to comply with the web application, we're the mapim PermissionException in response to status 401, if the conflict is 409, if the wrong 400, etc. The payload shall be determined so that the tape is comfortable with it as quickly as possible to understand what type of error is and decide to display it in one way or another.Analysing the above example, a few things were observed.UnknownException is a country that there are exceptions that you don't know, if you don't know, which means exactly how to deal with it, so I'd send it to the last other, and most likely the code needs to be corrected.EmptyException didn't even find where and how it was used, but the name says it should be removed from the code once it's empty.In DefaultExceptionMapper, in the first condition, the check that the row contains 401 is a clear signal that there are two kinds of mistakes in our program that we don't know very well, and that's why the logic wrote through a search in the row.Only the conditions for our basic exemptions should be specified in the moppe, and there should be no verification of specific exemptions such as BadDateTimePeriodException. Since a specific exception will be inherited from one of the parents we identified above. And we don't have to constantly expand the mopper with new daughter's exceptions.In most cases, as you said, one global hendler and the possibility of redefining behaviour for a specific wive with a chain of responsibility. If there's a more specific hendler than a global salad through it, and if it's not shydrated, we're going to the global. It's better to do them in separate classes with a method and already through the decorator (AOP) to hang or register in every "special" witch.About the machine, but my experience didn't.PS♪ Sometimes it is easier to give predetermined error codes for which the client can decide what tests to show and what internationalization.Sori's a long one. Below is the example of registration of a hendellor for REST API# global handlers
class InvalidOperationExceptionHandler(ExceptionHandler):
@response_status(HTTPStatus.BAD_REQUEST)
def handle(self, exception: InvalidOperationException) -> str:
return exception.message
class NotFoundExceptionHandler(ExceptionHandler):
@response_status(HTTPStatus.NOT_FOUND)
def handle(self, exception: NotFoundException) -> str:
return exception.message
special case
class InvalidOperationExceptionSpecialHandler(ExceptionHandler):
@response_status(HTTPStatus.FORBIDDEN)
def handle(self, exception: InvalidOperationException) -> str:
return {'message': exception.message}
@web.controller
class Controller:
@route_post('resource/{id}/')
@throws(InvalidOperationException, handler_cls=InvalidOperationExceptionSpecialHandler)
def create(self, id: Entity.Id):
pass