Sharing Databases Within Bounded Contexts

A contentious topic in distributed systems is if/when/how to share databases. Some developers/architects will tell you never to do it. Others, like me, will cautiously mention a few acceptable scenarios.

“Components [within a bounded context] work together very closely; therefore, having shared dependencies increases cohesion… without necessarily causing problems”

Patterns, Principles and Practices of Domain-Driven Design

Who is right? What is the one true way?

In this blog post I won’t be wasting time on such futility. Instead I’ll be elaborating on my belief that sharing databases, and other dependencies, within bounded contexts can be sensible and savvy.

Why we Avoid Sharing Databases

Traditionally teams built monolithic services around a single (usually SQL) database. They then realised that they needed to scale so started to distribute their applications.

Each component of the distributed system, representing a different business capability, then wanted to drive in different directions and scale at different rates in line with organisational priorities. With the central coupling point of the database, much pain and suffering ensued.

A change to the shared database for one team could cause breaking changes for another. Changes to the fragile database thus were usually highly coordinated and political — consuming a lot of time and resources.

I shudder at the flashbacks I get from the last SOA I worked on that had a universally-loathed shared database. The anxious look on DBA faces when we campaigned for multiple databases was priceless, though.

The shared database is a velocity bottleneck. Experienced practitioners like Udi Dahan have been warning us of this problem for years. This is nothing new.

Sharing Within Bounded Contexts

In Patterns, Principles and Practices of Domain-Driven Design, myself and Scott Millet expressed our belief in the importance of isolating bounded contexts.

“To retain the integrity of your bounded contexts and ensure they are autonomous, the most widely used approach is a shared-nothing architecture where each bounded context has its own codebase, datastore and team of developers”

Patterns, Principles and Practices of Domain-Driven Design

Based on experience however, we also accept that sharing databases within a bounded context is often a good compromise because services within a bounded context are highly cohesive and change for similar reasons — there is limited scope for the high coupling to spiral out of control.

Additionally we advise that each bounded context should belong to one team. Therefore, everyone in a team has knowledge of the services and databases making all of the coupling points explicit and clearly understood.

Not All Sharing is Bad

Just because sharing a database in scenario A is bad, does not mean that sharing a database in any scenario is bad. In fact, that is the narrow-minded thinking that leads to people mindlessly following best practices that are sub-optimal for their scenario

Bounded contexts are a way to decompose large problem domains into smaller, cohesive chunks, conferring improved comprehensibility and modularity. Importantly, bounded contexts are not arbritrary, they are aligned with the problem domain which ensures cohesion.

“Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas”

Eric Evans, Author of Domain-Driven Design

As a short example, bounded contexts are often aligned with business capabilities — sales, marketing, etc (for a deeper discussion refer to PPPDDD).

The cohesiveness of a bounded context is the reason we, and many others, encourage a team-per-bounded context. Each team has rich knowledge of their bounded context — the related concepts it encapsulates and the technical components it owns.

You really can have the opportunity cost advantages of tight coupling, without the fragility side-effects if you militantly enforce your context boundaries. This doesn’t mean you should tightly couple everything though.

An Example

Asynchronous messaging, CQRS & event sourcing, REST… glamorous integration strategies that have served me well. However, sometimes you just need a basic solution for a basic problem. And database integration has it’s uses.

“Database integration can be quick to set up but isn’t recommended for use in high-scalability environments “

Patterns, Principles and Practices of Domain-Driven Design

Consider the following scenario. You’ve got a website that captures a fairly large quantity of sensitive user data — 20 page medical questionnaires. You pump the questionnaires into a backend API that stores them in the database.

Periodically another service (the publisher) will poll the database for questionnaires and export them to a highly-secure, central medical information service.

This system has three services that are highly cohesive. A website (the UI), a data service (the back end API), and the publisher that supplies the information to other services that need the information. A basic solution to a basic problem.

Each of the three services requires the same conceptual model and they work together to carry out a single business case that provides a single business capability. This is clearly a bounded context.

Queues, Coupling and Controversy

One of those best practices that get’s thrown around is the one about each microservice having it’s own database. Clearly good advice that will prevent organisation-wide spider webs. Speculative over-engineering for a basic CRUD situation like the one we have here.

One architectural alternative is to have the database encapsulated by the ‘microservice’ (though really it’s just a HTTP API on top of a database). The polling consumer would then query that.

Possibly good advice, but with several costs:

  • You have to build the database encapsulation layer
  • The consumer is limited to what the encapsulation layer can do — none of those advanced database features are available
  • Any time you want to change database access patterns you have to update the consumer and the encapsulating microservice — and when you’re using the database as queue that’s a significant cost (more on that next)

Think about the benefits of the encapsulation here — you can change the underlying database or schema and the consumer won’t have to care. From experience, changing databases doesn’t happen a lot. And for the sake of one small coupling, the effort to create a layer of decoupling feels a lot like gold plateing and wasteful ignorance of YAGNI.

Admittedly there’s a risk that other services may want the data being stored. If you have that problem a HTTP encapsulation is more appealing however. But in our situation data storage is transient — it is just a queue. No other service will be needing that data.

Just because two components are talking over HTTP and not integrating via the database does not mean they aren’t coupled. It just means you’ve gone to more effort to couple them.

//platform.twitter.com/widgets.js

Let’s address the other design smell — why use the database as a queue, as Mike Hadlow

was bringing into question years ago. Again, the answer is simplicity. If you’re already using a database, and don’t have enough expertise in your team with queues, especially operationally, integrating via the database is a low-scale, low-complexity solution that is often (not always) good enough.

Conclusion

Integrating via the database hits you like a strong triple-espresso with it’s fiending time to market advantages. As you relish the vibrant high, though, it can devastatingly low-blow your organisation like an energy-sapping caffeine withdrawal.

But that doesn’t mean you should stop drinking coffee. It just means you have to sensibly moderate your intake and exploit its benefits for maximum effect. Exactly how I recommend you harness the unglamorous shared database integration strategy.

Isolate coupling within domain-aligned bounded contexts and judiciously apply the integration strategy that gives your employer the biggest win for their investment. Be that event sourcing, messaging, RPC or on special occasions it might just be database integration.

“Breathing is not a best practice under water”

--

--

Nick Tune
Strategy, Architecture, Continuous Delivery, and DDD

Principal Consultant @ Empathy Software and author of Architecture Modernization (Manning)