Domain-Driven Design – Separating a Monolith into Domain-Driven Libraries Without Duplicating Interfaces

cdomain-driven-designlayerslibraries

I'm working on a project with web services, and I've been structuring things where:

I've recently been learning about micro services. In hindsight, I'd want to split the service in multiple services each covering a specific domain of the business. What should I have done with the library? I'm thinking I could:

  • Keep the monolithic library that all the services use
  • Split the library into multiple pieces (again, based on business domains)
    • Each library includes all the interfaces it interacts with (which means redundancy between the libraries)
    • Glue libraries that cover the gap or boundary between two domains

I don't like the monolithic library, but I can't decide how I would've handled splitting it up. I don't like idea of duplicating the model code, but I don't like the idea of glue libraries either—it seems unintuitive; not as simple as it should be.

How can you separate a monolith into domain-driven libraries without duplicating interfaces and still keep dependencies simple?

At present, I'm thinking:

  • A single glue library containing all the interfaces and business logic
  • Multiple domain-driven libraries containing implementation and data access
  • Services mostly translate to and from the glue

Is this the best compromise? I'm worried the glue library is just a mini-monolith.

Best Answer

It seems to me your question is split into two parts. The first revolves around duplication of interfaces. The second on dependencies.

First, duplication of interfaces. You probably want to duplicate them in the case you are describing.

A good rule of thumb when breaking up a monolith is to keep your boundaries clearly defined. If you have two domains, it can be counter-intuitive to keep in mind that any given domain object needs to be modeled in a way which is unique to the domain.

For instance, if you have a customer interface, ICustomer, and you have two domains, Orders and Invoices, although both orders and invoices utilize something called a customer, it is often better to define the interface twice than it is to try to force a single interface (usually due to a misunderstanding of DRY). This is because a customer from the perspective of the Order domain is a very different beast than a customer from the Invoices domain. It might seem intuitive that both Orders and Invoices share the same ICustomer interface. But in truth, they probably do not. A customer in an Order domain is very different than a customer in an Invoice domain, and will change for very different reasons.

So if you want to split your project into domain-driven micro services, create libraries around each domain. Don't be so much concerned about how well you can re-use code (or other resources) across domains, but how easy it is for the code in one domain to be changed without breaking code in an unrelated domain.

Second, dependencies. This will mostly solve itself after you address the above issue. Keeping dependencies simple is a matter of keeping dependencies secluded to their proper domain, and then pushing the interfaces for more generalized dependencies down into your base common libraries.