Up to here, we covered the main characteristics of microservices. Short development time, cohesion, and coupling. Coupling and cohesion are strongly related and, at some level at least, are arguably the same in that both concepts describe the relationship between things. Cohesion applies to the relationship between things inside a boundary (a microservice in our context), whereas coupling describes the relationship between things across a boundary.
We want to reduce coupling because it is bad. Now let's focus on coupling and see some of the available types of coupling.
Domain coupling
Domain coupling describes a situation in which one microservice needs to interact with another microservice because the first microservice needs to make use of the functionality that the other microservice provides. An example would be order processing communicates with payment and warehouse. So there is a coupling between order processing and payment and warehouse. As a general rule, domain coupling is considered to be a loose form of coupling, although even here we can hit problems. A microservice that needs to talk to lots of downstream microservices might point to a situation in which too much logic has been centralized. Domain coupling can also become problematic as more complex sets of data are sent between services—this can often point to the more problematic forms of coupling we’ll explore shortly.
Temporal Coupling
Temporal coupling refers to a situation in which one microservice needs another microservice to do something at the same time for the operation to complete. Order Processor is making a synchronous HTTP call to the Warehouse service, The Warehouse needs to be up and available at the same time the call is made. If the warehouse service is down, then the operation fails. One of the ways to avoid temporal coupling is to use some form of asynchronous communication, such as a message broker.
Pass-Through Coupling
Pass-through coupling describes a situation in which one microservice passes data to another microservice purely because the data is needed by some other microservice further downstream. An example would be as follows: we have an Order Processor, which is sending a request to Warehouse to prepare an order for dispatch. As part of the request payload, we send along a Shipping Manifest. This Shipping Manifest contains not only the address of the customer but also the shipping type. The Warehouse just passes this manifest on to the downstream Shipping microservice.
A big problem is that a change to the required data downstream can cause a more significant upstream change.
To solve that we can directly talk to the downstream and bypass the intermediary. For example order processing talks directly to shipping micorservice. This creates more complexity since we should be sure that both warehouse and shipping do their job completely. This makes order processing heavier with more complex logic.
Another way is to fix this issue would be to prepare shipping manifest inside the warehouse microservice. This way, order processing does not need to know if there's a change in shipping. Warehouse will handle this change.
While this will help protect the Warehouse microservice from some changes to Shipping, there are some things that would still require all parties to change. Let’s consider the idea that we want to start shipping internationally. As part of this, the Shipping service needs a Customs Declaration to be included in the Shipping Manifest. If this is an optional parameter, then we could deploy a new version of the Shipping microservice without issue. If this is a required parameter, however, then Warehouse would need to create one. It might be able to do this with existing information that it has (or is given), or it might require that additional information be passed to it by the Order Processor.
Although in this case we haven’t eliminated the need for changes to be made across all three microservices, we have been given much more power over when and how these changes could be made. If we had the tight (pass-through) coupling of the initial example, adding this new Customs Declaration might require a lockstep rollout of all three microservices. At least by hiding this detail we could much more easily phase deployment.
One final way would be to send data from order processing to shipping via the warehouse, without the need for data processing by the warehouse.
Common Coupling
When we have two microservices using the same database, filesystem, or memory we have a common coupling. The main issue with common coupling is that changes to the structure of the data can impact multiple microservices at once.
For example, imagine our order processing and warehouse use the same database for countries. Any backward incompatible change in the database causes issues and both microservices should change.
Now imagine both order processing and warehouse updating the order status. If there is a finite state machine to manage states then how we should be sure that both microservices work as intended? A possible solution would be to manage the status with another microservice, which we can call order. Warehouse or Order Processor can send status update requests to the Order service. Here, the Order microservice is the source of truth for any given order. it is the job of the Order service to manage the acceptable state transitions associated with an order aggregate. As such, if the Order service received a request from Order Processor to move a status straight from PLACED to COMPLETED, it is free to reject that request if that is an invalid change. Multiple microservices making use of the same filesystem or database could overload that shared resource, potentially causing significant problems if the shared resource becomes slow or even entirely unavailable. A shared database is especially prone to this problem, as multiple consumers can run arbitrary queries against the database itself, which in turn can have wildly different performance characteristics.
Content Coupling
an external service accessing another microservice’s database and changing it directly. The differences between content coupling and common coupling are subtle. In both cases, two or more microservices are reading and writing to the same set of data. With common coupling, you understand that you are making use of a shared, external dependency. You know it’s not under your control. With content coupling, the lines of ownership become less clear, and it becomes more difficult for developers to change a system. For example, order processing uses orders to manage states, meanwhile, the warehouse manages states directly in the database. In such a case we should hope that warehouse logic and order logic behave in the same way. One possible solution is that make the warehouse use the order microservice. In short, avoid content coupling.
Top comments (0)