Apps have come a long way, getting more personalised and making developers constantly experiment with different things like A/B testing and feature flagging. This can be challenging, as it requires a lot of logic.
Common approach – REST APIs
Until recently, we handled these cases using REST APIs. For example, let’s say we are fetching a list of hotels that we wish to display in a list on the screen:
Here, the client will get back a list of hotels and use them to set the data in the list. In this case, the data is driven by the server, but the UI is defined by the client. The UI is statically calculated by the client, meaning the UI will always contain the same layout components. The server is returning the data which is used to populate the pre-defined UI components.
This approach has multiple problems:
- Over-fetching (returning data fields in the response the user does not need),
- Lack of declarative data
- Large amounts of logic required by the client
- Duplication of core business logic across multiple platforms (iOS, Android, etc.)
- Thick apps require constant updates to enhance user experience, security, or to test different scenarios
GraphQL API instead of REST API
One step forward is to use GraphQL instead of a regular REST API. In short, GraphQL is a query language for your API, and a server-side runtime for executing queries using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.
The above used case can be transformed like this with GraphQL:
This approach is more declarative, allowing you to ask for what you want and the related data. However the client still controls a lot of the logic (deciding what it wants), often making the app heavy in logic and hard to update.
Server-Driven UI with GraphQL
Server-Driven UI is not a new concept, and is successfully used by many companies to allow for more dynamic UIs in their apps, making experiments like A/B testing much simpler (along with quicker adoption for any changes shipped).
Moving the Business and UI logic/rules on the Server means:
- Thinner and more reusable client (simpler model)
- Rules are written once and maintained in one place
- It enables us to make changes to enhance the user experience without having to release a new version of the app (UI refresh, run-time personalization and curation)
With Server-Driven UI, the application (client) does not have the structure or components of a screen already defined. Instead, the server will return what that screen should look like – both in terms of the data for the screen and how that data should be presented, including the order.
Here the API returns a collection of UI components which are used to make up the presentation of a screen. The data of the items is the same as in the previous example, but this time it is contained within the UI components to be displayed. This way, the application becomes a simple renderer of visual components, displaying what it receives from API.
Note: Because of using GraphQL and its fragments functionality, the _type property above automatically maps to a strongly typed object within the clients via the use of Apollo.
Of course this kind of approach is also possible with REST APIs and imperative UIs (with a lot more complexity and code), but the type safety of GraphQL + Typescript, coupled with declarative UI frameworks like SwiftUI, React, and Jetpack Compose reduces the work dramatically.
Behind the scenes
Here are some high level details on how this works behind the scenes:
- When a client needs to request the presentation for a screen, they’re going to perform a query to do so. In the above used case, this will be either to query the hotels list, or an individual hotel details.
- The resolver for this query will query the database for the required data (just as we would with non-server driven UI)
- The resolver will then create the presentation for the screen in the form of a collection of objects. This list essentially defines how the screen should look and what it is made up of.
- The resolver will return this presentational object, which will be mapped by GraphQL to the corresponding Component types using a defined Type resolver
- Once all of these things have been mapped by GraphQL, a Hotels reference will be returned from the query
This way the API handles a lot of the responsibility that the client previously owned.
While in many cases, the Server-Driven UI approach is very useful, for example when dealing with multiple clients like Android / iOS / Mac apps, it is important to note that it is not a fix-all approach. We should keep in mind when it makes sense to use it, and how far into the components tree it makes sense to go, as the more complex the UI, the more complicated the server-driven UI logic becomes. And even if the UI remains relatively simple, we also need to decide whether it makes sense for the UI to be server driven, if for example, we have some screens in our app that don’t change, or have high native complexity. Once these initial questions are properly addressed, Server-driven UI can provide a simple, easier-to-use approach to address some of the fundamental challenges and open up new possibilities in app development.