Counting Calories: How We Improved the Performance and Developer Experience of UberEats.com
12 February 2020 / GlobalAt Uber Eats, we want ordering the food you crave at the touch of a button to be as easy as possible, whether on desktop or mobile. That’s why our engineering team spends a lot of time thinking about, building, and maintaining web applications for restaurants and customers. Uber Eats relies heavily on web-based applications, from our Restaurants app (which targets the web through React Native for Web) to our extensive analytics platform and menu tooling.
UberEats.com, which lets eaters order food through a web interface, complements our mobile applications by filling niches in which a web app provides a better user experience. For example, the extra screen real estate makes it easier to place large orders.
It also gives us the flexibility to integrate the Uber Eats food ordering experience with other Uber apps, providing a more seamless experience and freeing up native storage space on user’s phones. With newer versions of the Uber Rider app, for instance, users can order food directly in the same interface without having to install the Uber Eats app. A WebView version of UberEats.com makes this functionality possible.
The UberEats.com team spent the last year re-writing the web app from the ground up to make it more performant and easier to use. While refining UberEats.com, we prioritized increasing developer productivity without compromising on quality or stability.
As many engineers know, however, rewrites are not a panacea; they can be expensive, time-consuming endeavors. When planning a rewrite it’s easy to underestimate the scope of the task at hand, setting the project up for failure. In spite of the risks, building a new system from scratch tends to be much more alluring than revising an existing one because it provides an opportunity to re-think the existing architecture from scratch and address any structural pain points. Despite these benefits, working with an existing system is safer: engineers typically understand the current implementation and can accurately forecast the complexity of any migration project immediately.
Keeping this in mind, we considered numerous factors in deciding whether or not to rewrite UberEats.com. Ultimately, we decided that a rewrite would enable us to both conserve time and resources as well as implement new and intuitive features. This decision led to a more performant and scalable platform that facilitated an improved user experience for users of the web app.
A saga of Sagas
Written in JavaScript, UberEats.com was launched in early 2016 as a React single-page web app that leveraged Redux/Redux-Saga for state management and a backend powered by Express, a popular Node.JS HTTP server. Custom code allowed for the React components to be rendered on the server. However, the use of a Saga pattern led to additional complexity that prevented the web app from scaling to meet user needs and keep up with developer velocity.
UberEats.com used Redux-Saga for asynchronous actions such as data fetching. Sagas in this context are very different from the Saga pattern, introduced in the 1987 white paper, Sagas, by Hector Garcia-Molina and Kenneth Salem, to improve the performance of long-lived transactions. Instead, Redux-Sagas behave like microservices, communicating using “effects,” which have a type and payload.
Effects can read state from the Redux store, wait on promises, and listen for specific Redux actions. An effect that fetched user information from our backend might look like:
{
CALL: {