As is maybe obvious from the title, this will be a short series about APIs, specifically those written in Python, Django and Django Rest Framework (DRF). Today it’s hard to imagine speaking about web development and not touching on the subject of APIs in general and, more specifically, RESTful APIs. There are a lot of articles talking about Web, REST and SOAP APIs, so I won’t go into technicalities and usual details. Instead I will try to cover why you should rewrite old APIs written in Django to DRF, as well as include some examples. As usual, not everything is black & white, so I will also try to show why and when you should maybe rethink using DRF.
Most applications we all use daily have APIs implemented to deliver the experience we know and are accustomed to, and the same goes for us at t-matix. From the very beginning of t-matix, APIs were implemented to enable users to make use of our key features. The whole project started years ago and some other technologies were cool and used at that time, not to mention that each developer also had their own preferences and ways of doing things. All these things led to writing APIs in Django’s built-in class-based views with a plethora of mixins.
Although everything works, there is always the issue of maintaining the application, and the bigger it gets, the more complicated it is. Data is encoded and decoded manually to JSON format, serialized data representation is written in models, etc.
Is this your first time rewriting an API and you can’t make heads or tails of it, and if it wasn’t copy/pasted you would have no idea how to recreate it?
Or you have done this many times but are still looking for a potentially useful example because you are stuck somewhere?
I will try to help you all by demonstrating what helped us the most, and that is DRF.
Django Rest Framework (DRF) is one of the best-documented frameworks out there, and for most of your problems Stack Overflow is more than enough when you are writing your APIs from scratch. Rewriting can be much more complicated because everything has to work as it did before without breaking something else, so you could say change everything, but don’t make it change anything.
We were in a situation where we had loads of different modules that needed to be rewritten to a more maintainable version. As already stated, we started with typical models, views, urls, and tried to rewrite and adapt them to DRF, but further inspection brought us to models where multiple serialization formats could be found inside their methods. This was the first situation where we saw this would be more challenging than had first been thought, and it looked like this (models.py):
From the get-go it’s obvious that this is not easily maintainable nor DRY, and it is probably not where you would look for serialized data. This was just the tip of the iceberg as you will see, but we changed our approach and started paying more attention to the code structure, finally coming up with an approach that suited our cause.
I will start by showing what the aforementioned serialized data would look like in DRF for the two serialized options (serializers.py):
As you can see, we also have a PermissionsSerializer, which is used in multiple serializers, so to stay DRY we made a serializer that could be reused. How DRF helped us here is obvious:
- Anyone who inherits the code can easily find the serialization logic, it is located in serializers.py file as expected by DRF
- There is no repeating of the same code, we use inheritance
- The developers can easily conclude what type of data is needed for serialization
If you go back and take a look at the model code snippet, you can see how the DRF way is much more readable and concise.
Now let us continue with the views. The biggest change you will be making if moving to DRF will be in your views – at least that’s how it was in our case. Lots of logic we had inside the views was removed because DRF views, view sets and mixins handled it automatically, and validations were moved to serializers either by setting field parameters or in custom validation methods.
In our case, we had two views for the Group model (views.py):
- one for listing and creating groups with post & get (for retrieving the list of groups) methods
- one for retrieving, updating and deleting groups with get (for retrieving a single instance), put & delete methods:
It is obvious that this big chunk of code should and can be simplified. Here we have two views which require two different urls, but with the refactored DRF version in which we used the ModelViewSet, we needed to define only one url. The ModelViewSet knows how to handle every HTTP method depending on the url you use.
Let’s get back to the example and see what the updated serializer now looks like. It should include validation logic that was written inside the old view (serializers.py):
In the GroupSerializer you can see how we added field specific arguments which resolve lots of validation checks from the view and the validate_parent validator. GroupSimpleSerializer inherits from GroupSerializer and adds logic for creating and updating Group objects, returning a GroupProxy object, which we will touch upon later. A great help for where to implement what is the Classy Django REST Framework web site. There you can see for each view, viewset, serializer & mixin what functions it includes, how it is implemented, and get an idea how to override it.
The new DRF view we implemented looks something like this (views.py):
As you can see, we could not run away from all the try/except statements and they are still included in our code. All of our create & update logic is where it should be – in their respective functions inside serializers. This is a good thing because the, let’s say, create method does exactly what it says, it is used to create an object and nothing else. The exceptions are propagated to higher levels and are all located in adequate methods inside views.
If you read the code snippets you could see some Proxy classes called inside serializers and views which I mentioned earlier. In short, they are a way of not breaking things and easing the link with models, to make future changes less stressful and easier to handle.
This is it for now. I will tell you more about the Proxy classes in the next post, as well as explain why & when you shouldn’t go to great lengths and switch to DRF.
December 18, 2019
Continuosly building, testing, releasing and monitoring t-matix mobile apps