Microservices architecture is a popular approach for enterprise applications, that involve the development and support of a collection of loosely coupled services. Generally, these services are fine-grained and the protocols are lightweight. Services are often intended to represent a single business process and well suit a service-oriented approach. This is a departure from the long-established monolithic code base.
Traditionally, enterprise applications consist of three main parts that include a database, the user interface, and a server-side application. The server-side layer is what encompasses the business logic for the entirety of the application, often serving to provide a wide range of functions. Such an architecture is more fixed than microservices and although it has its advantages, a detailed comparison of these two is outside the scope of this article. Rather, we’re going to concentrate on the development of microservices using C++ and how it differs from monolithic applications in this regard. Some real-world use cases for microservices can be seen in this article.
Features of Microservices
The two main features of microservices are modularity and scalability. Individual services are represented as modules, where each module is built around a business function. Each service will handle its respective function very well, and typically not be responsible for anything else. Microservices can be deployed independently, although this is not to say that they work independently of other modules. On the contrary, while they are loosely coupled and run as separate processes, they do communicate with each other across the network. Modules can be easily upgraded or replaced, as long as their responsibility from the perspective of the rest of the architecture is maintained. Microservice architecture is highly scalable when compared to a monolithic application.
It is important to keep in mind that microservices are usually active part of a larger system and as such, they need to interact with one another. Where a monolithic application might rely on a simple function call, a microservice relies on interprocess communication using protocols such as HTTP (e.g. for REST API), TCP (for binary based protocols) or AMQP (which is also over TCP).
Microservices can interact using data in any form, but usually it is based on JSON, XML, or binary. Microservices which are REST API based, would usually pass data as JSON. The term “microservice” does not narrow it to a certain option and you can even use a non-standard format, although this is not recommended for services that will be publically posted. The principle of smart endpoints and dumb pipes is associated with microservices, and it refers to having simple communication between services.
Using Microservices Architecture Internally and Externally
Microservices can be implemented internally, offering functionality between components of the product, or externally, to have it interact with third-party solutions. An internal microservice might be operating inside a microservices cluster, and assume the role of a single module in what would otherwise be a procedure inside a feature-rich monolithic application.
Internal microservices may be accessible via API but this does not imply that the API is public. Rather, the services are specific to the product and the functionality is not independently offered to third parties.
An external microservice may offer its capabilities to other products by exposing a public API. This interface can be used by third-party systems to make use of the service. One such example is OpenWeather, which is a platform that exposes an API, offering a variety of weather-related data. Sending a valid request will return the corresponding block of data that the client can use for its purposes.
Developers usually rely on some kind of infrastructure to implement microservices. It avoids the need for writing the boilerplate of communication and protocol decoding-encoding, leaving the developer to implement only the actual code inside the service.
Popular Infrastructures for Creating Microservices
There are several infrastructures available for creating Microservices, and the choice of which one to use is often dictated by the programming language. For example, Flask can be the choice for developing microservices, if you are writing in Python, other popular option for python is FastAPI. Similarly, Spring Boot is popular by Java Developers, supporting embedded server that is easy to deploy with containers, and can eliminate the need for external servers. If you are developing in NodeJS then you may instead use one of the open-source tools, ExpressJS or Moleculer, which are both quite popular frameworks.
Reasons Why Microservices Are Ostensibly Less Relevant in C++
In many cases, when writing in C++, there is an advantage to having all of the modules run in the same process. This eliminates the need for external communication between components and instead relies on direct API calls. This can still be modular and it should be. Different components can be built as libraries that are statically or dynamically linked, or loaded dynamically at runtime, eventually running in the same process, but then it would not fall into the classic microservices definition.
Reasons Why Microservices Are Still Relevant in C++
C++ may be a good choice for computationally-intensive microservices.
You would see in C++ more binary services (e.g. with a gRPC protocol) than textual services that use RESTful APIs: choosing C++ is done in many cases when performance is crucial. In such cases, using a textual API (such as REST) would not be our first choice.
If you have a microservice that benefits from being implemented in C++ and you want to expose an external API to it, this can be done using your choice of protocol. There are some things to consider, however. For example, if the server is returning a sizeable block of data then a binary stream can be compressed to conserve bandwidth. Of course, a service may expose both binary and textual APIs, leaving the client to choose which is most appropriate.
Although there is a slower trend in C++ in moving to microservice architecture, as C++ developers traditionally prefer running all of the components in the same process, C++ is still relevant for microservices and we believe we would see a shift to Microservices in C++, mainly based on binary protocols, again for performance reasons.
C++ Frameworks for Microservices
There are myriad C++ libraries and frameworks available for developing microservices. One such framework is C++ Micro Services, which eases the building and managing of modular and dynamic service-oriented applications. Some of the others include the C++ REST SDK, Crow, HttpLib, Pistache, Restbed, and Restinio. A benchmark for these popular frameworks can be found here. Several more can be found in this list, meaning that C++ developers have plenty of choices regardless of what protocols they want to support. An informal discussion on the topic and some of these frameworks can also be found here.
The Challenges of Microservices
Employing a microservice architecture has its advantages, but like with most things, there is a trade-off. Indeed, a poorly designed microservice might take longer than expected to develop, and there is plenty of opportunity for failure. When it comes to writing a microservice, several challenges come to mind. Among those at the top of the list are defining the proper API, isolating the microservices, interservice communication, error handling, and overall complexity.
When designing the API for a microservice, consideration needs to be given to the fact that each one uses APIs to communicate with one another. There is firstly a question of what data should be shared but once this is decided, the protocols should be fast to compete with the monolithic applications that they are replacing. The most important aspect to remember about API communication, in this case, is to enforce loose coupling. Not doing so may invalidate the very reason that you chose to implement microservices in the first place!
When it comes to testing, microservices need to be treated as independent and standalone components. This necessitates testing be more rigorous for each microservice than, for example, the same functionality in a monolithic application. While the isolation of the microservices makes testing easier, as you can test each service separately, testing of each microservice should take into account all possible inputs and flows, especially if the caller might be external.
The same holds for error handling, debugging and troubleshooting, which is again more complex because of the loose coupling and intercommunication between microservices. Analyzing an error may become harder, compared to a monolithic application, as the flow is dispersed, multiple microservices may report parts of the same error, and data related to the error might be missing in some of the microservices, which leads to difficulty in finding the root cause.
Finally, tracing a request through several microservices is inherently more difficult than tracing function calls in a monolithic application.
The microservices architecture is a departure from monolithic systems, which aids in the development of complex applications by breaking them into smaller and more manageable components. They are modular and inherently more scalable. Microservices do come with additional challenges that need to be accounted for. Several languages are popular for microservice development, although C++ is not among the top contenders. In practice, C++ is a good language for microservices in domains which require the attributes of C++ such as runtime speed and direct memory access, and C++, as other languages, has a variety of infrastructures available to help you get started with developing microservices.