Building event-driven systems at scale in Kubernetes with Dapr — Part 2: How does Dapr work?
In my previous post, I introduced you all to Dapr. In this post, I am going to get into a bit more details as to how it works. I’ll touch on three core technical concepts that makes working with Dapr so easy, I’ll round describe how these concepts tie into my running code, and finish off with a conclusion about Dapr.
How does Dapr work?
To understand how Dapr works, one needs to understand the Sidecar pattern. The TL;DR of sidecar pattern is that there is a separate running application which works alongside your application to offload certain workloads out of your application. A common example of this would be a light weight, fast, application instance specific database (such as SQLite) which is paired as a sidecar container alongside your main application and is deployed as a single component — or pod in Kubernetes.
In Dapr, this concept is leveraged to solve the problem of writing plumbing code for your infrastructure. The code running in the Dapr sidecar is known as the Dapr runtime.
2. What are Dapr building blocks?
At a higher level, Dapr makes this abstraction of infrastructure possible through the concept of a building blocks. Building blocks can be understood as the feature set that Dapr provides for you. It’s these building blocks that make it easy to build a distributed application at scale — and from which Dapr gets its name; distributed application runtime.
The Dapr team looked at the most common challenges that development and operations teams run into while building highly scalable, distributed systems and set out to design a suite of features that make it easy to adopt industry best practices with a wide variety of infrastructure. As shown above, this involves leveraging service discovery while making service-to-service calls easy, leveraging Pub/Sub brokers to leverage horizontal auto-scaling and disconnect long-awaited call chains, interacting with state stores, distributed locks across multiple instances of a service, secure communication with mTLS, and more. You would simply pick the building blocks that you need in order to build towards your target architecture.
3. What are Dapr components?
Dapr components are what make the building blocks a reality. This is achieved by providing abstractions of real infrastructure to meet the need of the building block. RabbitMq, Kafka, Service Bus, Amazon SQS, are all examples of components that make the Pub/Sub building block a reality. These components all live in the dapr/components-contrib repository in GitHub and are regularly updated when updates to infrastructure libraries are made available. The Dapr team has an extensive set of GitHub Actions that run to ensure compliance with all existing components and validate no regressions are introduced with each new change. These components can be interchanged with no code changes necessary — the only changes necessary would be component configuration.
It should be noted, though, that Dapr is an opinionated framework and has made certain decisions around how components are implemented in order to have the widest coverage of the community as possible. If there is a component that does not suite your needs, you are welcome to submit a pull request against the repository to cater for your needs, or simply pick an alternative component that does suite your needs, or build your own custom component.
How does my application and Dapr actually work together?
Essentially, this is done via the Dapr API — although generally the details are hidden behind elegant configuration code. Fundamentally, to get Dapr to talk to your application — as would be the case in input bindings or Pub/Sub subscribers — you build your application to be reached either via gRPC or HTTP. For the majority of use cases, that means building REST APIs all day long which is something the industry has become very good at. This is then simple configuration code that allows Dapr to “discover” your API endpoints and interact with them as and when needed — either an incoming message or event on a queue/topic that your sidecar is subscribed to.
In terms of you reaching into your infrastructure through Dapr, that is handled by the Dapr Client. The Dapr Client is an abstraction of the Dapr API that gives you easy properties, methods, or functions to interact with in your code in order to get what you need. You typically use the Dapr Client to perform CRUD operations against your state store, publish a message to a Pub/Sub broker, make an HTTP call to another Dapr-enabled service via service-to-service invocation (which actually runs via gRPC under the hood), etc.
When you round up all that Dapr provides to you, it becomes immediately clear how powerful it can be to build a system at scale in Kubernetes with Dapr. Dapr is somewhat magical in how elegantly it solves complex problems for you. Gone are the days (or weeks, or months, or years) when you expend a tremendous amount of energy on new projects just to build the infrastructure code which is (hopefully) neatly encapsulated, highly resilient and easily referenced throughout your code base. By offloading all the work of interacting with infrastructure to a system that runs asynchronously to your main application, your working code becomes highly focused around the business problem you are trying to solve. This is what makes Dapr a system that turns a developer into a 10x developer. You can spin up a local development environment with lightweight containers while being comfortable that your production environment is the best of the best. It also allows you to rapidly prototype infrastructure changes without having to gut your entire application stack to accommodate these changes.
Dapr is ultimately a force multiplier for software and operations engineers.