Microservice: Shared database


If you are familiar with microservice architecture, you might heard the pattern “Database per service”. There are popular articles written by Chris Richardson, AWS and Microsoft on this topic. These articles suggest that microservices should not share data via database access, but only via inter-microservice RPC/API calls. This pattern ensures that the respective microservice owner owns the data and the schema, so that it can evolve independently. The microservices communicate only via established interface of the API calls.

I have worked in several companies and I’ve seen both shared database and separate database per service played well. Since there are many references on database per service available only, I’m going to instead explain my experience on working with shared database in a microservice architecture.

Software engineering is always about making trade off and not being dogmatic with the tech decisions.

Shared database service do it right

I used to work in a startup company (now no longer active) that uses microservice architecture in a monorepo. The entire team that I worked with is just around 10 engineers. In my opinion, choosing shared database was the right call at the time.

For context, although we’re working in a relatively small team, we build and operate around 10 microservices, each of them listens to events and process them asynchronously. There were a lot of message passing between the microservices but all of them access the same databases at the same time.

The data access layer or the ORM was shared across all of the microservices.

One way you could achieve this is by using monorepo setup where the data access layer is made as a shared library that can be imported by other microservices. In context of Go project setup, it would look something like below:

.
├── data_access_layer
├── microservice1
├── microservice2
└── go.work

Alternatively, you could also be a package published to package registry which is then imported by all microservices in the system. However, I personally like the monorepo setup better because each time you make changes, you can test all the microservices in the system to work as expected and then you can deploy confidently.

Nevertheless, this data access layer is used a common way to serialize & deserialize data in the data store of your choice. You can use tools like sqlc, jet, gorm to auto-generate the code that translate the table schema into the object or you can hand write your data model. This layer is database-agnostic, which means you could write the data access layer for Mongo, Cassandra or any other databases.

One important distinction you’d need to make is, each time the data model is changed, all microservices has to redeployed with the latest data access layer. This ensures that all services are referencing to the latest data schema when interacting with the database.

Another important detail is, when you are making schema change, you have to always make sure that they are always backward compatible. This is also applies to Database per service approach where the API has to be always backward compatible so that you don’t break their existing behaviour.

If you are using SQL databases, the schema & the migrations have to be managed centrally.

Advantages of using shared database for microservice architecture are:

There are disadvantages of this approach are:

In my opinion, this pattern works well when you are working in a small team, and you prefer microservice instead of monolith architecture.

A big company that adopt this pattern is Datadog until then they finally migrated to database per service pattern.

The wrong way of doing shared database service

In my opinion, if you are repeatedly declaring the table schema in many places, then you are doing it wrong. If the data model is not propagated immediately to the other microservice, you could face a lot of issues in the future.

For example, when you repeatedly declare the same ORM in all repositories, you’ll likely lost track which one is the latest schema. Your system is now has become so brittle. Some microservice could be accessing a field/column that no longer exists/deprecated. Some people/team might forget to update the data model in some repos after many months.

Compare this to Database per service pattern

It’s commonly stated that the Database per service is the best practice when architecting a microservice pattern. However, it comes with a price.

You can read about its advantages from the articles I shared earlier. However, the common trade offs when working with database per service pattern are:

Conclusion

Building system for 10 engineers is definitely not the same buidling for 1000 engineers. If you’re confused or conflate between the 2 situations, we’d probably end up in a bad place.