This is a follow-up post to my earlier ramble which you can read here https://medium.com/it-dead-inside/domain-driven-design-in-the-era-of-icroservices-de2be01821ed

Photo by Chrissa Giannakoudi on Unsplash

Picking up where we left off

So last we spoke, we’d seen how DDD and microservices one could assume these two to be a happy lovin’ couple, until we got down to the brass tacks of implementation. That’s when things seemed less rosy.

Microservices, by their nature, should be

  • Self-contained (including their data, and hopefully data models)
  • Self-deployable (any one microservice should be deployable without needing to deploy any of it’s siblings in your product offering)
A Domain Model

With this in our brains, in DDD, we often see the Domain Model itself folded up into one or more common deployables, such as a Java JAR or a NuGet package.

Now, this could be one package, it could be several, but that’s not really the question we’re concerned with here (there are PLENTY of articles out there on how best to slice up your Domain Model into packages. I’ll leave that debate alone)

The issue were trying to solve is, if my microservices should be self-deployable in most scenarios, how does this common library of our Domain Model factor into this? Normally, if I were to change aspects of my Domain Model, I want all clients of it to be updated with these changes. This breaks our rule of a self-deployable microservice, for if there are 4 microservices in my product, then every time I update my Domain Model, I need to update all four of my microservices

(Note: This is not always true, btw. There could be minor changes / changes only specifically meant for microservice X which need to be deployed. However, we want to ensure, as often as possible, that our microservices stay in sync with the same version of our Domain Model.)

What are our options?

So how can we handle this? Let’s look at some possible ways we can deal with this.

1: Don’t use DDD

I said it before, and I’ll now reiterate in all caps:

IF YOU DON’T NEED THE COMPLEXITY OF DOMAIN-DRIVEN DESIGN, DON’T USE IT!

DDD is complex. The modeling of your application into well-crafted aggregates, properly-structured repositories and application services is a fair bit of work. The kind of business application which requires DDD should also be complex. If you are building a simple CRUD system, where it’s nothing but objects created, retrieved, updated, and deleted, then forget DDD. That simplifies your life a lot. There. Problem solved 🙂

If you really do need DDD, then read on.

2: Follow the old ways: Keep your Domain Model self-contained

It might make sense for your application to keep the Domain Model isolated into a common set of self-contained packages. This obviously constrains you in the microservice space, particularly if you want to keep them in sync with the Domain Model AND deploy them independently. However, if you do not make a lot of frequent changes to the Domain Model, or if you want to rollout all the Domain Model changes every time to all your microservices, this is a viable option.

How you can help to make this less awful:

  1. A robust blue/green deployment strategy, coupled with strong functional tests can help reduce the potential of your Domain Model changes from causing issues / outages. Blue/Green deployments mean you have a new version of your microservices running with the new Domain Model, against which you can run your functional tests. You are now responsible for ensuring your tests are robust enough to go from the old product to the new one.
  2. Backwards compatibility: Ensure that your Domain Model is capable of dealing in backwards compatibility, so that older Domain data can play nicely with your new Domain Model.
  3. Keep those Domain Model elements as atomic as possible: The more tangled-up your various Aggregates, Entities, Repositories, Domain Services are, the more complex the problem becomes of deploying these independently. The smaller / more atomic the Domain Model elements are, the easier this process will be.

3: Split your Domain Model per Microservice

This is an interesting solution to the problem, one I’ve seen in Microsoft’s reference architecture for their Containerised .NET architecture. The Domain Model is split into each of our microservices, so that the microservice itself is the Bounded Context to it’s own much smaller subdomain.

A self-contained microservice / Bounded Context

So now your microservice is it’s OWN self-contained Bounded Context within a larger Bounded Context. It has it’s own models, repositories, application services, factories, etc. Using communication protocols such as REST and a Service Bus for Event publishing / consumption, you now have a self-contained unit, and which can be deployed independently.

Now, this definitely has benefits from a microservice perspective. Your microservice is both self-contained in terms of data and data model, and it is also self-deployable. The drawback, of course, is that your Domain Model is now spread across your microservices. The trade-off is that now you must be much more diligent in making sure your team are being rigorous in the care and feeding of the Domain Model, so you don’t end up with misaligned behaviour or logic. While our single package for our Domain Model might be a pain to self-deployment of microservices, it is assuredly a nice neat way of keeping all your domain logic in one place.

Something you will also note in this design is that you are expected to allow each microservice to have a full stack of infrastructural capabilities, such as access to a data store, access to the service bus, to a REST HTTP client, and so on. Each of our microservices is slightly bigger in terms of code, and we end up with more code duplication in this model. Not unmanageable (if you’re careful), but still something worth bearing in mind.

How you can help to make this less awful:

  1. Keep those Domain Model elements as atomic as possible: This seems simpler in some ways with each microservice owning its own part of the Domain, but you still must be diligent. No one microservice should be doing more than one thing. The trick here is to make sure you are keeping that “one thing” very small. If your microservice is named the “Billing” or the “Accounting” service, you’re asking for trouble. That isn’t a microservice; that’s a full-blown Domain. Keep each microservice to a single task.
  2. Use those communication tools (wisely): So your Order service needs to get some product details from the Product service? REST. Your Shipping service needs to know when the Order is ready to ship? A nice OrderReadytoShipEvent fired on your service bus / event bus ensures a nice loose coupling. It’s important to remember that communications between microservices should NOT be chatty. If two or more microservices are chatting back and forth all the time, you might need to rethink those services. Perhaps some of the logic causing the chattiness belongs in one or the other microservices, or maybe you need to refactor the chatty behaviour out.
  3. Keep those contracts loose: I tend to NOT share contracts in any way. I expect that if a microservice calls this or that sibling service (either via REST or via Events / Commands), then each of them is responsible for it’s OWN copies of these models. Why? Well, if my Order service has a Product contract of the data which is found at https://ProductService/Products/{id}then it knows what it expects to retrieve. If the Product service changes its contract based on what it publishes later on, as long as the new contract doesn’t break the old one, then the Order service can still retrieve its data as expected. The Order service can be updated at a later time with the most recent version of this contract. Other, newer consumers of the Product service can take advantage of the newer contract without effecting my Order service. You could even use content negotiation to ensure the caller is getting the correct version expected.

In conclusion

When dealing in DDD and microservices, it’s not all sunshine and lollipops. We really need to take the time to design our system, based on our needs, on our application’s lifecycle, and on what works best for us and our dev teams.

Different approaches have their pros and cons, and I don’t think there is any one panacea to this marriage of DDD with microservices. It comes back to you, and what you expect of your application.

Hope this helps.