Food Delivery Service

Overview

With an emergence of food delivery services, there weren’t any that set their focus on healthy foods. The client we’re working with on this project sees this gap and we’re working together to fill it.

This project is no small feat. It consists of five parts, each one somewhat reliant on the last. There are public pages geared towards getting consumers to register, as well as pages enticing both drivers and restaurants to take part in the platform. Once past the public pages, each one of the three user’s gets their own experience, with the driver being the only one who’s experience is entirely mobile app based.

Last, but certainly not least, is the administration portion. These admins, each with a different set of permissions, manage the flow of orders placed by the consumers until they are finally delivered by the driver.

The Build

This project is proudly built with Ruby on Rails. This monolithic approach allows us to more quickly add, update, or remove pieces of functionality and onboard team members. For pieces of the UI requiring a bit more reactivity, such as the consumer experience, React and TypeScript were used in combination with the new Webpack capabilities built directly into Rails 5.2.

We opted for React in this situation, rather than something like Angular or Vue, because the mobile application for drivers was build using React Native. Thus it didn’t make sense to add yet another framework to the mix beyond what we had.

As far as databases go, this project leverages PostgreSQL and, in some instances, Redis. We also use Redis’ Pub/Sub functionality for a number of things via Rails’ ActionCable, as well as our background job queue for Sidekiq.

All of this runs through our CI / CD system build on top of GitLab, which automatically runs our tests and deploys when they pass. This provides a small layer of abstraction in the development and deployment process, allowing developers to focus on what they do best–writing code.

Challenges

This is a large project, so I don’t want to go into every challenge faced for each aspect of the build. So I’m going to highlight one or two for each, knowing there are many more going unmentioned.

Public Pages

These pages were rather straightforward. They enticed the user to sign-up for the platform, as well as provided a few ways it could be used. Since the user could be a consumer, restaurant, or driver, we needed to cater the experience to each. Other than that, it was just a matter of rendering static content.

Consumer Experience

This is where things got trickier. There not only needed to be an ability to search for restaurants and view their menus, but the user needed to see an estimated delivery time for each restaurant, accounting for any traffic that may exist.

These estimations were solved with the help of the Google Maps Distance Matrix API, which allows up to 25 distances to be calculated from a given origin. In order to do this in the quickest way possible, the list of restaurants are being rendered to the page without a time estimation within a React container, which subsequently fetches the delivery distances for each restaurant. This solves a few problems: 1) perceived load time for the end-user, 2) less time spent in the server for each request, and 3) the restaurants can still be listed if the call to the Google Maps API failed for whatever reason.

Another challenge within the consumer experience was the tracking of orders as they go from being placed all the way to being delivered. While we imagine most user’s won’t sit with their “active orders” webpage open, we didn’t want to require them to refresh their page to see delivery updates.

Tracking these orders was solved on the backend by using a combination of a status enums and some timestamps to log when each step of the process took place (important for admins; more on that later). As far as keeping the order information up to date for the user, we had a couple options to choose from within Rails. We could either use ActionCable’s Pub/Sub (Redis) to open up a websocket for the user, streaming relevant data to them, or set up polling on a set interval.

Since we don’t expect people to remain on this page too often, it was seen as a pre-mature optimization to jump to using ActionCable for this piece of functionality. A React component is rendered to the page, which fetches the active orders on an interval using RxJS. If it becomes evident in the future that this needs to be websocket instead, it’s simply a matter of switching that RxJS interval to a subscription.

Restaurant Experience

Keeping on the topic of subscriptions, one of the largest challenges with the restaurant experience is the rendering of orders as they come in. Since this is the most important feature for a restaurant, it did make sense in this case to invest time into using ActionCable to keep an open connection with the server.

When a consumer submits an order, it queues a job in the background (Sidekiq) to send the data to any subscribers to that particular restaurant’s order queue. If the restaurant is viewing the queue, the new order is popped right in. If they aren’t on that page, and have notifications turned on, they receive a notification that an order has been placed. Since the system account for consumer’s to submit pre-orders, this functionality was limited to orders that needed to be delivered ASAP.

On top of managing orders, a restaurant also needed the ability to manage their own menu. This includes setting menu categories, items for those categories, option categories (required and optional) for each one of those items, and finally option items for each one of those option categories. For example, a restaurant might have a “Burgers” category with a “BBQ Bacon Burger” as one of the items with “Toppings” being one of the option categories and “Extra BBQ Sauce” being one of those option items. A restaurant can also set certain menu items as being “Featured”/”Most Popular”, which also required an attached image to whichever item they wanted to feature.

The approach to solving this challenge was to try and structure the DB relationships as closely as described above, while keeping the attributes within each relationship to a minimum. There wasn’t any other magic behind getting this to work, other than putting in the time to create the structure with proper validations, calculations, and lots of tests.

Driver Experience

With the API build-out being a matter of making already existing functionality available via a number of endpoints, the only real challenge not already built by this point, from a backend perspective, was authentication. We were able to solve this by leveraging stateless authentication with JWT.

As far as the mobile application goes, the trickiest parts were managing all the data needed to render the UI and working with React Native. We found Redux to be the best way to manage all the data, which is more or less the community accepted solution to this problem. But since our company hadn’t taken on a mobile app before (though we were extremely proficient with React), that was a journey in and of itself. It’s one that’s still ongoing.

Admin Experience

Take all the pieces of functionality talked about earlier, combine them, multiply by a factor of two or three, and you get the admin experience. I say this because the admins not only need to manage the entire application, hence their name, but admins were broken out into four subsets of permissions: 1) super, 2) regional, 3) district, and 4) zone, where a zone admin has the least amount of permissions, increasing as you go up to district, region, and then super admin level.

In order to get this functionality in place, all other parts of the application needed to belong or be assigned to a zone. Combine this with the fact that drivers signed up for shifts within each one of these zones, the zone level permissions and fetching of data got out of hand quickly. But some tender data massaging helped this issue in combination of assigning certain relationships based on the varying status’ an order can take.

Woof.

Personal Contributions

With the exception of the designs and mobile application for the driver, I was the sole developer on this project. All features built within Rails–public, consumer, restaurant, admin, and driver api–were built by me, including all UI and deployment configurations. I then worked with another one of our developers to build out the driver app, making sure he had all the endpoints he needed.