Modern coupling is now traveling in our queues
We’ve all seen systems that were tightly coupled, and maybe you are working in that kind of system right now. It seems that keeping codebase without high coupling its hard. Because most of the systems ends as not maintainable mess, we just need some time to make it like that. And I want to be clear, I’m not trying blaming any of you because I’ve done the same. But after 60 years from when coupling was invented, I think it’s time to stop chasing the newest technologies and focus on what is causing those problems in our current systems.
“If you can’t build a well-structured monolith, what makes you think that microservices are the answer?”
— Simon Brown
Nowadays, when microservices are like “must have” in many projects, we’ve taken that coupling from our monolith and put it in some kind of queue. Now we have “independent” microservices because they are loosely coupled. We are so modern, so we have modern coupling right now. Every new feature must go through many microservices and changing existing solution breaks something somewhere else. This is making our system even worse because we’ve end up with a distributed monolith.
This is real-world diagram of one application that I had “pleasure” to analyze for how services communicate with each other and try to create a plan for make the system more maintainable. All above communication is asynchronous. This is an example of coupling traveling in queues.
What is exactly wrong in that?
The System above has few problems:
- Services are not business oriented
- They don’t have correct boundaries, so it forces developers to share data between them
- They are not autonomous, they depend on many things in this system.
Sharing volatile data between services causing problems. It provides a high risk of creating logical coupling. With that, we are forced to change something in many places when requirements have changed. There is also a high possibility of breaking something unintentionally when the system become more complex.
How to avoid that?
In my opinion, the most important aspect when designing modules/services in your system is to make sure that each of the service has technical authority on specific business capability and shares nothing excepts thin contracts.
If a service need to make a some business decision they should have their data to make that decision. What it can share to the outside is information that something important in their domain just happened, but that information should be thin as possible and most of the time contain only some kind of identifier. Service should protect their boundary. If you feel that you need data from another service then possibly your service boundaries are wrong.
How to make it wrong easily
I will give you a simple example how to make boundaries incorrect effortlessly. Let’s say that we have a car rental domain with “modular” monolith app. Then we get the following requirement:
- Price of the car should be calculated based on customer status
So, a typical division of services might look like this:
With division like above the process of car reservation could be designed in a few ways. Let’s consider one of them.
In step one we are calling reservation service. When handling that request we are gathering price from cars catalog and status from customer service (step 2,3). At the end we are publishing an event or command (step 4) to the payment service to invoke a charging logic. Payment service is getting credit card info from customer service (step 5) then is calculating the price and charge the client (step 6).
No matter how we’re going to model this process, with modules like this we cannot achieve authority and autonomy. The amount of logical coupling is very high. Let’s imagine that we have to delete one of the statuses of a customer or add a new one. That kind of change will spread through most of our services, and possibly, we’re going to introduce some bug in this way.
The first justifies the second, the second justifies the third…
And guess what, if we’re done that kind of modeling once, the second attempt will be justified by the previous one etc. After 2 years of development we will have a system like this:
How it happened?
Well, we developers like to focus on technologies. But in this case, technology will not help. No matter is we are using kafka, docker, kubernetes, cloud, whatever, this will not solve our problem. Possibly, it will make it even worse. Wrong service boundaries will not become better. The problem is that we are not spending much time on analysis. That causing our system to be an unmaintainable mess in the long term. We were taught that kind of entity modeling. We were told that car has a price and customer has a status because this how it was stated in requirements. In our minds this is very logical. But we have to be careful, and filter those kinds of information. Be also aware who is creating those requirements. E.g. from user perspective our system looks very different from what it really is sits underneath. Amazon is a just regular online shop :).
Context is king
We were taught modeling one big entities, whose has specific place in our domains. But this is wrong. Customer in example above has completely different meaning in different use cases. From user experience perspective maybe a customer has a status, but when it comes to calculating price based on that status, we are really worry about the rest of information that the customer entity has? Going further, in context of calculating a price is it still a customer or maybe a payer?
Sharing volatile data between boundaries would end up terribly. This type of coupling is bad because you will not even know that someone is deciding based on your internal state, and you cannot possibly change it without breaking someone else logic. You are not autonomous anymore. It would look good at first glance, but after a some time you will end up with an unmaintainable mess like one that I show you above.
So protect your boundaries, and not introduce modern coupling by putting your internal data to some queue. Be careful what data you are sharing between services. This would not solve your problem, it would make it even worse. Stay tuned, In the next posts we are going to focus how to discover proper service boundaries.