If you make a quick review of apps in various categories — healthcare, games, finance, — it will show that the device’s location plays a bigger role than whoever imagined. Services may use location data for primary functions (like Uber does), or for secondary ones (like Facebook). But despite the role, location data makes the use of service more user-oriented and, by that mean, more convenient.
Creating a location-based service is a big deal, but quite realistic. The article will show how we approached the backend of a location-based service – namely, a delivery service Azyan. We will discuss the business logic, the flow for different roles, and where and how we stored data. Let’s begin!
Location-based app development: Key objectives
Azyan is a service that connects drivers and clients who need to order food delivery. Basically, they have a “fleet” of drivers who are ready to pick up and deliver orders on demand.
Client was looking for a Python development company and we were introduced to the project. It had three things to consider:
- There will be different roles, namely, the set of functions and permissions associated with users – customers, driver, admin, or operators – who require personalized functionality and UIs.
- The service will extend the range of delivery categories, and to avoid the need to rewrite the entire code, we had to take that into account at the very first stage of development.
- It should be ready for a worldwide use. As in the previous point, the long-term perspective must be considered in advance.
For the back-end, points 2 and 3 mean high server loads. Understanding the importance of responsiveness and high performance in positive user experience, we decided to first create a flow for a simple use case, and start brainstorming.
Location-based app backend technologies
Here’s a flow for a simple use case “online food ordering”.
As you see, this is a rather typical use-case for end-users, and a challenge for engineers. To build a location-based app, we identified three main challenges we had to deal with. Remember these, as we will return to them soon.
The project challenges:
- Business logic
- Transmitting live data to the drivers and service (regarding order status, driver location, etc.)
- Data storage (i.e., storing and processing the coordinates). This helped us identify what technologies we should choose. Thus, we used Python and Django for the product’s business logic.
For another challenge – sending the live data to both users, the driver and customer we chose Aiohttp async service. It would ensure the immediate processing of the data and live transmission of it to the driver and user interfaces. Later in this article, we will show how it saved us hours of work.
Then we decided to go for Redis to (1) transmit messages between the services and (2) store and display driver location GEOADD. And the final technology used to handle the challenges listed above was PostgreSQL, for the main database.
Solutions for location-based service backend
We will refer to the three problems mentioned above, and go with the business logic first. Since order processing involves high-load processes such as processing of the location data and transmission of the live data to users and drivers, we decided to divide the whole product into two structural elements. The first one handles business logic in general; the other one ensures the processing and transmission of all data in live mode.
Since we had separated the business logic from processing and data transmission, we used a different structural solution for it. The separation itself would have been useless if we hadn’t clearly identified what it had to deal with. The list was as follows:
- **Handling users and role permissions**. We had several roles with different permissions and available actions. Those were mostly about viewing and editing delivery requests (operator and admin correspondingly). - **Managing the delivery request process**. We’ve described the “request an order” use case from the user perspective above. Here’s the technical flow of the same use case: - Create a request using the provided by the **Django** application. - Publish the request data to the **Redis PUB/SUB** channel. - Schedule a **Celery** task to broadcast the request. - Check if the request doesn't have an assigned delivery - Define the search radius (first run – default radius; each subsequent run increases the radius) - Get a list of drivers in the given radius using **GEORADIUS** - Send the request to the drivers within the radius
Transmitting live data
The initial request in the non-functional requirements stated that the system had to be able to process 20,000 requests a day from 2,000 service providers, without a loss in performance. Now, here’s the trick. Remember when we first heard about the project, we singled out worldwide usage as something we should always bear in mind? The plan was to build such a system that could support 10-50x scale growth over the next two to three years without any performance losses. Our decision to separate the business logic and data transitioning helped us achieve this task.
Aiohttp async would deal with the following live issues:
Deliver the live request offer to the driver application. A driver would automatically get a notification about a new offer without having to continually refresh the list of available offers.
- Deliver live request updates to the webpage. Not only does this ensure positive a user experience; it also lets the operator see the most recent request updates. Otherwise, unless a driver refreshed the request manually, they might later discover that some of the purchased goods were removed from the order.
- Handle driver coordinates. This allows all users to track the driver’s location, thereby letting them estimate the time of arrival.
Data storage — Live location
To process the live driver coordinates, we opted for GEOADD – a Redis command that adds geospatial data — longitude, latitude, and name — to the specified item. The data is stored as a sorted set. When required, we can retrieve the required items (location data) using the query by radius (the above mentioned GEORADIUS).
After an item is added to the set, Redis lets us update its location using the same GEOADD command. When called with existing items in the list, GEOADD updates the spatial data (longitude and latitude) associated with each item with the new values. This allowed us to provide live locations in the application.
Algorithm for the communication
To transmit the data between two services (the Django app and the aiohttp service) we used another Redis command — PUB/SUB. To transfer the data (request updates or offers) to the end user, aiohttp serves as a “transport” between the Django app and end users, as it handles the websocket connection as shown in the image below:
All drivers connected to the channel — in our case, a list of orders — will get a message with the details about the order. The same applies to the web app that receives updates about the order if one appears.
Here, we shared what main things we considered while building backend for our product Azyan, a delivery service that connects drivers and those who need fast delivery of goods. After learning what actions the client expects the service to perform, we identified the technology stack for the product:
- The business logic — Python and Django - Collecting live data – Redis - Transitioning the live data – aiohttp - Database – PostgreSQL
Having come up with an idea on how to organize the application, we decided to separate the Django app from the data transmission service.
If you’re interested in other platforms for building a location-based app like Azyan, we suggest you read how to develop a location-based application using React Native. Many apps are implementing location-powered features, and perhaps you’ll get a chance to use the knowledge from this article when you work on your own app.
This article about building location-based services is originally posted on Django Stars blog.