Powering iOS, Android and web experiences with a backend-for-frontend
Hi there 👋 I’m Skye, and I’m a Software Engineer at iZettle. I work in the Understand team in the Food & Drink Area, on our mobile apps, websites, and backend services.
When building our new iZettle Food & Drink products, we encountered challenges in getting data to our clients in a fast and efficient way, and this article discusses how we fixed some of those issues.
Our focus is to help our sellers understand their food and drink business, by analysing their data, and getting that analysis to them quickly so they can make the right decisions and achieve their business goals.
Getting data to our clients
We’re building new web, iOS and Android based reporting experiences, which have bespoke designs to suit their respective platforms, to help us achieve our focus described above.
All of our seller reporting data is stored in a brand new “data warehouse” service. This service is populated with data through an event-driven system (specifically, SQS) from an upstream “source of truth” service we call “transactions”, as our sellers make sales through the iZettle Food & Drink POS (an iPad point of sale system).
We started building the web experience first, alongside the data warehouse. At first, things were fine, but as the service grew and we started to build out our iOS and Android experiences, we started to hit some problems relating to what’s included in each of the service endpoints, and how they evolve. The mobile apps would end up receiving data that they don’t need, which is problematic when sellers are frequently on mobile internet.
Further, the release cadence of the website and our mobiles apps are necessarily different per platform. The website can be delivered quickly, with changes taking only a few minutes to go out to production, but our mobile apps have a two week release train for new code, because of how app stores work. Deployed versions of mobile apps must also be supported for longer, causing frustration when we’d like to change or deprecate an endpoint that was previously only used by the web.
Ultimately, with our data warehouse, we want to strive for two things:
- The time from selling something, to the data appearing correctly in a seller’s reports, should be as low as we can reasonably make it with a complicated distributed system
- Sellers should be able to request data over time periods that are helpful to them, including over the previous year, and have it appear on their chosen platform as quickly as we can reasonably get it to them
We knew that, unless we changed something about how we get data to our clients, our problems would only get bigger as we added more endpoints, features, and clients.
Working towards a solution
As with all the problems we have to solve, we did some investigation and came up with a few possible solutions:
- Have frontend developers own the presentation of data within the existing data warehouse service, requiring them to learn Go
- Investigate and implement a brand new GraphQL service
- Investigate and implement a “Backend for Frontend” service, potentially in a language more suited to frontend skills
The team has a lot of micro-service and REST-ful service experience. The cost, for our team, of setting up new services, is relatively small.
Most of the iZettle Food & Drink backend is written in Go. It’s a good language, with great performance characteristics (especially when dealing with large amounts of data), but it’s difficult for frontend team members to learn, contribute to, and context switch between when delivering their other work, which is written largely in Typescript, Kotlin, and Swift.
Other sections of the business use Kotlin to build their backend services already, and I personally have a lot of Kotlin experience, so we felt like that was a good bet.
Our team has little experience with GraphQL and less appetite to own and maintain a brand new paradigm in our area for getting data to clients, when a more typical REST service will get the job done and let us ship quicker. It did look promising, and it is used elsewhere in the business, but we decided it wasn’t the right fit for us at the time.
Given all the above, we decided to try making a “Backend for Frontend” service, written in Kotlin, to focus on getting data quickly and efficiently to our clients with great native experiences. It would be owned and maintained by the frontend team, with help from backend team members to make sure everything was up to standard. 💪
Building the BFF reporting service
We picked Ktor as a service framework because I had some experience from other projects, and it can be as lightweight as you want it to be.
To make spinning up this new kind of service nice and simple, and inspired by my colleagues’ work on a Go-based service chassis internally called
izettlefx, I put together a Ktor-based service chassis, exemplified by one of the tests:
I’m really impressed with how easy Ktor makes unit testing services. With a small amount of glue code, we have separation between our “request contexts” and “response handlers”, meaning we can thoroughly test our code at multiple levels.
withTestApplication in Ktor (shown above) lowers the cost of doing a more integration-style test enough that we have many of them for the service chassis, and we can be much more confident that the integration of the features is working as intended.
In the BFF reporting service itself, request handlers often aggregate data from upstream services— combining seller information like their business details, and seller data from several different endpoints exposed by the data warehouse.
We built the service in a way which frontend team members are comfortable with. We use ReactiveX a lot, so it made sense for us to use this tech for wrangling upstream requests to make our mobile landing page. Relatively complex upstream request combinations become simple:
Finally, requesters built to fetch upstream data from services can be reused for different endpoints and platforms — the requester that provides payment type information to our web-based Sales Report, in component-form, is reused to provide the same information in the mobile sales reports.
Ktor has been great to work with. It has a really solid set of building blocks with which we built a small service framework. The new service is very much focused on solving a particular set of problems, without being a kitchen sink. When we need another piece, we add to the service framework incrementally.
Trust within the team is really important — everybody took the time to listen and discuss solutions when we started to have problems building new experiences with the existing data warehouse service. Folks helped throughout with backend best practices, and the service is deployed, monitored, and managed just like all our other services, with frontend and backend team members contributing to its ongoing development.
Our data warehouse service stays lean, and the team builds fast endpoints for extracting seller data as we want to. The BFF service worries about the aggregation and presentation, and clients get the data they need in a format that suits the platform. 🚀