In March 2020, two of the biggest telecom companies active in Italy, WIND and TRE merged into WINDTRE. As a result, the new company needed to be reorganized on several fronts, including in relation to software.
The original companies used different technologies, approaches and legacy systems that needed to be integrated within a more complex single system.
The additional need for a redefinition of the entire software architecture aimed to provide a new system that could improve ease of maintainability and allow for robust system integration in the short and medium term.
The company therefore transitioned from several software systems based on monolithic architectures to a new system based on microservices. The aim was to provide a unified user interface with a couple of ambitious goals:
First, WINDTRE needed to provide users with a unified service despite continuing to use different legacy technologies ‘under the hood’. That meant reusing both companies’ original software code bases. Second, the new system needed to support a progressive integration of new unifying technologies that would end up replacing the old systems to form a unique software suite.
To address these challenges, the WINDTRE team leveraged an interesting architectural paradigm that fit perfectly with the need for a modular, microservices-based approach. This paradigm – hexagonal architecture – allows solid, modern, and effective systems to be built at the same time as merging heterogeneous technologies and legacy systems.
For those who may be interested, WINDTRE is on the lookout for experienced JAVA 8, GO, JAVASCRIPT, NODE.JS or REACT.JS developers. Potential candidates should accept their Software Developer Coding Challenge. The winner will be further evaluated by the company, and will also receive a prize of €500 worth of Amazon vouchers.
Monolithic applications vs. microservices
Before going into detail about the work WINDTRE has done, it is worth clarifying the differences between monolithic and microservices-based applications.
Monolithic applications represent the traditional way of building software. Applications are divided into several modules, such as a database, a front-end and a back-end. The latter is usually the foundation of the application, in which data and visualizations represent the communication aspect and most of the core business logic is implemented. This monolith is a single logical executable that governs the whole application’s logic.
Monolithic applications have the advantage of having relatively simple structures (given the low number of modules), but present several disadvantages. For instance, altering any part of the system creates a need to re-build and deploy the whole application, irrespective of the size of the changes.
This has implications for the development cycle, where new releases need to be scheduled according to a (usually) rigid schedule. Reuse of code is also often limited across monolithic applications.
Other drawbacks include low scalability and several constraints relating to the software stack used to build the application.
Luckily, we can now solve many – if not all – of these issues by relying on microservices. A microservice is a single module that implements specific capabilities; these are also made available to other modules via Application Programming Interfaces (APIs). Each microservice usually encapsulates a particular business capability and might use specific technologies. These are completely hidden from the other modules by the only available interface, which is represented by the APIs.
Microservices offer several advantages: modular by nature, they are consequently good for high scalability and for code reuse. Introducing a change to a single microservice does not require restarting an entire system: the restart can be limited to a single module, allowing a more agile management of the development cycle.
However, there are some drawbacks to this model. Microservices orchestration is one of them. Many microservices demand the implementation of specific logics that can properly govern integration. Additionally, communication among microservices may introduce latency, which can be a problem in some contexts.
Solving microservices integration may be not straightforward. This is especially true when demand for such a solution arises from a process connected to transitioning from monolith to microservices. The use of a specific architecture to orchestrate microservices will significantly ease this process.
Hexagonal Architecture
As previously mentioned, WINDTRE’s integration needs demanded the combination of different technologies, including different data sources and management tools. In the future, legacy systems would need to be replaced without affecting the UX.
To implement the required logic, the WINDTRE team opted for hexagonal architecture. As this model suggests, they used it to create loosely-coupled application components, interconnected through adapters.
This produced a scheme in which the core business logic is protected from external interactions through specific adapters, thereby avoiding direct interaction.
Implementing this basic concept involves creating one set of microservices for the business logic, and another set for the adapters, orchestrating each hexagonal microservices subset as a single logic unit.
One alternative is to encapsulate the business logic and adapters in a single microservice, so that the logic unit matches the service. Here, it is important to ensure that the ‘inside’ (represented in the previous figure) remains independent of the ‘outside’, so that any replacement of the core logic is still workable.
The following image shows how logic units can communicate with each other by implementing the concepts described above.
Advantages of hexagonal architecture
The WINDTRE team realised that an hexagonal architecture offered two major advantages.
First, the possibility of isolating the core business logic from the interface (adapters), allowing easy replacement of modules without impact on the system as a whole.
Considering the initial requirements of their heterogeneous systems, and the necessity of merging them, this operation perfectly matched the demands arising from a corporate merger.
Another significant advantage of such an architectural choice is testing. Since each logic unit is independent, designing a set of tests for a single hexagon becomes easy, whether for the adapters or the core features. Case testing is crucial for effective troubleshooting and debugging of microservices.
Lambada: putting hexagonal architecture into practice
Following their analysis, WINDTRE has subsequently used hexagonal architecture for microservices in many projects, including ‘Lambada’. This amusingly-named endeavour required implementation of what is described above with an additional lambda integration.
Basically, Lambada is a Java Spring container that acts as an orchestrator. It provides several micro-functionalities, implemented as Lambdas. In addition, Lambada makes it possible to implement more complex features as separate microservices, which are queried via HTTP when necessary. Such microservices are implemented singly as hexagons, containing a core business logic that communicates only with the adapters, which in turn provide the interface for other services, including the Lambada orchestrator.
Lambada is particularly interesting because it reduces system complexity by decreasing the number of microservices to just those that are strictly necessary, thus reducing the number of complex features to be implemented as Lambdas. This positively impacts maintainability, since it reduces troubleshooting quite significantly by limiting the number of independent services to be tested and monitored.
Another advantage is the ability to release and deploy specific modules (or microservices) without having to stop the orchestrator, thus offering an agile approach to the development cycle.
Conclusions
Transitioning from a monolithic architecture to microservices requires splitting several functionalities into logic-separated modules, and encapsulating those modules in microservices. As shown above, micro-functionalities allow this to be achieved through lambda functions and a tailor-made solution such as Lambada.
In this case, the hexagonal architecture proved particularly effective in separating the business logic represented by legacy technologies (to be replaced in the near future) from the communication interface.
This clever approach provided flexibility, modularity and the chance to replace any module with a newer one while keeping the application running.
The WINDTRE case study offers an incredible insight into how a monolith-to-microservices transition is feasible even when combined with a complex merger if those responsible are open to investigating and exploring creative solutions.
We hope you have enjoyed this article. If you want to get in touch with WINDTRE and are an experienced software developer, have a go at their Software Developer Coding Challenge. WINDTRE is hiring; this coding challenge is an opportunity for you to demonstrate your skills and also win a prize of €500 worth of Amazon vouchers.