The HyVar project, as with most Horizon 2020 projects, has partners from several European countries. While this is good for enabling cooperation across borders and opening up for new international collaborations, geographically distributed projects come with several challenges. One of the key issues is pulling the R&D results from each partner into a cohesive deliverable where the whole is greater than the sum of its parts.
In HyVar, the partners represent both academia and industry. Travel budgets are finite and thus the opportunities to sit down together and make things work are limited. The main output is a hybrid variability toolchain, deployed on a scalable cloud infrastructure. Each partner is responsible for their piece of the toolchain puzzle, be it a DSVL cross compiler or hybrid product reconfiguration. It is a complex pipeline where each component is critical. When face-to-face time is scarce, how can we make sure that all the pieces fit together in the end? Moreover, we do not have one implementation partner but rather many. Different partners have different approaches to software engineering; this further complicates the task of final integration.
One of our first design choices was to think of the HyVar toolchain as a collection of microservices. Microservices is an implementation strategy where a software system is constructed as a collection of independent services, each of them easily deployable, cohesive and without any coupling with other services. All services are accessed through a lightweight, open communication protocol; in our case through RESTful web APIs and JSON messages. Microservices can be considered as a specialisation of the classic service-oriented architecture (SOA) paradigm.
Why is formulating the toolchain as a collection of microservices a useful strategy? First of all, it forced us to think modularly about the whole pipeline: What are the tasks of the different components, what information do they need to perform their duty, and what are the minimum viable services. Moreover, we had to make sure that there was no shared state, which in general is a good thing. Once we had a common, agreed-upon specification, each partner could work independently without getting into discussions on implementation details. As long as each service is accessible through a web API, the toolkits and programming languages used do not matter much. This allowed each partner to use the tools and languages they felt most comfortable with to achieve their goals. As long as the specification and API is adhered to, the components should work together. Accordingly, the integration risk is greatly reduced.
Another benefit of a microservice architecture is that it provides more flexibility when it comes to scaling the system on the cloud. Different services have different resource needs; some may require more aggressive scaling approaches. As an example, we have already identified our final binary compilation service as being particularly resource-intensive, thus requiring extra scaling and caching attention. Also, the lack of shared state eases the scaling task and has benefits on both a theoretical (our work on modelling scalable cloud services) and practical (actually making it scale) level. Being able to scale the services independently of each other makes us a lot more confident that the final product can work in real-life scenarios and not just with our test cases.
Finally, the microservice approach makes the toolchain easier to extend and modify, not the least in comparison with a classic, monolithic software system. For non-automotive domains, customer requirements may be different. Being able to shuffle services around and easily add new services makes the toolchain a lot more flexible. For instance, some clients may be wary of running the entire toolchain on a public cloud infrastructure and may want to mix in legacy components running on their existing servers. With a service-oriented approach, this is a feasible task.
Did microservices solve all of our integration challenges? In practice, there were more hurdles to overcome, especially for deployment. Having a microservice work on your computer or in the test lab is a good start, but the HyVar toolchain is meant to be running on the cloud. The cloud is not a clearly defined entity: there are multiple public cloud providers with different platforms and also various private cloud architectures. What we gained by allowing each partner implementation flexibility could easily be lost by having to make each service, with their different dependencies, work on any given cloud infrastructure. For this we turned to Docker.
Docker is a virtualization technology that recently has seen a lot of traction and usage. With Docker you bundle your software—in our case each individual microservice—with all the libraries, tools and dependencies it needs to function. This is known as a Docker container. Such containers are more lightweight than traditional virtual machines, and it is not uncommon to run several concurrent containers on e.g. the same Linux instance. Unlike virtual machines, a Docker container does not include an entire operating system.
A Docker container can be considered an instance of a Docker image. To build an image, you start with a base layer image. A Dockerfile is written; this file contains a set of instructions similar to a shell script that specifies how your image should be provisioned. Once the image is created it is used to launch a container. The image can freely be shared and is completely standalone.
While useful for scaling and encapsulation, for us the real utility in Docker lies in its “build once, run anywhere” philosophy: if your container works locally it is guaranteed to work just the same on any Docker-compatible system, be it on the cloud or anywhere else. When the operating environment is no longer a critical component, the tasks of integration and deployment become simpler. Returning to the challenges of HyVar being a geographically distributed project, this enabled each partner to work in isolation on their components and then distribute them to the rest of the project without having to worry about whether the other partners could use them or not. As long as the Dockerfile and the necessary supporting files are there, it will work on any system that supports Docker, such as e.g. Amazon Web Services, Microsoft Azure or your own laptop. Furthermore, it became possible to choose supporting technology freely without having to worry about whether it would work in production or not. As long as the Dockerfile compiles and the launched container fulfills the API specifications, this was as strong a guarantee as any that the delivered service would be deployable.
In conclusion, microservices and Docker have proved very useful when putting together the pieces of a complex project such as HyVar. Integration and deployment are critical parts of distributed projects which Docker has helped make more manageable. We have still had our share of integration issues but they have mostly been related to interface omissions or bugs in the services themselves. There are also downsides to service-oriented architectures and containerisation, one of the major being debugging when something goes wrong. Having to dig through multiple layers of virtualization does make bug finding more difficult, so putting some additional thought into logging and traceability is clearly beneficial. Nonetheless, so far our container-and-microservice approach has definitely helped keep the HyVar project on track.