Search code examples
mongodbdatabase-designgeolocationnosqlgeojson

What is the best way to design MongoDB Schema for location based service


I am looking for a right database that supports location based search, found MongoDB supports GeoJSON Objects.

This is a Store Locator application, where user can look around and choose Store that's near to him for ordering products.

Simple Vendor schema :

const VendorSchema = new Schema({
    address: {
        type: String,
        required: [true, 'Please add address']
    },
    formattedAddress: {
        type: String
    },
    location: {
        type: {
          type: String,
          enum: ['Point']
        },
        // GeoJSON Points
        coordinates: {
          type: [Number],
          index: '2dsphere'
        },
        formattedAddress: String,
        street: String,
        city: String,
        state: String,
        zipcode: String,
        country: String
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});

Database will have many FMCG Products that multiple Vendors could be selling.

Product Schema :

const ProductSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    desc: {
        type: String,
        required: true
    },
    price: Number,
    createdAt: {
      type: Date,
      default: Date.now
    }
});

A Vendor can sell multiple Products and a Product could be sold by many Vendors, there's N-to-N relationship between Vendors & Products.

So was thinking of creating a new Schema, VendorProduct Schema :

const VendorProductSchema = new Schema({
    price: Number,
    discountVal: Number,
    vendor : { 
        type: ObjectId, 
        ref: 'Vendor' 
    },
    createdAt: {
      type: Date,
      default: Date.now
    }
});

This is where its getting tricky / challenging :

User's can either look-up for Vendor/Stores around them or can also directly search for a Product.

If asked to find Vendors, its straight look up in the Vendors collection with-in certain Radius.

Vendor.find({"location.coordinates": {$geoWithin: {$centerSphere: [[User long, User lat], 1/6378.15]}}})

But when user searches for a Product :

System should send back searched Product(s) details along with near by Vendors who are selling them - from closest to farthest, in certain radius.

For this reason i was thinking of storing Vendor's GeoJSON location details for a Product in VendorProduct schema.

VendorProduct Schema with GeoJSON details :

const VendorProductSchema = new Schema({
    price: Number,
    discountVal: Number,
    vendor : { 
        type: ObjectId, 
        ref: 'Vendor' 
    },
    location: {
        type: {
          type: String,
          enum: ['Point']
        },
        // GeoJSON Points
        coordinates: {
          type: [Number],
          index: '2dsphere'
        }
    },
    createdAt: {
      type: Date,
      default: Date.now
    }
});

Let's say user searches with keyword ONIONS. It will be found in Products collection, will use ProductID and in-turn filter them in VendorProducts collection based on Geo co-ordinates. In that way i can pull Product info like description, images from Products collection & Vendor, Prices info from VendorProducts collection.

This is only keeping in mind, a single Product can be sold by multiple Vendors. When user searches with a Product name - there could be N number of sellers of this product at different prices. But to find only closest vendors, was thinking of storing Vendor location details against Product in VendorProductSchema.

FEW QUESTIONS :

  1. Is it right to use MongoDB for such use-case ?
  2. Is this the right design and approach ?
  3. What is the time complexity of such Geo location searches in MongoDB ?

Solution

  • As per my understanding, you should only save the location coordinates in Vendor Schema only, because they are related to vendors. Your old VendorProduct Schema is fine

    Case1: User is looking for nearby vendors

    You need to run a query to find the vendors on the basis of the user's location and provide the vendors listing to the user

    For that, you need to run

    Vendor.find({"location.coordinates": {$geoWithin: {$centerSphere: [[User long, User lat], 1/6378.15]}}})
    

    Case2: User is looking for products he/she can find nearby

    In that case, also search for nearby vendors first by the above query (Remember there will be limited no of vendors nearby, if not you need to reduce the search radius).

    Once you get all the nearby vendors, save their ids in an array, let's call it

    vendorArray = [vendor_id1, vendor_id2.....]
    

    Then search for products in VendorProduct schema

    VendorProduct.find({
      vendor : {
        $in: vendorArray
      }
    })
    

    The above solution was just for an alternative to store location information in VendorProductSchema

    Now you need to get the product details after getting product ids from vendorProduct collection, I would suggest you should add product name or any other field in vendorProduct by which you want to search or filter

    Downside of current approach There are few downsides storing location coordinates in VendorProduct

    • You are saving redundant data in multiple collections and you will be maintaining them while add/edit/delete VendorProduct records
    • VendorProduct collection will contain the multifold no of records in comparison to Vendor collection, The geojson queries require more computational power than a basic search.
    • When your database scales, It is more efficient to run geojson query on a few thousand of record in Vendor instead of 100 of thousands records in VendorProduct