A Product as a Document

We want to store each product with its own set of fields, so that a mug and a shirt do not have to share the same columns. Here is the blue mug written as a JSON object:

{
  "_id": "42",
  "shopId": "mugshop",
  "name": "Blue mug",
  "description": "Hand-thrown stoneware with a deep blue glaze. Holds 350 ml. Dishwasher safe.",
  "priceCents": 1800,
  "quantity": 5,
  "capacity": "350 ml",
  "glaze": "deep blue"
}

And here is the blue linen shirt from threadline:

{
  "_id": "7001",
  "shopId": "threadline",
  "name": "Blue linen shirt, medium",
  "description": "Relaxed fit in washed linen.",
  "priceCents": 5400,
  "quantity": 12,
  "size": "medium",
  "color": "blue",
  "fabric": "linen"
}

The first six fields are the same in both objects. They hold the details every product has: the identifier, the shop, the name, the description, the price, and the stock. The fields after that are the ones each merchant chose. The mug has a capacity and a glaze, and the shirt has a size, a color, and a fabric. So the two objects have different fields. Neither one has an empty field for an attribute that does not apply to it.

A database that stores records like these is called a document database. Each record is a document, an object with named fields in a format like JSON. Documents of the same kind are kept together in a collection. A collection is like a table, except that the database does not require its documents to have the same fields. On Shopend, we put the products of every shop in one collection, called products.

MongoDB is a widely used document database. We will use its terms and its syntax in this chapter. MongoDB stores documents in a binary form of JSON, but we will write them as JSON. Every document in a MongoDB collection has a field called _id, and no two documents in the same collection have the same _id. So _id does the job that the primary key does in a table.

Adding an attribute

Suppose threadline wants to tell shoppers how to wash its linen shirts. On Shopend, each size and color of the linen shirt is a separate product, so the shop has six linen shirt documents. The merchant adds a care attribute to each of them, and each of the six documents gets one more field, "care": "hand wash". We do not have to change the collection. The shop’s other products, and the products of every other shop, stay as they are.

Compare this to the design where each attribute had its own column. There, the same change means adding a column to the table that holds the products of every shop. That is a schema change we have to plan and run ourselves. The merchant cannot make it.

Reading a product

When a storefront sends a request for a product, the data access code reads one document from the products collection. In MongoDB’s syntax, the query for the blue mug looks like this:

db.products.findOne({ _id: "42", shopId: "mugshop" })

The part inside the braces is a filter. It matches a document whose _id is "42" and whose shopId is "mugshop". It includes the shop because of multi-tenancy: the lookup for a request to mugshop must find only products that belong to mugshop. The result is the whole document. It includes the product’s attributes.