11 Nov 24
Refactoring and simplifying code is essential for your application to scale well, be easy to maintain, and generally work without giving us major headaches. In today’s post we address crucial backend issues through a case I have recently faced.
By refactoring the code we managed to significantly improve the communication between the frontend and the backend, and we also significantly reduced the number of API requests. I am 100% sure that our servers greatly appreciate all these improvements.
In the project I was working on, there were four different endpoints to handle all the consumption and billing information. Each one did their own thing:
So far, so good, right? For simple requests, of course. But imagine the following situation: the user needs to know the consumption for 100 customers, their relationships with 20 different products (products, services, whatever you want to call them), and how much they have spent in a certain period of time, all this by clicking a single button, obviously.
I imagine you are thinking: “what a number of endpoints to call for a single user, the tangle of requests to resolve and the time it will take to process all of that.” Yes, it was crazy. The server was crying, the frontend was crying, and the end user I won’t even tell you. Maybe it only needed a few more seconds, but nowadays a few seconds is an eternity: we want everything right away.
Given this scenario, I wondered if there was a way to simplify everything into a single endpoint. The goal was clear: a single request that would return all the information at once, but how?
And this is where I thank my colleagues for choosing Django as our backend framework. I also have to praise the endpoints that already existed, because from them I was able to visualize the problem and the solution.
Django has a very powerful and flexible ORM (Object-Relational Mapping). Simply put, the ORM allows you to interact with the database using Python models, rather than writing manual SQL queries. This makes the code easier to read and maintain, and also offers more efficient ways to handle data.
The main challenge was how to unite all those queries into a single endpoint without compromising performance while maintaining the flexibility to apply specific filters by customer, product and date. Don’t get me wrong, the above endpoints served their purpose at first, when requests were separated, not so complex, and speed was not a priority (I’m talking about seconds, it’s not like they took weeks). To give an example, let’s look at these two requests, where we ask for the consumption lines of the selected invoices:
billing_lines =
BillingLine.objects.filter(billing__in=billing_list).select_related("billing","item)
#En esta, filtramos por un producto en específico
billing_lines =BillingLine.objects.filter(billing__in=billing_list, product=product).select_related("billing","item")
Each ‘billing_lines
‘ was present in an endpoint, but they are practically the same request with more or less filters. Seeing this, I decided to create a new endpoint that had a more simplified but generic query, and could, from it, obtain all the necessary data at once. To keep things short, by joining a few ‘select_related()
‘ here and a few ‘annotate()
‘ there, I managed to have the data at hand, and from there, all that was left was to process it and return the response in JSON format.
First, because a smaller number of requests are made. This was the biggest advantage. We went from making hundreds of separate requests to just one. Imagine the difference in performance. For the end user, the difference in speed saved a few seconds; but for the server, the change was very positive in terms of consumption and efficiency.
Second (and the most important in my opinion), maintenance. The code became much easier to maintain. It is no longer necessary to make changes in various places. If something changes, you do it in one place and that’s it. Less risk of bugs and less duplicate code. The developer of the future will appreciate it 💪.
I think the most important lesson here is that it’s not enough for code to work, it also has to be readable and maintainable. In this case, the code served its purpose, but maintenance was a bit more expensive. My goal throughout was to make the code easy to read for any developer who might touch it, because, well, I don’t want to be called with technical questions when I’m enjoying my vacation in Cancun. 🤪.
With a single endpoint, we made life easier for the frontend team and ourselves. The app can now grow without having to worry about performance issues as more customers and products are added.
In short, refactoring is always a challenge, but the benefits it brings are really worth it. So, when it comes to endpoints, remember that less is more. Good, clean and efficient code is key for any backend project that wants to last in the long run.