Migrating ActiveModel Serializer to JSerializer
Our API backend service relies heavily on ActiveModel::Serializer for building response objects. We are using version 0.8
which hasn’t been updated for many years. As part of the effort of upgrading our service to Rails 5 (and eventually Rails 6), we start to seek ways to upgrade active_model_serializers
.
ActiveModel::Serializers From 0.8 to 0.10
The first candidate is the latest 0.10
of active_model_serializers
. On one hand, our benchmark shows some really good performance improvement. In one test case, where we have 1000 records, and each record has about 5000 configuration rules, the performance result looks like this:
version | Total Response Time (ms) | ActiveRecord (ms) |
---|---|---|
0.8.2 | 12357 | 60.5 |
0.10.2 | 5212 | 59.8 |
As shown above, with the same SQL query (very close ActiveRecord time), the total response time is about 2 times faster when using 0.10.2
.
However, on the other hand, the latest active_model_serializers
introduced a lot of breaking changes, which make it hard to migrate to. The most significant changes that break our serializer classes are:
- The
root
class method is removed. It needs to use different json adapter for root, no-root response. embed
method is not supported.- The
include_xxx?
filter is not supported. - The behaviors of some private methods like
_attributes
are changed. - Cannot use private method in serializer
There are workarounds for some of the changes, but there are quite efforts to fix broken code. Furthermore, after reading the What’s happening to AMS docs, we are worried about the future of this project. As a result, we decide to move on to search for other solutions.
ActiveModel::Serializers Alternatives
When we start to look for other alternatives, we come out the following expectations that we hope the candidate could meet:
- Stable and well maintained. Support Rails 5.2
- Able to support most of our customizations without too much magic
- Minimum migration effort
- Perform reasonably well comparing to
active_model_serializers
- Easy to write unit tests that could integrate with things like authorization layer
- Easy to debug and track errors especially when embedding with nested objects
We then spend a lot of time in testing a bunch of JSON serializer gems in Ruby:
Long stories short, we test and list out the pros and cons of each gem, and eventually, we couldn’t find any candidate that could satisfy most of our needs.
Introducing JSerializer
I then start to think about writing our own solution, and as it turns out, it didn’t take me very long to come out with a library that supports most of what we need from active_model_serializers
. The gem is called jserializer where the j
stands for json
, and it is aimed to be a drop-in replacement of Active Model Serializer version 0.8.
Throughout the implementation, I made several design decisions and trade-offs to make the code looks simple and performs well. In the following sections, I’d like to discuss some of the designs that are worth to mention.
Reuse the serializer object for collections
jserializer
tries to reuse serializer instance when serializing a collection of records. This saves memory and improves the speed when generating complex response that includes a list of records which share the exactly same structure.
The drawback of this design is that if the serializer instance keeps some state, for example, in an instance variable, the state might get passed to the other records in the same collection, which corrupts the result.
To solve that, a reset
method is provided, which can be implemented in your subclass, and then it will be called before passing the new record to the same serializer object.
Inline attribute methods to improve the lookup speed
The code can be found here. By defining the attribute access method and the include_xxx?
method inside the current class, the code doesn’t need to look into the method chain of the class inheritance system to find the value of certain attribute.
Code explicitly
This gem does not touch any of the Rails’ internal methods. The obvious benefit is that it could be compatible with any future version of Rails. And the drawback is that you cannot blindly call the render
method like:
render json: @posts
and then expect it magically figures out which serializer class to be used. This implicit approach brings a lot of issues when dealing with large project. It may use a wrong serializer class which end up exposing data that you don’t want to show to certain user.
However, this doesn’t mean that you always have to do this:
render json: MyPostSerializer.new(@post)
You can still bring that magic back in the application controller layer as described here
Zero dependency
It is also made so that there is zero dependency on 3rd party libraries, comparing to many dependencies used by active_model_serializers
.
The advantage is that there are less things to rely on, and to keep track of, which makes the code much easier to maintain.
Conclusion
We’ve successfully migrated most of our serialization classes from active_model_serializers
to jserializer
, and the new code has been running in production for a while without any issue. We are pretty happy with it.
One last thing to bring up is that we haven’t completed the migration yet due to the large codebase. However, since jserializer
can live with active_model_serializers
gem together, we can gradually migrate serializers without breaking existing code that uses active_model_serializers
.
Practically, we use the bottom up approach for the migration. That is migrate those child serializers first, and then their parent serializer class that has many children class embedded into.
I hope this article could be helpful to you and your team! Please don’t hesitate to leave any question or comment.