6

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.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
ricksmt
  • 163
  • 6

3 Answers3

5

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.

Price Jones
  • 635
  • 4
  • 8
  • I think this is my answer. @Darien mentioned adapters, and I'm thinking I'll need to research that topic to understand how the full solution works. If you want to elaborate on that in part II of your question, that'd make me happy. – ricksmt May 06 '16 at 18:33
  • It would be difficult for me to elaborate on the specific use of adapters without more knowledge of your design. ...I think you might be interested in them because you see the possibility of adapting your monolithic objects to the finer tuned interfaces of a divided domain model. If that is what you are thinking, then you would still want to consider whether it is wise to keep all your monolith logic monolithic and try to adapt it to different domains, rather than breaking it up. It is a decision only you can make. – Price Jones May 06 '16 at 19:15
  • It would be for adapting between domain library interfaces. Perhaps facade is what I really mean/want. I'd hate for that to be a burden on the end application when I'm anticipating the usage. – ricksmt May 06 '16 at 19:30
2

I think you want dependency inversion, where "higher level" code (like the Domain) defines interfaces that it will work against, and then demands that whoever uses it will somehow supply suitable implementations.

Metaphorically, you're hiring a prima-donna celebrity: They'll perform, but they stipulate that at the airport they expect to be greeted by a black limo stocked with mint-chocolates, bottled-water, and a puppy. You probably won't find any limo-service, candy-store, pet-store, etc. that does all of that, but you can certainly glue them together. (Just don't let the puppy have any chocolate.)

Back in the real world, I currently have a project that works like this:

  1. Domain Library
    • Domain logic (duh)
    • Supplies interfaces like FooRepository or BarSerializer or BulkDataStorer.
    • Is coded against those interfaces, expects that whoever uses it will inject concrete implementations. (In unit-tests, you supply your own.)
  2. Independent supporting libraries, e.g.:
    • A third-party ORM tool
    • A stand-alone client library for an internal webservice
    • Libraries for web-tier or MVC stuff
  3. The Application:
    • Brings everything together
    • Maybe has a Dependency Injection framework
    • Provides concrete adapters and facades to bridge the gap between the domain's interfaces and the concrete classes which could get the job done.
    • Example: A very thin implementation of FooRepository that leverages the third-party ORM.

Now, if anything in #3 becomes too big, you have the option of splitting it off into its own library, but before I'd do "glue libraries" I'd take a long hard look at whether they're actually context-specific for one application and how reusable they'd really be.

Darien
  • 3,453
  • 2
  • 19
  • 18
0

One of the advantage of microservices is that each microservice can evolve relatively independently from the other, due to a looser coupling.

Keeping the monolith library and reusing it in all the microservices would not fit into the logic, because you'd have many more dependencies that you'd ought to have. The only advantage that would gain from such a pseudo-microservice approach would be an easier distribution of your services, so perhaps a better scalability.

Another important point to consider is that each microservice should be responsible for its own data. Having lots of microservices using a single monolith database would somehow miss the point. This suggest that what is domain data access for one microservice, should be domain service request for another one.

The glue library for the interfaces is a first step to decouple a little bit more the services. However I think it would still avoid independent evolution of each microservices. I'd rather opt for this alternative:

  • Domain-driven interface libraries
  • Implementation of microservices, segmented by domain (not necessarily in a library), but using the corresponding interface library.
  • Domain-driven service consumption libraries depending on the respective interface libraries (distinct library for each domain/microservice)
  • Applications using microservices would reuse these service consumption libraries, but only the needed one.

The advantage of this approach compared to the glue library, is that:

  • dependencies between microservices are explicit (use of consuming libraries) and reduced to the strict minimum.
  • dependency between consumers and service is isolated in the interface library.
Christophe
  • 74,672
  • 10
  • 115
  • 187
  • I probably shouldn't have led with microservices because what I'm really asking about is the libraries. You're pro domain-driven libraries, but how do you handle the interactions between the domains? Isn't there a circular dependency? – ricksmt May 06 '16 at 18:28