Why microservices shouldn’t share a database

So you want to build a new micro service, huh?

Just spin up a new process, connect to the database and you’re off on your way! Easy. Six months later, our micro service is chugging along just fine. No problems, nothing changed. Let’s build another! Copy and paste, change a few things, and you’re off to the races.

What’s the problem?

The moment you deploy two micro services pointing to the same database, it’s over. You’ve no longer got micro services but instead a distributed monolith. The only way to safely deploy changes those services is to test all services which use that database. And once you start sharing databases, what are they, anyway?

Let’s walk through an example.

Our Catalog Service exposes an endpoint which returns a list of products with their names, descriptions, and associated prices. A product looks like this:

{
	_id: '5bd4f4a82389c1e445ec4c1d',
	name: 'Coffee',
	description: 'Starbucks Coffee 16oz Bag',
	price: 10
}

Then, we build an Inventory Service which, well, contains inventory. We connect to the same database and leverage the product collection.

{
	_id: '5bd4f4e72a2514e47f6ca10f',
	product: '5bd4f4a82389c1e445ec4c1d',
	inventory: 10
}

and, to print items for a purchase order, we do something along the lines of…

import mongoose from 'mongoose';
(async () => {
  const InventoryItem = mongoose.model('InventoryItem');
  const inventoryItems = await InventoryItem.find({
    inventory: 0,
  }).populate('product');
  const poItems = inventoryItems.map((item) => ({
    description: item.product.description, // required field!
    _id: item.product._id,
    quantity: 100,
  }));
  console.log(poItems);
})();

We’ve got a background job that pulls items with no inventory, and places an order for more! Everything’s fine. Until…

Somebody from product wants to change up the product page up a little bit, and wants to start using a short description as well as a long description. A developer opens up the product service, does a quick search, and nothing appears to use the field. Its a simple web app, and the one web client is ready to change to use the two new fields instead of the one description field. Product will now look like this:

{
    _id: '5bd4f4a82389c1e445ec4c1d',
    name: 'Coffee',
    description: 'Starbucks Coffee 16oz Bag', // legacy
    shortDescription: 'Starbucks Coffee 16oz Bag',
    longDescription: 'A dark and smoky roast grown high in the mountains of Mordor',
    price: 10,
}

The description field wont be deleted for backwards compatibility during the release, and it wont be maintained because going forward, it shouldn’t be used.

Releases are coordinated, all goes fine with the deploy, now what? Still no problem. Well, they start creating new products with no description field and long story short, our little purchase order process starts pumping out purchase orders with missing description fields! Downstream third parties are rejecting the purchase order because it’s missing fields, but we swallow the errors to prevent it from hurting other jobs so nobody finds out. The process silently fails and next thing you know, we start running out of things to sell!

This is the problem. At the heart of the micro service architecture is autonomy. When a developer opens a code repository to make a change, they shouldn’t need to fish through other code repositories to understand or test the impact change. One needs to be able to quickly assess the scope of the change they’re about to make, and sharing databases across services makes this impossible. Want to learn how to manage data in a microservice environment? Check out my other post: How to migrate data when building microservices.

Thank you for reading! To be the first notified when I publish a new article, sign up for my mailing list!

Follow me on medium for more articles or on GitHub to see my open source contributions!