Backend Driven Development — iOS

Rodrigo Maximo
Movile Tech
16 min readMar 2, 2020

--

[Thanks to my cowriter Raíssa Nucci]

Advantages and experiences with the technique

If you are a mobile app developer you may have, at least once, envied your web developer fellows for their ability to deploy layout and flow changes in a snap and their possibilities to run hypothesis tests in the blink of an eye, and iterate over the results even faster.

As mobile developers, we are conditioned to believe our speed of delivery and iteration is strongly attached to app rollout, user adoption, release train windows and App Store’s, sometimes frustratingly long, review time. Following this rationale, working on a Software Development Kit (SDK) would be even slower, after all, you would have to face the challenge of fitting your requirements on someone else’s product development and release cycles. This could be the perfect excuse not to perform hypothesis tests at all.

Luckily at MovilePay, as mobile developers responsible for developing and maintaining an iOS SDK, we found our way out of this nightmare by adopting the concept of Backend-Driven Development.

In this article, we will shed light on the subject and walk you through the steps of Backend-Driven Development, explaining a little bit about this way of designing apps, telling you how we applied it to some of our specific problems and, also, which benefits it has brought us.

Backend Driven Development

First of all, by no means we wish to claim the invention of the wheel to ourselves. Many of our ideas and designs were inspired by John Sundell’s Building component-driven UI at Spotify talk (great talk, recommended), where he talks about some of their experiences in using this concept and how it helped them with A/B tests, to not depend on App Store releases and also the advantages of having low dependency between core models and views.

We have just added some tweaks so it would fit better to our specific requirements. Keep that in mind and let’s get into it.

What is it?

Briefly, Backend-Driven Development (or Backend-Driven UI, or also Server-Driven UI) is the idea of developing front-end applications with screens and flows based on servers responses. This means screens and navigation have different behaviour depending on server’s responses.

However, before going any further, it is nice to understand why this technique has emerged, that is, the problems it tries to solve and why it may help many front-end developers.

Well, often the most common task in the process of developing mobile applications is implementing screens and all their visual components and user interface to allow users’ interactions. Some of these components are visually filled with information provided by API’s payload, probably a JSON with primitive types, such as integers, booleans and strings, or by the result of business logic operations the app applies over that payload data.

This classic implementation of screens works, but it also has some limitations or problems:

  • You need to develop the same code for multiple platforms (Android, iOS, web, or whatever other front-end). With this approach it is harder to maintain consistency between platforms, increasing the occurrence of bugs and inconsistencies.
  • For mobile applications, every update or change implies on code modification, and therefore, an App Store release cycle, which makes the release process very slow.
  • A/B testing is harder. It is more difficult to validate ideas and extract data from users’ interactions, which could be used to conclude important things about the product.
  • Your code is more difficult to maintain, since a lot of business logic may be present in the application side.

Backend-Driven Development has come to help against those inconveniences.

Consider the example below (it’s an iOS example but it could be easily applied to any other front-end application):

{
"pageTitle": "Demonstrative Title",
"boxes": [
{
“type": “bigBlue",
“title”: “Nice box”,
“subtitle”: “Subtitle of box"
},
{
“type": “smallRed",
“title”: “Great box”
},
{
“type": “rectangleGreen",
“title”: “Incredible box”,
“subtitle”: “Subtitle of box”,
“number”: 10
}
]
}

For the given JSON response above and applying the Backend-Driven Development concept, the app would present that screen.

All the business logic to decide which box should be presented, which texts, subtitles and any other visual information that should be displayed are centralised and abstracted on the server side, avoiding its replication on each front-end platform that consumes this API. The server then applies this business logic and uses its results to assemble the API response JSON representing it.

In this example, the application knows how to display each of those boxes (“bigBlue”, “smallRed” and “rectangleGreen”), and it is going to use the extra information contained in each box to do that. An interesting thing to observe is that the variable “boxes” is an array, which allows the backend to send as many boxes as it desires, and the app will be able to handle and display each one of them.

You have probably realised how A/B tests could be implemented in this scenario. In our server we could select some users to only see “bigBlue” boxes while others would see all three box’s types. We could also have A/B tests to switch the order in which our boxes are presented.

Another possibility is changing defined visual information. For example, if we want to modify titles, we just need to update our server response. The same goes for subtitles and boxes’ presentation order. No App Store release required.

{
"pageTitle": "Demonstrative Title",
"boxes": [
{
“type": “rectangleGreen",
“title”: “Another title”,
“subtitle”: “Another subtitle”,
“number”: 100
},
{
“type": “smallRed",
“title”: “Different title”
}
{
“type": “bigBlue",
“title”: “Backend Driven”,
“subtitle”: “Development”
}
]
}

Be careful

An important thing to keep in mind when considering to use this concept is how far and complex you should go. In other words, how much flexibility do you need?

After a lot of discussions, readings, studies and, most important, some experience, we really do recommend a mid term flexibility.

If you go too shy on flexibility you end up not having any advantages over plain old classic development approach.

On the other hand, we recommend not going overboard with it, once the implementation for all this flexibility probably won’t pay off. You cannot predict all the variables, such as screen size and other device specifications (that makes margin definitions off limits for our backend). After all, you would end up with an extremely complex and overkilled presentation logic on the application side. Furthermore, you cannot predict every change your design team will ever want to make, so you would have to change code in the app anyway and still have many complex visual paths uncovered by tests, for example.

In short, in MOST cases, you don’t need a HTML-like flexibility. So our advice is to think in all of those points before developing screens and server implementations using the Backend-Driven Development concept.

Our Experience

Now we have already introduced the concept, it would be nice to share some of our practical implementation of this technique, sharing how we have taken advantage of that, how it has improved our applications, helped us with A/B tests and also speeded up our development processes.

A Super App Case

By now you are probably familiar with the concept of a Super App. Most likely you’ve heard of WeChat, the world’s most successful benchmark, from China. The idea behind a Super App is to gather several recurrent services in one app, creating a single access point to most online needs present on users’ day to day life, creating a high frequency product.

In a not distant past, we had our version of a Super App. It was thought as an innovation lab, where we could test and validate a variety of services we thought could be relevant, as well as try to find insights between this services’ usage.

Given that we wanted to test different combinations of services, with different user groups and contexts, we had the challenge of building our app in such way that service availability could be easily changed. We needed to be able to change which services a given group of users would see as fast as possible, as hypothesis were defined and analysed. For most, we did not have the time, the work force, nor the will to go through an app release every time we wanted to turn something on or off. Those are some reasons we have already mentioned to apply Backend-Driven Development.

Because of this challenging scenario, we designed our home screen based on services sections. Each section contained a collection of rows, called Widgets. These sections could display entry points for any given service already implemented, in any order and highlighting some of them.

Sometimes a service would be the only element in a table view line, in other cases we would have three of them together, according to what was being tested. Instead of having any rule hard-coded on the app, we left it to our server’s decision. Therefore, the app would only know the definition of a service entry point, and how to draw each defined style, while our server would tell us which services should be presented and how. Here are some examples of widgets our app may support.

So basically, to build our very adaptable home screen, we just had to receive a response from our backend containing a list of sections, where each section contained a list of widgets. For example, the JSON file response below represents the response our app would parse to present the home screen illustrated below.

[
{
"title": "Section 1",
"widgets": [
{
"identifier": "COLLECTION_WIDGET",
"content": [
{
"title": "Title A",
"image": "A",
"color": "yellow"
},
{
"title": "Title B",
"image": "B",
"color": "blue"
},
{
"title": "Title C",
"image": "C",
"color": "red"
},
{
"title": "Title D",
"image": "D",
"color": "purple"
},
{
"title": "Title E",
"image": "E",
"color": "green"
}
]
},
{
"identifier": "IMAGES_WIDGET",
"content": [
{
"image": "Image",
"color": "green"
},
{
"image": "Image",
"color": "blue"
},
{
"image": "Image",
"color": "orange"
}
]
},
{
"identifier": "COLLECTION_WIDGET",
"content": [
{
"title": "Title E",
"image": "E",
"color": "green"
},
{
"title": "Title F",
"image": "F",
"color": "purple"
},
{
"title": "Title G",
"image": "G",
"color": "red"
},
{
"title": "Title H",
"image": "H",
"color": "blue"
},
{
"title": "Title H",
"image": "H",
"color": "yellow"
}
]
}
]
},
{
"title": "Section 2",
"widgets": [
{
"identifier": "CELLS_WIDGET",
"content": [
{
"title": "Cell 1",
"color": "red"
},
{
"title": "Cell 2",
"color": "purple"
},
{
"title": "Cell 3",
"color": "yellow"
},
{
"title": "Cell 4",
"color": "blue"
},
{
"title": "Cell 5",
"color": "dark green"
}
]
}
]
}
]

Also, we present some other screens examples that could be generated thanks to backend driven development.

Flexible navigation

Our Super App certainly served to its innovation purpose. We have learnt a lot from its hypothesis tests, but one thing we most definitely became good at was processing payment of different services and goods. We had an app checkout that was able to process payment for any service we had implemented. So why not use this knowledge for the good of the company? Having control over payment transaction is a great advantage, as we could achieve very low costs with a huge amount of transactions, alongside with gathering valuable customer behaviour data.

We then decided to implement a payment SDK, following the model defined by Google Pay, Apple Pay or VisaCheckout, in which we could potentially integrate with any other app. This SDK went into production integrated to one of our goods’ selling app.

The problem is that working on an SDK implies in very low testing ability when using the common development patterns.

Because we were working with payments, flow conversion was extremely important. We did not have the luxury of loosing our users at any step of the conversion funnel. That considered, we had to optimise our flow, from user registration to card addition and to the payment itself. And here is where we could use Backend-Driven Development again, taking one step further and turning our server into our app’s “navigator”.

Basically none of our screens knew which screen would come next. It was our server’s responsibility to return which screen should be presented after the current screen was done with its tasks. Each route contained the identification of the screen it was supposed to route to, along with all the parameters that screen would require to work properly.

These routes were Actions that our app knew how to parse and handle through a structure called Router. Those actions were nothing more than another node on our JSON response that, when parsed and identified were sent to the router, which was the instance that could interpret the actions and indicate which should be the next screen presented from this interpretation.

This simple change in development strategy was enough to enable the flexibility we needed to optimise our flow. We tested several hypothesis changing the step we would place the sign up form. It was possible to validate if it was better to present checkout screen before or after card addition, for example, one of the reasons we were able to optimise our conversion rate to be 30% better than other payment options.

This is yet another example of how we used Backend-Driven Development to solve some of our problems. It shows us that many times, an approach presented by other developers, that may look unrealistic or useless to you, if looked at by another perspective, can be adapted to solve a whole lot of your problems.

Hands on! The iOS stuff

Ok, but after all this blabbering about how Backend Driven Development saved our necks more than once, the reader must be thinking “But how the hell do I really implement this?”. Ease, we will show you how.

There are hundred different implementations that could lead one to achieve the same outcome. We are going to present how we did, specifically considering an iOS context, since we are iOS developers, but this concept can be reproduced similarly by any other front-end platforms.

When we implemented it, we took into consideration aspects such as how easy it would be to add new widgets, code readability and single responsibility. For serialisation, to make things easier and native, we opted to use Codable API.

Widgets (iOS)

In terms of flexibility and also based in our opinion, the best choice we probably have made was to generalize the widgets we desired to parse using a protocol. We also have defined an enumeration to be responsible to select which widget structure we should use to parse the data.

This enum is only responsible to select the suitable structure to parse the API response, taking advantage of the Codable API through Decodable protocol, that is implemented by the Widget protocol. For a better demonstration, we have defined the widgets structs below.

Finally, and maybe most importantly, it was defined a type erasure, responsible for parsing any type of widget through the required init implemented when we wish to modify the custom init provided by Decodable.

This type erasure is used to decode the widget identifier, and with its metatype property, defined in WidgetIdentifier above, discover which Widget struct should be used to parse the remaining information of the widget that is being parsed.

With all these implemented, the response that contain all the widgets information can be parsed using the struct below. It has a unique property, an array of a Widget protocol type, and it is capable to decode each one of widget using the type erasure defined above.

We could have opted for other solutions, such as not using a protocol and expecting our backend to deliver an array of each supported widget for parsing, but we found our protocol choice to be very assertive when we think in terms of maintenance and readability. It is only necessary to implement a new struct and to add a new case in the enum every time a new widget needs to be implemented. In that other approach we would also have to change the HomeResponse structure in a situation like that.

A possible API response JSON that would be parsed by this models is displayed below.

{
"widgets": [
{
"identifier": "BANNER",
"imageURLString": "url_image_to_be_downloaded"
},
{
"identifier": "COLLECTION",
"sectionTitle": "Section Title",
"list": [
{
"imageURLString": "url_image_to_be_downloaded",
"title": "Title item 1",
"subtitle": "Subtitle item 1"
},
{
"imageURLString": "url_image_to_be_downloaded",
"title": "Title item 2",
"subtitle": "Subtitle item 2"
},
{
"imageURLString": "url_image_to_be_downloaded",
"title": "Title item 3",
"subtitle": "Subtitle item 3"
}
]
},
{
"identifier": "LIST",
"sectionTitle": "Section Title",
"list": [
{
"imageURLString": "url_image_to_be_downloaded",
"text": "Text item 1"
},
{
"imageURLString": "url_image_to_be_downloaded",
"text": "Text item 2"
},
{
"imageURLString": "url_image_to_be_downloaded",
"text": "Text item 3"
}
]
},
{
"identifier": "BANNER",
"imageURLString": "url_image_to_be_downloaded"
}
]
}

This approach is really close to what we used to achieve our Super App result, which allowed us to present different services to different users, test many exhibition and highlighting hypothesis, changing which widget would be used for which service, changing on screen sorting and service grouping, things we even have already said.

Navigation (iOS)

Just as we solved our Widgets scenario, we tried to improve our navigation. For this, we created something almost identical to our Widget protocol, a protocol we named Action.

An action is a structured object, returned in the JSON response of some of our APIs, with an identifier and all the parameters that the scene it represents needs to be presented. Therefore, the Action protocol has the responsibility of helping to parse the structured object.

The only difference between the Action protocol and our Widget protocol is the provided method in the Action definition, that returns the corresponding scene to each Action. Take these implementations of actions as an example.

Briefly, the actions contain all the necessary properties to init its scene, and call the Coordinators method to create an UIViewController instance. For us the Coordinators are structures responsible for providing the UIViewController. Here is reasonable to mention that we use a factory design pattern in our Coordinators structures, represented by the static scene() method, which is responsible to create that UIViewController instance for each scene.

A nice thing to notice is that regardless the chosen Architectural Design Pattern (MVC, MVP, MVVM-C, VIPER-C, VIP, or whatever architecture that uses a Coordinator as the responsible for creating the scenes and navigating from the current scene for another one), this Action & Backend Driven Development implementation is feasible.

Now, talking about how to parse the actions, we used exactly the same type erasure used for widgets, a bit adapted for the actions context of course.

Here, an observation is valid. We could have used a Generics design pattern to avoid code repetition for the type erasures, but we have opted to not do it thinking in an easier readability and understanding of the article. An example of response structure which contains an action can be seen below.

This could be the JSON provided by an API, for example to build a UIButton of a screen. Then, the action object parsed after an user touch in this button may execute the provided action, which would make the application to present the Home Screen.

{
"text": "Demonstrative text",
"action": {
"identifier": "HOME_SCREEN"
}
}

This could be easily implemented by an extension of our Coordinator protocol, that makes all the Coordinators to be capable of get another scene through an action object.

This implementation allows every Coordinator to execute an action, that is, to create the next UIViewController instance corresponding for that action and then to present it. This completes our solution for navigation using Backend Driven Development.

A hint on server implementation

Your are probably wondering how all of this looks on the server side of the story. How not to make a mess of my services and core functionalities when mixing it with front-end info? Well, the trick is to work in layers, as most of the things in software development.

So, besides all the complex core services, we add another layer to our server, one that abstracts all these other services and applies all the applications related logic, converting the data into the response the fronted is expecting. This layer is called BFF (Backend For Frontend) and it guarantees the communication between two independent and unattached ends of the system. This is where strings, images, flow and style variation are configured and applied over the core data, before being sent to the apps.

Summing up

Our purpose on writing this article was to shed light on a different approach on how you look at your mobile development problems, so you and your team have yet another strategy in your problem solving playbook.

As we tried to make clear throughout the article, there are plenty of benefits on following the Backend-Driven approach, such as A/B Testing, releasing some stuff without the constraint of app stores’ release and policies, and also reducing inconsistencies between mobile platforms. But, just like any other solution pattern, this is not a silver bullet for application design. The application context and needs must always be considered. Will you have multi-platform apps? What kind of things will you want to A/B test? Do you need this amount of control over all your screens? How big will your payloads be? Do you have a backend team big enough to take on this?

Besides all that, we must always be very careful on how flexible we want to go. Building extremely generic UI components, changing margins and fonts can easily lead to very complex presentation code which follows to uncovered/untested UI paths that may originate horrible user experience. Remember, we are not trying to build a browser to handle an HTML-like payload. To be honest, most of out projects do not need that much flexibility and, seriously, we do not have time nor team to build that. Also, not every user has a good network connection available all the time, so we recommend being aware of your payload size.

That’s all! We really hope this article brought you some new knowledge or insights! GOOD CODING! Also, feel free to leave comments, contact us or even leave some feedbacks or share some Backend Driven UI experiences.

--

--

Rodrigo Maximo
Movile Tech

Lead Mobile Engineer at Nubank |  iOS Engineer