3/3 Polyglot microservice development with Dapr – What options are available to simplify the development of polyglot microservices?

This is the third part of a blogpost series about polyglot microservices, which is based on my master’s thesis on “Polyglot Microservice Development with the Distributed Application Runtime”. In the first part we looked at polyglot microservices, how they differ from microservices, what is necessary for their implementation, and what advantages and disadvantages they have. In the second part we identified the challenges that experienced developers face when developing polyglot microservices and what kind of tooling would solve them. Now in the third part of the series, we will examine tooling that might solve the challenges associated with polyglot microservices and simplifies developing them.

Contents

Most of this tooling is not directly aimed at polyglot microservices and sells itself as a solution for microservices. However, as explained in the previous blog posts, free choice of programming languages is often touted as one of the key advantages of microservices. Therefore, tooling that is aimed at microservices in general is also in scope for polyglot microservices. Furthermore, leaving it out would not provide the whole picture and would not explain why modern tooling such as Dapr is needed in the first place.

Available tooling

As already mentioned, only parts of the tooling described in the following sections is aimed explicitly at polyglot microservices. But we will include it to provide the whole picture and to see which challenges are solved by tooling, that you might already know. This is necessary to get a grasp of why tooling as Dapr is even needed, when things such as libraries, microservice frameworks, Kubernetes, and service meshes are available. As a teaser, all of them solve specific parts of the challenges associated with developing microservices but miss to address other areas. In our case the focus will be on the capabilities they provide for developing polyglot microservices.

Language-specific libraries and frameworks

Libraries and frameworks that provide parts or a full set of capabilities to simplify the development of microservices are usually language specific or tied to a specific technology stack, e.g. the JVM or .NET Runtime. Furthermore, they are usually included inside the applications code and are therefore tied to the applications programming language. This is also known as the microservice chassis pattern, which provides a common set of capabilities for all microservices but must be recreated for each programming language that is used. Of course, this does not mean that libraries or frameworks do not simplify the development of polyglot microservices. Without them it would be even harder than it already is, but they are limited to specific programming languages. Examples for them are Spring for Java, Steeltoe for .NET, or Micro for Golang.

The main problem with this approach is that the expertise of developers and existing implementations are limited to the specific technology stack that was used. Furthermore, this approach limits the choice of programming language to programming languages for which such frameworks and libraries exist. Therefore, using more exotic or newer programming languages is challenging with this approach.

Consequently, language-specific libraries and frameworks work fine with a limited number of programming languages, but do not scale well. This means they are a good fit for single language microservice architectures but not for polyglot microservice architectures.

Microservices platforms

In the definition of this blogpost series microservice platforms are platforms or frameworks that provide capabilities to operate microservices. The most prominent and widely used solution for this is Kubernetes, which is the de-facto standard for operating containers. Of course, microservices are not required to be deployed as containers and containers are not necessarily microservices. But both are good fit to be used together. Thus, Kubernetes is a great solution to operate and scale microservices without the requirement to implement all the stuff needed to do this by yourself.

Kubernetes provides your services with capabilities such as deploying, serving, scaling, and monitoring of your containers, managing the available resources of the underlying machines, and load-balancing to utilize resources as efficiently as possible. Furthermore, Kubernetes established the concept of Pods on which the sidecar pattern and the following tools, which are service meshes and Dapr are built on. Summarized, a microservice platform provides a microservice architecture with an abstraction layer for the underlying infrastructure and takes care of operational tasks. This means a microservice platform is a solution that provides all non-functional requirements that are related with operating services. However, they are no solution for the other requirements that were explained in the first part of the blog post series.

Nevertheless, microservice platforms are immensely helpful for both single language and polyglot microservice architectures. But as they provide only a part of the non-functional requirements that are necessary to implement a microservice architecture, they are only a baseline of the tooling required to simplify the development of polyglot microservices.

Service meshes

Service meshes were and still are a hot topic in the cloud-native space. Unlike Kubernetes, there is no de-facto standard for service meshes (yet) and there is still a lot of competition. Noteworthy projects are Istio, Linkerd and Open Service Mesh but there are plenty more options beside them. Service meshes are built on the sidecar pattern, which is the idea of combining an application container with a second container (the sidecar) that contains additional capabilities. Combined with Kubernetes Pods, which guarantee that all containers in a Pod are executed on the same machine, they allow for an efficient execution of this pattern. You can see this as the concept of libraries, which also provide capabilities for an application, applied to the world of distributed and heterogeneous systems.

The selling point of service meshes is that they provide these additional capabilities such as observability, traffic management, encryption and so on, in a way that is transparent for the application. This means an application does not need know of the service mesh and can be enhanced by a service meshes capabilities without changes. While all of this sounds great, service meshes are not a good fit for every system, but this is beyond the scope of this blogpost series.

With our focus on polyglot microservices, the transparent way service meshes provide applications with their capabilities has another significant advantage. Transparent means there is also no coupling to specific programming languages or technology stacks. This means a service mesh can be used no matter the programming language a microservice is written in. Now you might say that sounds like the solution we are looking for to simplify the development of polyglot microservices. But there is a caveat. Service meshes are focused on non-functional requirements that are linked to the network layer. They still do not help us with requirements such as service invocation, asynchronous communication (Pub/Sub), state management or integrating with third-party components. However, we are almost there. Now let’s see how Dapr can help us with the missing parts.

Dapr

The Distributed Application Runtime (Dapr) is a fairly new project, which was initiated by Microsoft in 2019 and has recently become a CNCF Incubating Project. Thus, Dapr is no longer owned by Microsoft, but under Open Governance. For more details on Dapr see my blogpost on the Dapr v1.0 release in February 2021. The following graphic shows an overview of Dapr, its integrations and capabilities.

Overview of Dapr

The main point that makes Dapr stand out is its idea of providing capabilities such as service invocation, publish and subscribe or state management via open and widely available APIs that can be used in almost every programming language. Furthermore, the Dapr building blocks define an Interface for which different implementations can be provided; thus, allowing Dapr to work with any cloud or service provider. The self-hosted and Kubernetes hosting options of Dapr are an additional feature that makes your code portable across different infrastructures and service providers.

Dapr vs. Service Meshes

As explained in the previous section on service meshes, both Dapr and service meshes use the sidecar pattern to enhance applications with additional capabilities. The main difference between them is that service meshes are transparent to the application while Dapr must be explicitly called from within the application. Furthermore, there is a difference in the focus of both kinds of tooling. This can be seen in the following graphic, which outlines the Developer focus of Dapr and the Operator focus of service meshes, with the later providing more network capabilities.

Dapr vs. Service meshes

A key point is that Dapr is not a service mesh and does not replace the service mesh. Therefore, the main difference between them is their focus on various kinds of non-functional requirements. It is even perfectly fine to run Dapr in combination with a service mesh, if you need the advanced networking features of a service mesh and the developer-centric capabilities of Dapr. How this can be done and when this is appropriate is explained in the Dapr Documentation. Now that we know the differences between service meshes and Dapr, let’s see what problems Dapr can solve for us.

What problems does Dapr solve?

Dapr is specifically aimed at simplifying the development of distributed systems. Unlike in the past distributed systems are increasingly common nowadays with the broad adoption of the microservice architecture. This means a lot more developers must implement distributed systems and not only a few highly skilled distributed system engineers as it was the case in the past. Consequently, there is a need for a framework or building blocks (in Dapr terminology) to make distributed systems more approachable. As explained in the previous section on language-specific frameworks and libraries, this problem is solved for single language microservices but not for polyglot microservices.

This is the part where Dapr comes to the rescue. As frameworks do for single language microservices, Dapr provides developers with the key capabilities needed for a distributed system and does it via an easy to-use and language agnostic API. This means as a developer, you have the freedom to use your preferred technology stack without the restriction imposed by which frameworks and integration libraries are available. Because of the abstraction provided by Dapr it is easy to change third-party components and even programming languages of microservices. It still requires work to do so, but with Dapr your microservice architecture becomes a lot more flexible and allows you to revise technology decisions with much less cost than before.

Overview and examples of the Dapr API

Limitations of Dapr

Although Dapr solves most of the problems you will encounter when developing distributed systems and polyglot microservices, it also has its limitations. Mainly when it comes to state management and observability. These limitations are not severe but must be considered when deciding whether Dapr should be used for a project or not.

As of the recent release of Dapr v1.6, the state management building block is still limited to the key-value data model, but now has basic query capabilities. While they make the state management building block more useful, they are not a solution for advanced use cases. Thus, if your main use case for Dapr is to abstract state stores in various programming languages and you require more than basic query capabilities, Dapr might not be a good fit. In this case, the overhead of introducing it could not be worth it.

Secondly, Dapr only provides logging and metrics for its own components and distributed tracing is limited to invocations made over the service invocation and publish-subscribe building blocks. Unfortunately, there is no way to use Dapr for application-level tracing, logging, or metrics for now. Again, this can be a severe constraint, if this is the main use case for introducing Dapr to the system. Currently there are proposals to add application-level tracing and application-level metrics support but as there is no release date for these capabilities, they remain limitations of Dapr.

For which projects does it make sense to use Dapr?

Now the important question, when does it make sense to use Dapr. The answer is it depends. As with all technology, Dapr is no silver bullet but does solve most of the problems linked with polyglot microservice development. Overall, the use of Dapr should be considered when polyglot microservices are being developed, the capabilities and flexibility of Dapr are needed, and the project size justifies the initial effort to adopt the Dapr programming model.

If you are looking for a managed offering of Dapr, there is an Azure Service called Azure Container Apps, which is in preview and allows you to use the Dapr APIs without managing Dapr yourself. Try it if you’re interested in using Dapr and want to use a serverless platform.

Alternatives to Dapr

To complete the overview of available tooling, let’s have a look at alternatives to Dapr. Beside Dapr we will examine two projects with a similar scope, called Micro and wasmCloud. Both also try to simplify the development of distributed systems, but do it differently as Dapr.

Micro

The first alternative Micro describes itself as a cloud platform for API development that deals with the complexity of distributed systems and provides abstractions to build on. This sounds a lot like Dapr. However, while the goal is similar, developing services for Micro is limited to Golang. It is possible to interact with Micro’s services via gRPC and HTTP APIs, but while this sounds almost identical to Dapr, the services themselves must be written in Golang.

Consequently, Micro might be a good fit if you are primarily using Golang to build microservices and have only a few services written in other languages that should integrate with the capabilities provided by Micro. However, this is only possible with an intermediary service using the Micro framework to access the Micro server. Overall, Micro is not an equivalent alternative to Dapr because it is not a good fit for polyglot microservices through its limitation to Golang.

wasmCloud

The second alternative wasmCloud is not stable yet but is an interesting project based on WebAssembly. The latter is a hot topic right now with a promising future, which is the reason wasmCloud could become a competitor to Dapr in the future. In contrast to Dapr, wasmCloud does not use the sidecar pattern, but is built on a runtime that is available written in Elixir leveraging the OTP framework or in JavaScript leveraging waPC-js. These runtimes then run WebAssembly Actors that can invoke capabilities such as keyvalue, messaging, or an httpserver. In this way wasmCloud resembles the Dapr building blocks. But the way these building blocks are provided is completely different.

Unfortunately, while WebAssembly is polyglot in theory, the tooling to compile code to WebAssembly is still limited in popular programming languages and thus wasmCloud only supports Rust to develop actors for the moment. But as WebAssembly could in theory support almost every programming language this might change in the future. As a CNCF sandbox project wasmCloud still has a way to go but should be kept on your radar if you are interested in developing distributed systems.

Does modern tooling simplify developing polyglot microservices?

To answer the question from the conclusion of the second part of the series if modern tooling does simplify developing polyglot microservices and if it does solve the identified challenges and needs for tooling. Yes, it mostly does. Now to round up the blogpost series let’s recapitulate the outcome of the previous content.

To do so, let’s first recap the challenges of developing polyglot microservices and the needs of tooling that we looked upon in the previous part of this blogpost series. Then let’s conclude with which parts modern tooling can and which it cannot solve.

Challenges of polyglot microservices

  • Implementing non-functional requirements in different programming languages
  • Implementing asynchronous communication, observability, and state management
  • Coupling to third-party components and their dependencies in microservices code
  • Different tooling in different programming languages

Summarized, the challenges of polyglot microservices mostly resolve around implementing non-functional requirements multiple times, differences in programming languages in general and coupling to language-specific libraries and tooling. This is a direct result of the limitations of language-specific libraries and frameworks as we have discussed earlier.

Modern tooling such as Dapr does solve these challenges by transferring the concept and advantages of language-specific libraries and frameworks to the world of distributed systems by abstracting implementations from programming languages. Although Dapr has its limitations as we have seen earlier, it still solves most of the identified challenges and thus makes developing polyglot microservices a lot more approachable.

Needs for tooling that simplifies developing polyglot microservices

  • Abstraction layer and a unified, programming language-independent API to third-party components
  • Tooling that can be used independently of specific programming languages via an abstraction layer
  • Best practices to help select the appropriate technology from the wide range of tools and services available
  • Basic components that implement non-functional requirements and can be used independently of programming languages

The needs for tooling can be divided in two parts. On the one hand, the needs for language-independent and abstracted tooling, including ready-to-use basic components and on the other hand best practices for selecting the right technology. The first part is clearly linked to the identified challenges around language-specific libraries, frameworks, and tooling and the wish to reuse existing implementations in multiple programming languages. The second part, however, is more of an issue with the fast-evolving world of technology and thus the number of available tools and services.

Modern tooling such as Dapr does fulfil most of these needs by providing an abstraction from programming languages, implementations of components and third-party components. However, technology cannot replace experienced developers, which are necessary to choose the right technology for the project. But Dapr can assist by making it easier to revert technology choices at a later point through its abstraction layer and language independence. Furthermore, Dapr can reduce the choices to be made by providing ready-to-use basic components for which no technology has to be selected. But in the end, nothing can replace experienced developers to decide on the technology to use.

Nevertheless, a lot of the needs for tooling to simplify developing polyglot microservices are met by Dapr. Thus, Dapr makes it possible to profit from the advantages of using polyglot microservices without the complexity overhead, that existed without such tooling.

Conclusion

As we have seen, there are multiple options available that help us to simplify the development of polyglot microservices, with Dapr currently being the most advanced. Nevertheless, which flavour of tooling is right for you depends on your application and use case. Go for libraries and frameworks if you use one main programming language, use other programming languages sparsely and do not require flexibility with changing third-party components. Leverage service meshes if you need traffic encryption and advanced network capabilities or want to upgrade an existing microservice architecture with these features. Choose Dapr if you use polyglot microservices, need its flexibility and if the Dapr capabilities are useful for your application. You might even try one of the alternatives to Dapr or combine the approaches by gradually enhancing an existing microservice architecture by adding Dapr and service meshes to the mix.

Finally, I would like to thank you for reading this far. I hope the blog posts gave you valuable insights on polyglot microservices, their challenges, the available tooling and that you are now confident in choosing the right approach for your next project. Enjoy building your next distributed system or polyglot microservice architecture!