Migrating Critical Movies to a new API

The New York Times deprecated the Movie Reviews API that powered Critical Movies. I needed to migrate the app to a new API with a different response structure.

When I built the app, I modelled it with a single Movie type that decoded data and displayed it in the UI. This meant I would need to propagate any changes to the model throughout the app.

I decided to use the migration as an opportunity to isolate the networking code. This would limit the impact of future API changes on the rest of the app.

Expected Changes

I would always expect to update code related to the endpoint and the network request. This includes constructing a new URL and decoding to new model types as necessary.

In this case, I had to add parameters to target articles in the Movies section of the New York Times. And, of those, only articles that were movie reviews. I also updated the parameters for pagination and sort order. The pagination logic had changed with the new API. Results in reverse chronological order had come for free before.

Separating Models

I needed to update my models to decode data from the new API structure. To make the app more resilient to change, I created a new set of models and mapped them to the existing Movie type. 

Movie became a flat model containing primitive data types ready for display in views. It retained Hashable conformance to work with UICollectionView. I was able to remove Decodable conformance, Coding Keys, and supporting data types. Instead, these live in the new models dedicated to handling the API response.

Future changes to the API, or switching to a new data source, would mean updating data decoding models. But there would be no need for any other changes downstream.

Mapping Models

Below, I’ve described some of the mappings I made and places I was able to improve my own bad code.

Critic’s Picks

The old API returned a dedicated field for whether a movie was a Critic’s Pick. It was an Int returning 1 if a movie was a Critic’s Pick and 0 if it wasn’t. By separating the models, I was able map this property to a Bool and rename it isCriticsPick.

This change kept the API logic in the networking layer. The caller no longer needed to test against an Int value it shouldn’t know exists. It insulated the call site from future API changes. As a bonus, the code is more readable calling if movie.isCriticsPick { displayCriticsPickLabel() }.

Date Formatting

With a single model, I passed the publication date straight through to the app’s views as a String. An extension on String converted it to a Date only to convert it back to a formatted String. Hilarious. Another API detail had found its way into places it shouldn’t have. 

The solution was to convert the incoming date String to a Date before passing it through to Movie. This insulated against future API changes and made the app more flexible. Data decoding and date formatting are now independent of one another. Date values can have different formats in different places in the app.

Mapping Bylines

The new API returns the article byline as a container. It includes a String for the byline as printed in the newspaper and an array authors with first and last names. I needed a single String byline.

I took the more robust option of mapping over the array of authors. I combined first and last name pairs and joined them into a single comma-separated String.

The other option was to use the print edition byline. I had to drop the first three letters to remove a prepended “by ” because I already provide a formatted “by” in the app. I started out using this method but switched to using the structured data. It protects against bylines containing errors or straying from this format.

Mapping Images

The new API response provides a large array of images for different screen sizes. The URL for each image is now only a path. I updated the existing Multimedia model I had used with the original, singular Movie. I mapped the largest image by width and construct a new URL string from the path.

Using the largest image was a quick solution to ensure it could display at all the sizes I could need. The price was performance. I can see a lag before images appear in the app and I need to improve this in a future enhancement.

Final Thoughts

The app is better than before but despite the model mappings, it remains flat. The app pulls data in from an API and passes into view controllers without anything between. I would consider this all framework code. Adding any business logic would need another a layer between the network and view layers.