Serialization — A Bird’s Eye View of the Fragments

Rasmus Feldthaus
Level Up Coding
Published in
8 min readAug 13, 2021

--

Photo by Rene Böhmer on Unsplash

Choosing the right type of serialization can have huge performance implications, in a world where the majority of software systems consist of microservices that need to communicate with each other across processes, networks, and machines. Unfortunately, the decision of which serialization scheme to use is often quickly dismissed to using well-known techniques without much regard for whether or not they are the right fit for the job.

This article seeks to identify some of the issues with serialization and offer some clarity between the trade-offs that various schemes expose.

Very often we are not at liberty to choose the serialization scheme when dealing with 3rd party services. Many services will expose either a scheme using JSON, or XML, and if you have been exposed to some financial solutions you may have come across FIX. In these cases, we have to abide by the schemes we are given. However, when it comes to the solutions we choose to develop and maintain internally in our organizations we have larger degrees of freedom to chose how data should be exchanged between our programs and services. This is exactly the time where we should think a little bit broader than the common solutions, we have been exposed to while working with external APIs.

The Pitfalls of Not Considering Serialization

Let’s assume you are a backend engineer working on some software project, written in C#, in a microservice environment, where you need to expose an API to some clients somewhere. You decide to implement your backend as a web service that receives and sends JSON, as that seems to be what everyone else is doing these days. An argument you also find compelling is that you have almost total freedom of modeling your domain objects, as both Newtonsoft.JSON and the built-in System.Text.Json are very good at serializing most of the data structures and objects we throw at them. This will enable you to seamlessly add and remove new fields during your development process without getting hampered too much by breaking serialization code. Should performance become a problem, you can always switch to a more efficient scheme later on.

So what’s the problem? Well, you have made a few implicit design choices that also comes with some downsides. By not taking the time to specify a schema, all the callers of your service during the development phase will receive data that rapidly changes in format, without any notice, hampering their development process. Also, you may come into problems should you decide to roll with data types such as decimalin C#. How will this translate into JSON, as this format has no real support for the increased precision that decimal provides? Chances are they will most likely be converted to doubles with the equivalent precision loss.

There are ways to work around issues like this, e.g., by making sure the string representation of a decimalis sent across the wire, rather than relying on the default conversion. But all of a sudden you have to actively make choices like this, and serialization is no longer as trivial as it used to be. When addressing the performance issue down the line, this is only easy if it never occurs or your solution has no users. If you have to change the serialization scheme, you may have lots of different users of your API, with not all of them trigger happy to upgrade, to your new binary serialization scheme, so you may have to resort to running and maintaining two versions of your API for a while. It is wise to remember that your system has never had fewer and happier users, than when you are planning the design of it, hence the cost of changing serialization is practically free.

Some Questions to Guide The Choice of Serialization

Luckily there are a few questions we can start asking ourselves to guide us towards a reasonable solution. Here are some of them:

  • Who are the participants of the communication?
  • What are their limitations?
  • Schema or Schemaless?
  • Text or binary?
  • Do you need versioning?

Who Are The Participants of The Communication?

In most software systems you will have two kinds of participants in the communication: humans, and machines. Even though you rarely get to present data directly to humans these days as command-line programs have become less and less prevalent. Users may still make demands, such as they need the data you provide, easy to process in Excel, a browser, or something else. This obviously puts some restraints on how you can format the data, as some ultra-compact binary formats may prove difficult to import in Excel, whereas a textual format such as CSV may prove a lot easier. When communication is between two software systems, you need to ask the question of whether or not it is really needed to read the communication between them in a text format, or whether or not a more compact binary format will suffice. Very often the latter is the case.

What Are The Limitations of The Participants?

Very often you will have to consider the requirements of the callers of your system. If you are primarily called by a React front-end, choosing a high-performance binary serialization scheme like SBE, may not be optimal as little tooling and libraries are available to support this format from javascript. If the callers are other systems with an ample amount of libraries and you can expect lots of calls, or sending large amounts of data over the wire, you may benefit from choosing a binary serialization scheme such as Protocol Buffers, Cap'n'Proto, or SBE, with the improved performance that comes from these schemes. In general, the performance difference between textual formats and binary can be in orders of magnitude, so depending on the number of requests your service is hit with, it is better to identify this early on in the development process.

Schema or Schemaless?

The choice of whether to use a schema or not has given room for quite some debate. If you choose to take the time and specify a schema, you make the lives of any developer calling your app easier. Most likely they may even be able to generate data models through tools. Choosing not to, makes your life easier as you will not have to maintain a schema while changing your data models, however, the callers may be in for a surprise when you change stuff and their code will magically break. For certain data formats such as XML and JSON you can choose whether or not you wish to define a schema or not using XML Schema, and JSON Schema respectively. But for other formats such as SBE, schemas are required. I generally prefer doing schemas as they help me think of how my data models are going to be, without diving too much into the code. But for smaller utility apps schemas could be overkill.

Text or Binary?

Choosing a text format for serialization allows developers to peek into the data going over the wire using a basic text editor, this often comes with costs of larger message sizes and slower deserialization than most binary formats. Binary formats will often expose the opposite trade-offs. Even though some people will make the argument that textual formats such as JSON are not human-readable unless you have memorized the entire ASCII table. While technically this is true, it does not change the fact that there are lots of tools out there, that will print both XML and JSON without much hassle, while you may need to spend a little bit of time developing your own tooling for displaying the content of the messages going over the wire in binary serialization scheme. So which one should you choose? Again this depends on who the communication participants are. Obviously, the case could be made that since messages primarily are exchanged between machines, it makes little sense to make them human-readable, so why bother considering textual formats? Well in order to make other machines communicate with the machine running your code, you need developers to make applications to do just that. These developers are human and may have an easier time reading through some JSON rather than either relying on tools you provide or having to roll their own. Besides just as with editors, chances are that the same rule applies to language libraries. The more esoteric or niche the language that your clients use, the higher the chances are that they will still support JSON or XML through some packages, and not some of the more contrived binary formats. As a rule of thumb, I generally go for binary serialization with schemas when exposing them internally in the organization, especially if I know most of the code base is either written in .NET, C++, or Java. Should the need arise to expose a textual format, I would add a translation gateway, in that way, the extra complexity and performance cost of translating from binary to textual formats is hidden in a single spot.

Versioning or Not?

Do you ever need to roll out features down the line that will require new fields to be added to the objects going over the wire? This is where versioning comes in handy. If you have schemas that specify the version of the data objects going over the wire, each client will know which messages it can handle and which it cannot. In quite a few formats adding new fields is not a problem, most JSON frameworks will happily ignore fields you have not specified in your DTOs, and some will even assume default values for the fields in your DTO’s that the deserializer could not find a matching value for. But adding and removing fields from your DTOs is a perilous path to go down, especially if the people calling your service are not onboard with the changes. That being said, if you do work in a highly agile environment, and you run an ASP.NET web service with autogenerated Swagger documentation, that may be okay. Other serialization formats, such as SBE, offer support for versioning, where you can add certain fields, specify from which version they are applicable, and the default value for messages from earlier versions. Whether you need to consider versioning in your serialization format, is highly specific to your situation, but it is definitely worth considering early on in the design process.

Conclusion

Serialization is a non-trivial issue, that if not addressed in time, may lead to other issues downstream in the development phase. I hope that this article has been helpful in changing your view of serialization, from just picking a sensible default, to make it an active choice in the architecture of your application, whether or not you choose to stick with the stuff you know, or try something new, you should now be armed with some knowledge of the trade-offs your choice exposes. Best of luck with your future endeavors with API design and serialization.

--

--

Software developer, with a background working in the financial industry. Writes about software development, and other stuff I find interesting.