Microservices work independently without affecting each other and the system is operational even if one of the microservices fails — it is one significant advantage. Read on for an introduction to Dapr.io, a tool that faces up to the challenges of the microservices architecture.
The architecture of distributed systems — Microservices and their challenges
The construction of distributed systems in the microservices architecture solves problems with accessibility and scalability, while also increasing flexibility and shortening the time-to-market of new functionalities, which is an important part of software development projects.
However, the complexity of such systems requires solid knowledge of how to deal with challenges like distributed transactions, load balancing, state storage, monitoring, communication between services, and security. As a result, programmers have to spend some of their time making decisions pertaining to the selection of tools and patterns and solving problems, none of which are directly related to the implementation of business logic.
Dapr — the construction of platform-independent distributed systems from building blocks
In response to these challenges, in October 2019, Microsoft launched the Dapr (Distributed Application Runtime) open source project on the GitHub platform (https://github.com/dapr), releasing the first stable version of the project for production use in February 2021. In accordance with the motto “Any language, any framework, run anywhere”, the key feature of Dapr is its independence from the programming language and the environment in which microservices are run.
It is a set of good practices (design patterns) implemented in the form of independent building blocks made available using the sidecar pattern. Developers often ask “Is Dapr a Service Mesh?” Whereas Service Mesh tools such as Istio, Linkerd or OSM facilitate communication between microservices using a sidecar proxy, DAPR’s task is to support developers building distributed applications.
The sidecar pattern
The sidecar pattern is a single-node pattern consisting of two containers: the application (containing the business logic) and the sidecar. The role of the sidecar container is to extend (provide new functionality) and improve the application container. The advantage of using this pattern is its modularity and the ability to reuse components used as sidecars. It also facilitates integration with older solutions without having to interfere with their code.
The container or process of Dapr sidecars (Dapr is not dependent on Kubernetes and can be run independently even on edge devices) provides the container/application process with a universal API for individual building blocks. Communication takes place via HTTP/gRPC protocols, so the use of building blocks does not require any dependencies to Dapr to be placed in the application code, and integration takes place when the code is run, not compiled. Additionally, to make the process more intuitive, Dapr makes SDK available for languages such as:
This approach also allows applications to take advantage of solutions/patterns not available in the language in which they were written. For example, an application written in PHP can draw upon the actor pattern (a pattern used for multi-threaded applications).
Building blocks — key components
Building blocks are the framework for the distributed systems created thereon, encapsulating the functionalities of the infrastructure. Each block consists of a public API and components that implement its functionality. These components can be replaced without having to change the API provided, which constitutes an extra layer of abstraction. This approach separates the application and makes it independent of solutions from various vendors, enabling migration without the need to interfere with the code.
Use case: migrating from Mongo DB to Redis
Our application state management app uses a no-SQL database — the MongoDB solution, referencing it directly in the code through the SDK. For reasons of best performance, we decided to migrate to Redis. As a result, we need to change the SDK and application code, which is specific to MongoDB, to adapt it to Redis. If we were to use the state management building block from Dapr from the beginning, such a change would be limited to correcting the component type in the YAML configuration file.
Dapr consists of 7 building blocks; however, adding more is not a problem due to its inherent openness. Let’s take a look at the individual blocks in the context of their responsibilities.
It allows direct communication between services, ensuring:
- reliability — automatic attempts to resend messages in case of errors, e.g., network errors
- mutual authentication (mTLS) and encryption
- access control (ACL)
- load balancing — the Round Robin algorithm
- automatic service discovery — uses Kubernetes DNS or mDNS depending on the environment.
It allows you to manage the state of services/actors, ensuring:
- datastores in the form of plug-ins — you can connect one of the many solutions available on the market, like Redis, MongoDB, MySQL, etc.
- optimistic concurrency control using Etag headers
- data integrity — supports strong and eventual consistency
- bulk operations
Publish and subscribe
Implements the Publish/Subscribe pattern, ensuring:
- integration with solutions like Redis Streams, Azure Service Bus, AWS SNS/SQS, etc.
- sent messages formatted in accordance with the CloudEvents specification
- message delivery guarantee (at least once)
- message lifetime control (Time-To-Live)
- topic-based grouping of messages, enabling control of senders and recipients
- grouping message recipients — implements the pattern of competing recipients
This enables two-way integration with external sources (systems/components) in isolation from a specific implementation. Binding events to specific actions takes place at the level of YAML configuration files. For example, in response to user registration events, we call up an external SendGrid service to send a confirmation e-mail. Changing the service provider for sending e-mails will not involve changes to the application code — changing the configuration is sufficient, and the application operator can do so without getting software developers involved.
It implements the actor model, which simplifies the construction of highly available and reliable systems without the need for complex concurrency and scaling patterns. In this model, the actor is the basic concurrent processing unit. The actors are completely hermetic and communicate with the outside world only via messages.
Characteristics of an actor:
- manages their own state (actors do not share states)
- receives messages from other actors and processes them sequentially (one actor = one thread)
- communicates asynchronously with other actors
Similarly to the Microsoft Orleans framework, Dapr utilizes the concept of a virtual actor, whereby the management of the actor’s life cycle takes place automatically.
Provides a structured approach for using:
- metrics — we can use, e.g., Grafana to monitor them
- logs — browsing can be done by means of, e.g., FluentD or Elasticsearch and Kibana
- distributed tracing — compliant with W3C Trace Context standards, which ensures integration with solutions such as ZIPKIN, Jaeger, and Application Insights
Provides secure storage of sensitive configuration data, i.e., passwords and keys, integrated with solutions such as Azure Key Vault, HashiCorp Vault, Kubernetes Secrets, etc.
The COVID-19 pandemic and the related changes and business challenges have dramatically accelerated the pace of moving infrastructure to the cloud and contributed to the growing popularity of multicloud and hybrid environments. The requirement for high application scalability has forced the “breaking” of monoliths and transformation to the microservices architecture.
This poses a serious technological and staffing challenge for companies (due to the limited availability of specialists in a given field). Solutions such as Dapr (https://dapr.io) and Open Application Model (https://oam.dev) and the concepts on which they are based, providing a set of best practices and streamlining the migration process, can significantly shorten this path and smooth it out as well.