Cover image downloaded from https://images3.alphacoders.com/674/674734.jpg
One time in 2022, while working on the mobile app for a project, I had an issue with the format the backend was returning when an error occurred.
The content type was different among errors
When the content type for the error was JSON, the schema varied wildly with no specific set of schemas.
This meant I couldn't parse the error response to let a user know exactly what went wrong, and that meant a bad user experience. Imagine the frustration of using an app where you're making an error but aren't told exactly what you're doing wrong. You have to painstakingly try different things to figure it out. Other developers using the same backend simply decided to dump whatever error response they got on the screen without parsing. However, I felt this could easily be resolved by the backend and lead to a huge improvement in user experience. I mean, just catch your errors and reformat them, right? Right?? So I communicated the issue to the backend developer.
The backend developer and I had discussions and he eventually mentioned he was using Django (which has a wonderful beginner tutorial) and Django REST framework. I'm always curious about all aspects of a project I'm working on so I asked if I could have a look at how he was handling errors. He agreed and this is what I found:
The Django REST framework had a default error-handling implementation that had a different format for exceptions depending on which aspect of the framework raised the exception. These different exceptions were being passed through to the backend clients without processing and that explains the different content types and different JSON schema issues mentioned above. Besides the parsing challenge, the two issues below were apparent:
No separation of concerns: Receiving error feedback from the framework is a different concern from communicating a response to the client. An exception raised by the framework is only letting the program know an error has occurred. As to how the program communicates that to the client, that should be based on a standard set between clients and the backend, optimised for the client. However, the framework reporting an error has become the same thing as communicating a response to a client, leaving no room to be flexible about the structure of responses to a client.
Coupling of clients with Django and Django REST framework exceptions or any framework used by the backend: Imagine a situation of an owner walking a dog on a leash. That's the situation the lack of separation of concerns has created; whichever direction Django and Django REST framework exceptions go, clients must follow. Client code has to change in the following situations though their requirements haven't changed:
A new Django or Django REST framework update that changes the default exception handling
Rewriting the backend with another framework (e.g Laravel)
You get the cost of maintenance on the client side and potential fatal crashes in production environments.
In addition to the two issues above, the implementation from the backend developer also introduced a security risk. Allowing exceptions from the framework to pass through to clients can expose vulnerabilities of the backend and sensitive data to attackers.
To solve this problem, Django REST framework provides an option to use a custom error handler for errors thrown by the framework. The following can be done
Exceptions raised by custom code should be caught in their code blocks and formatted to a standard relevant to clients
A custom error handler should be used to reformat each exception type raised by the framework to a standard relevant to clients
In my experience so far, developers have repeated the pattern of depending solely on a framework's offerings. We do this naively, not recognising the problems they create for the product we're building. What I have learned is to let the problems I'm solving drive how I use the tools, libraries, or frameworks rather than allowing how the tools work to determine how I solve the problems.