The glue makes the difference – Microservices in a web project
It also makes sense to work with modularized subsystems for web projects. However, errors during implementation can lead to sluggish modules.
(Image: erstellt mit Midjourney durch iX)
- Max Schröter
In modern software development, microservices in general and self-contained systems have established themselves as a special approach for the web. Turning modules into independent deployment artifacts promises technical advantages: independent deployments, targeted scalability and better maintainability.
However, technical separation also has disadvantages: For one thing, modules that are part of a system of systems cannot, by definition, exist independently of each other. On the other hand, systems that have been cut apart must be brought together again for users in order to create a user experience that is as pleasant and uniform as possible.
Distributed systems such as self-contained systems therefore talk to each other for various reasons. There are several approaches to enabling the necessary technical communication, and it is not trivial to find the right one for a specific use case.
Videos by heise
CAP of good hope
The CAP theorem (see Figure 1) states that distributed systems can only fulfill two of the three desirable properties of consistency, availability and partition tolerance.
- Consistency describes the guarantee that every node of the distributed system knows the same state of a data set.
- Availability means that every non-failing node must respond to all read and write accesses within a reasonable period of time.
- Partition Tolerance means that a non-failing node must continue to work despite the failure of one or more other nodes.
Since compliance with consistency and availability guarantees is irrelevant in the case of non-guaranteed reliability, especially for web projects, the popular description of the theorem used above is misleading in practice. The decision to be made ultimately consists of whether the system guarantees consistency or availability in a failure scenario.
When deciding on a particular integration mechanism between modules in a system, the CAP theorem should play a role, as it prevents an under-complex view of the world of distributed systems. Daily failures of network nodes are part of everyday life in IT departments and should not be ignored by software developers.
The effects of coupling
A strong coupling means that a change in one module necessitates further changes in others. What is already a problem in monolithic systems is even more evident in distributed and distributed systems: strong coupling requires high communication effort and leads to longer release cycles. A microservice architecture develops into a distributed monolith due to excessive coupling of the individual systems with each other, which combines the poor characteristics of both architectural approaches.
A module cannot exist completely independently as part of a system of systems, but the degree of coupling should be as small as possible. As the decision for an integration mechanism can have a significant influence on the degree, careful decision-making based on selected quality criteria is extremely important.
Figure 2 uses the example of shared code to show how the use of specific technology reduces the initial effort but increases the degree of coupling. Keeping the initial effort low is an example of a poorly chosen quality criterion, as this is a one-off effect and has a negative impact on medium to long-term development.
Runtime or development time?
There are many different approaches to integrating distributed systems with each other. Very abstractly, they can be divided into two categories: While integration at development time in relation to the CAP theorem is a clear decision in favor of the availability feature, integration at runtime requires further differentiation.
Integration at development time usually takes place via shared code. Either libraries are made available with a package manager or version control systems are used. This resolves dependencies before a deployment artifact of the integrating system goes into production – at the latest during the build process.
As the integration at development time is a clear decision in favor of the availability feature, the system does not guarantee consistency across the integrated content; it is updated at the earliest with the next delivery to production.
A typical use case for this type of integration is the use of a cross-system pattern library that provides the subsystems with UI components. This leads to a uniform user experience across all subsystems and reduces the workload for the development teams. Consistency is high here, as different color tones or the positioning of a button have little influence on the functionality of the software.
Mechanisms for runtime integration can be divided into two further categories: synchronous and asynchronous. Both have one feature in common: they only resolve dependencies during operation in a runtime environment. This results in a slightly higher degree of coupling compared to the degree of coupling that occurs during integration at development time. This is because changes to the interface of one system can affect the other system without this being recognized during the development or deployment process.
If a system is integrated asynchronously at runtime, this happens independently of user interactions. Triggers can be, for example, notifications about events in other systems or scheduled time intervals. After triggering, the integrating module accesses data from the module to be integrated and translates it into a model that is tailored to its needs in terms of functional and cross-functional requirements.
The decision in favor of asynchronous runtime integration is a decision in favor of the availability feature. Eventual consistency is accepted for high availability and – in contrast to synchronous runtime integration – low runtime coupling. This means that consistency is not immediately available, but the promise of a consistent state across modules at some point in the future is. Depending on the situation, this can be the case within milliseconds, minutes or hours.
In contrast to its asynchronous sister, synchronous runtime integration is often linked to the user interaction time. Every interaction of a user with the system represents a trigger: Be it loading a website or clicking on a hyperlink. Once the trigger has occurred, the system loads content from another system at the front end, for example, or even sends a whole series of logically consecutive requests to other systems. A decision for synchronous runtime integration is a decision for consistency because every request influences availability due to the behavior of the modules to be integrated. The impact on the runtime coupling between the modules is higher and can range from marginal to dramatic, depending on the specific integration mechanism.
(Image:Â iX)
This article can also be found in the iX/Developer special issue, which is aimed at software architects. In addition to the classic architecture content on methods and patterns, there are articles on socio-technical systems, quality assurance and architecture and society. Domain-driven design is also a topic, as are team topologies and security.
We have been able to attract well-known experts as authors, who pass on their knowledge in many exciting articles – such as this one – for both architecture beginners and specialists.