5

I am reading the architecture guide from the .net core project. They state:

The integration events can be defined at the application level of each microservice, so they are decoupled from other microservices, in a way comparable to how ViewModels are defined in the server and client. What is not recommended is sharing a common integration events library across multiple microservices; doing that would be coupling those microservices with a single event definition data library. You do not want to do that for the same reasons that you do not want to share a common domain model across multiple microservices: microservices must be completely autonomous.

There are only a few kinds of libraries you should share across microservices. One is libraries that are final application blocks, like the Event Bus client API, as in eShopOnContainers. Another is libraries that constitute tools that could also be shared as NuGet components, like JSON serializers.

There is a reference implementation, the eShopOnContainers repo on Github. Digging around a little bit, I found that they duplicated the messages in both services. Example: the OrderPaymentSucceededIntegrationEvent appears in the publishing payment service as well as in the subscribing order service.

My feelings vary on this approach. Sure, it is decoupled in the sense of no compile time dependency. But any change of the message might break the application at runtime, since the compiler does not check the compatibility of the message sent matching the message received. Would it be illegal to publish a kind of "Contracts" assembly providing all the messages published by a micro service to be compile time bound by the subscriber? I'd rather think about such messages as "common knowledge", somehow like he base class library is common knowledge for all .net core programs.

2 Answers 2

5

One of the greatest promise of microservices is that you will have independently deployable services.

There is a lot forces which fights against this so you should make wise decisions when you introduce anything which is shared amongst different services (a static xml file, a library, a database, etc..)

Whenever you have shared library (for example that one, which contains the contract's objects) you should consider that whenever you make a change how does it affect the services (the provider and its consumers).

  • Do I have to deploy only the provider because my change is backward compatible?
  • Do I have to deploy all of my consumers first and finally the provider because my change is not backward compatible?
  • Is it acceptable that different consumer services may use different versions of the package?

The proposed solution (to have duplicate contract objects) emphasises the independency of the services. More precisely it tries to loosen the coupling between them.

A common and great practise is to use Consumer-Driven Contract Testing. The big idea behind this is that each consumer of the provider gives a separate contract to the provider. ("Hey look, this how I use you. You can change in any direction until you can guarantee me this contract").

So instead of testing the provider's API we are testing that from the consumption point of views. With that in mind it makes more sense to separate the contract objects and let the service and its consumers evolve slightly more independently.

There is a really great package, called Pact.Net which can help you write these kind of tests.

2
  • First, this is terrible. Integration events are valid across microservices. Which means that for any integration event change, you now have to go to all the services and update each one of them. This does not loosen the coupling in any way at all. Second, Pact is not what we are talking about here at all. Pact is schemas with a broker, this is infrastructure layer and implementation. This is not domain events which are higher abstraction. What should we be talking about is integration events as things related to domain and business language. Those can be static types or alike. Aug 9, 2023 at 17:34
  • @bodhihammer Feel free to leave a post where you can detail your perspective. Aug 9, 2023 at 18:08
0

I do not have a good answer, at least not a very satisfactory one. However, at its root, the recommendation from .NET site conflates physical boundaries (micro-services) with logical boundaries (business capabilities). This is a non starter. Logical boundary can consist of many independently deployable physical units. In such case, micro-services can communicate using domain events which are valid inside your bounded context.

There is no real way to eliminate coupling in the case of integration events, since contexts must be coupled by something, for example, a contract. In DDD there is no way to eliminate coupling but only ways to minimise it.

It is a fallacy that a micro-service can ever be autonomous. If you slice a monolith into five micro-services then those services immediately acquire the need to communicate, and are bound to each other in some way. Coupling still very much exists, but we are interested in what are the services coupled to. If they are coupled by a common interface (such as integration event) it might loosen the coupling, but never eliminate it.

Another piece of advice is given about sharing only infrastructure libraries (i.e. EventBus). This ignores a very broad and lively concept of Shared Kernels in DDD. Advice like this is a "hardcore" micro-service approach, which might conflict with the guidelines of DDD more often than it doesn't [1][2].

As for integration events, you really have two options here:

  • Shared integration events. This can work especially well if your project is a mono-repository which contains your entire bounded context. This means that entire vertical feature slices are delivered at once in a single code integration, which include multiple services. Otherwise, not so much, since you need to update library version in each discrete (micro)service, in each discrete repository.
  • Duplicated integration events. If this doesn't entail mono-repository, this becomes a very tedious task, similar to what it is when using shared events library. Here, every time the integration event changes, you need to do multiple service sweeps. You must update a bunch of code in every single one of the micro-services. Atop of that, it clearly doesn't remove any coupling at all. In fact, all services are coupled in the same way. It is now just harder to update.

Footnotes:

  1. https://codeopinion.com/microservices-gets-it-wrong-defining-service-boundaries/
  2. https://medium.com/@vladikk.com/bounded-contexts-are-not-microservices-ead44b8d6e35
4
  • The coupling we are targeting is between the publisher and consumer(s) of the event; the event is the solution not the problem. The autonomy of a microservice is from other microservices: can I deploy my microservice whenever I want and it won't affect other services? this is a prerequisite to any hopes of CI/CD. A microservice's autonomy has nothing to do with whether it depends on an event or not: otherwise, 100% autonomy would equate to software that does nothing since it depends on nothing.
    – Shai Cohen
    Aug 9, 2023 at 23:35
  • the two footnotes you mention discuss how using bounded contexts can guide you in determining better service boundaries. I'm not sure they apply here.
    – Shai Cohen
    Aug 9, 2023 at 23:39
  • @ShaiCohen "The autonomy of a microservice is from other microservices: can I deploy my micro-service whenever I want and it won't affect other services?" You are not describing autonomy here in the context of coupling but independent deployability which is a physical characteristic. It is is not related to the coupling in the context of module or service coupling (logical). DDD does not talk about independent deployability since it is not about physical-infrastructure characteristics. If you have event duplication across micro-services you have so-called Stamp Coupling but coupling it is. Aug 10, 2023 at 9:00
  • @ShaiCohen "the two footnotes you mention discuss how using bounded contexts can guide you in determining better service boundaries. I'm not sure they apply here" Of course it applies here. Did you read it? First link states that the coupling should be considered from logical perspective, not physical perspective. Micro-services and DDD talk about different kinds of things which aren't necessarily related to each other and we should not conflate them into one. But the .NET article conflates these two. Vlad Khononov talks about this topic in the article but also in some of his DDD talks. Aug 10, 2023 at 9:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.