Extending Groser iOS and Rails apps to support grocery delivery in multiple cities - Part 1
7 minute read
Initially we were trying to proof a concept and didn’t think it will work, now over 1 year after our first delivery we’re facing new opportunity. After getting multiple inquiries from eager entrepreneurs in large cities, we decided to franchise our model. That requires extending internal architecture to support multiple cities.
Current architecture supports grocery deliveries from one store in one city only. We have 1 iOS client app, 1 iOS Shopper app and Rails back end. All apps are working over REST API namespaced under API::V1, supporting multiple cities will require creating new and introducing breaking changes into old API.
Setting up Rails routes
Let’s start with copying API::V1 code in app/views/api/v1, app/controllers/api/v1, app/helpers/api/v1 into new v2 directory.
Then I’ll update routes.rb and simply copy v1 namespace into v2.
I use excellent Sublime Text 2 and will use its project file folder_exclude_patterns setting to exclude V1 directories to avoid accedentally changing V1 of API.
After excluding old API from text editor, I’ll use global Replace command to change module V1 namespace to V2.
After that cleaned up deprecated code used to support old version of the iOS apps, when I didn’t think it merited upgrading API version.
Switch iOS app to API V2
I use AFNetworking with AFHTTPSessionManager descendant initialized with the base URL of an API. Just change v1 to v2 at the end of the URL.
Multi city delivery data model
Before extending current architecture for multi city deliveries, I drew the diagram of the current architecture and added necessary models and associations.
Left: Current Model, Right: Extension
We’ll do limited roll out of delivery across city with by dividing city into geogrpahical zones. I’ll create new Zone model with geometry:json attribute to keep GeoJSON-encoded geometry of the delivery zone:
Then I need to create default delivery zone for the current city we work in, Moscow.
At the start we’ll support delivery from one chain of supermarkets in the city, so I’ll create many-to-one association between DeliveryOption used to track availability of delivery windows, and City. Later when we’ll support delivery from multiple supermarkets in the city, I’ll move that association to Store model.
To enable variable prices for products in multiple cities, I need to extract Product attributes that will vary between cities. Attributes that will stay the same:
Attributes that I need to extract are:
I also need to keep barcode attribute in 2 places, cause bulk product barcode varies between stores. Let’s create ProductVariant model.
Right now we have only one Store with one set of products. To make it all work, let’s create ProductVariant for each Product currently in database.
Replace Product references in queries and associations with ProductVariant
First up is an aisle model which I mistakenly named Shelf
It’s Department turn.
Next up is ShelvesController.
Time to move Product.as_json method to ProductVariant. I override Product.as_json method to avoid using partials, which do slow down serialization to JSON.
Upgrade UserFavorites to ProductVariant
Turns out there are many more dependencies on Product, I use UserFavorite to track which products user has liked which, now UserFavorite should reference ProductVariant instead of Product.
I’m iterating over each UserFavorite setting product_variant attribute to ProductVariant created in previous migration for the given UserFavorite.product
Update User.favorite_products association to ProductVariant
Move Product markup calculation to ProductVariant
We mark up product prices for customers to incentivize shoppers who pickup and deliver products.
Upgrade OrderItem to ProductVariant
I fear this the most, cause 1 mistake in migration can break everything. I’ll keep original OrderItem.product_id attribute intact, and remove it when everything works again.
First, I’ll add OrderItem.product_variant reference and replace product_id, replacement_id and chosen_replacement_id with ProductVariant ids for given product’s id.
Change replacement and chosen_replacement class name to ProductVariant.
Upgrade possible product replacements
Oftentimes some products are sold out at the particular store, especially meat and produce in the evening, we give customer choice of possible replacements before placing an order. Let’s upgrade from ‘Product’ to ‘ProductVariant’ for replacements.
There are hundred thousands of RelatedProduct records and it’s eastier to recreate them later than find ProductVariant for each Product that it used to reference.
Upgrade user’s preferred product quantity
We keep track of quantity user ordered before, so if he bought 3kg of apples, next time he wants to order same apples, he won’t have to select the weight.
In Part 2, I’ll upgrade search to use ProductVariant, upgrade order dispatcher to support multiple stores and route orders to city’s shoppers.