9

There and back again: a {json:api} tale at Trainline

 3 years ago
source link: https://engineering.thetrainline.com/there-and-back-again-a-json-api-tale-at-trainline-f6b4d184c153
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

There and back again: a {json:api} tale at Trainline

Our team was tasked with creating a new RESTful API to help reduce the amount of logic that is implemented in potentially different ways across our front-end channels. One item discussed early on was if we should try using JSON API to structure our responses.

Other teams within Trainline have had some success creating JSON-API-based services in Ruby, and we saw no reason why our C# implementation would be any less successful. This blog post is an attempt to tell the story of the journey we took and where we ended up.

For a full explanation of what JSON API is, see the project site (http://jsonapi.org)

Why join such a fellowship?

Firstly, why would we consider JSON API for responses when, surely, standard JSON representation of our classes is simple and ‘just works’?

  1. We simply don’t want to spend time discussing the structure of our response and how best to optimise it. JSON API gives us a way of having a defined response for each endpoint without having to re-invent the wheel
  2. Resource objects. JSON API provides a way of linking primary resources. These can be displayed within the ‘included’ section of the response, to minimise requests, or just linked in order to minimise payload size. Given the data we are representing had numerous external resources, this appealed to us more than any other aspect of JSON API
  3. At the end of the day, it’s just another way of serializing data. We figured we can switch serialization methods easily enough and so why not try…

As with all these things, there are of course trade-offs that need to be made for these advantages

  1. Readability: There’s no other way to say it, but compared to a nice readable standard JSON output, JSON API is ugly! It feels like we are turning JSON into SOAP…
  2. Performance: Essentially, outputting JSON API requires mapping from nice C# objects to the objects in the correct formatting before serializing. That means that the whole mapping step is additional processing that needs to happen, which has a knock-on effect on performance. Here is some benchmarking we did, serializing a very simplified version of our model:
serialization_benchmark
serialization_benchmark

Implementation

The project site has a list of .NET server libraries which can be used to implement JSON API, so we started a spike to consider which option would work best for our scenario:

(http://jsonapi.org/implementations)

jsonapi_implementations
jsonapi_implementations

What quickly became apparent, is that most of these libraries would not support the JSON API output we were attempting to create. We believe that this is due to that fact that JSON API started as a group of guidelines and then evolved into a strict specification by version 1. The libraries are still catching up with these updates. Some common issues were:

  • Top level ‘included’ node not supported
  • Resource objects with external links not supported
  • Complex types are not always supported as an ‘attribute’

NJsonApi

Eventually, we discovered that NJsonApi provided the best option for us as it created the desired output with simple setup, integration into ASP.NET WebApi and extensibility. We were able to set up the mapping details and bootstrapping in the WebApiConfig, so any reflection needed by NJsonApi to map to JSON API would be done on initialisation of the service.

As we set up the more complex object, we discovered the need for these extensions / workarounds:

  • Every custom object requires mapping configuration which gets quite verbose and has a lot of duplication in our case, given that we want to handle all resource objects in the same way. We resolved this with a set of extensions to the NJsonApi ConfigurationBuilder to allow automated configuration when classes implement a specific interface or attribute
  • To specify serialization options (case, enums etc), we had to override the default serializer
  • Unable to serialize IEnumerable<T> types. There is an issue in the GetItemType method here which means that only collection interfaces that implement IEnumerable<T> are supported rather than the IEnumerable<T> itself. We worked around this by using ICollection<T> or something similar, but if we moved forward with NJsonApi it could be fixed with a simple pull request
  • The JsonApiActionFilter swallows any exceptions so and then falls back to the standard JSON serialization — which makes debugging a little trickier

Deserialization

Now we had our objects serializing nicely into JSON API, surely, it’s a simple case to deserialize back to those objects you say? Sadly, this is not the case. The NJsonApi deserialization did not seem to support the compound documents with included resources. Fortunately, there is a library available that only does deserialization and supports all these things: JsonApiNet

Sadly, JsonApiNet, whilst excellent at deserializing from our JSON API to C# objects, provided new and exciting challenges:

  • Because we now had a separate library for serialization and deserialization we had to create a new formatter for deserialization which needs to be at the top of the formatter collection, then the NJsonApi formatter needs to be the next in the list followed by the standard JSON formatter.
  • Performance: JsonApiNet uses reflection at deserialization time and the performance benchmarks we got were much slower than standard JSON:
deserialization_benchmark
deserialization_benchmark
  • GNU Licence: The limitations of the GNU licence mean it is better for us to not use open source software under this licence for commercial application

In the end, we implemented a custom solution for deserialization which involved deserializing to a ‘jsonapi’ C# object and then mapping from that to our original objects. Whilst this is very performant as there is no reflection necessary, it does make for tedious and hard to maintain code so a more performant reflection version would have to be created at some point.

The Return of the (James Newton) King

At this point, we took stock of the situation in the end-of-sprint retro and re-assessed the viability of JSON API. We decided that, whilst our service response did indeed provide a good candidate for the advantages of JSON API, we would not pursue it at this point for the following reasons

  • We were spending longer on the JSON API serialization than we were on solving the engineering problems that our project is trying to address
  • We already supported JSON as well as JSON API to demo work in progress in a more readable way
  • Removing the JSON API work would not take very long at all and not much work had been done to consume it yet
  • It was looking more and more likely that we would need to create our own JSON API library, but there is no reason that it would need to be done before the project work. The library can be built in isolation and then we can always switch back to JSON API at a later date — it’s just serialization after all…
  • JSON is simple and just works

About the author

Mike Reeves is a Software Engineer at Trainline working mainly with C# on back-end services. He likes good whisky, good books and good enoughcode!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK