Dapr is a framework written in Golang, which promises that it will support the development of
cloud-native applications are greatly simplified so that the developer can focus on the core logic of his application.
Dapr was launched in October 2019, after an initial confusion of names in a first
alpha version released.
The name was originally Dapper.
But since this name already existed, a decision was made to keep the same wording and just change the word itself.
Now you can find Dapr in version 1.4 and according to GitHub there are currently 139 contributors.
But what is Dapr now? Here is the original description from the official dapr documentation:
Dapr is a portable, event-driven runtime that makes it easy for any developer to build resilient, stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks.
Leveraging the benefits of a sidecar architecture, Dapr helps you tackle the challenges that come with building microservices and keeps your code platform agnostic.
Dapr codifies the best practices for building microservice applications into open, independent building blocks, which
enables you to build portable applications using the language and framework of your choice.
Every building block is completely independent and you can use any, some, or all of them in your application.
In addition, Dapr is platform independent, meaning you can run your applications locally, on any Kubernetes cluster and
run in other hosting environments that Dapr is integrated with.
This way you can use microservice applications that can be run in the cloud and on edge devices.
Structure of Dapr
Let’s take a closer look at the structure of Dapr.
As we already read in the official description, Dapr is connected to its own application in a sidecar container.
So theoretically, you don’t need to change anything inside your application.
In practice, however, it looks a little different because Dapr provides SDKs for different languages.
One uses the SDKs so you have a dependency on Dapr in your code.
However, you don’t have to go to an SDK
but can also access the various building blocks directly via REST or gRPC.
Here we see the basic structure of the architecture.
Building Blocks
Building Blocks are an interface provided via HTTP or gRPC that address and simply solve known problems of a microservice architecture.
Dapr delivers the following 7 building blocks as standard.
Service-to-service invocation
The service call allows applications to message each other through known endpoints (HTTP or gRPC)
to exchange data.
Dapr offers an endpoint that works as a combination of a reverse proxy with integrated service discovery
functions and at the same time uses the integrated tracing and error handling.
For this purpose Dapr uses Service Discovery components.
For example, the Kubernetes Service Discovery component is integrated in the Kubernetes DNS Service.
Dapr also allows custom middleware to be added to the request process.
With that it is possible doing additional actions such as authentication or the transformation of a message before the request reaches the application code.
State Management
In order to save the data of an application, you need a persistence.
Dapr offers a KeyValue Store API for this to persist data in exchangeable stores.
Dapr offers different types of stores here, such as file system, database, storage.
Common state store implementations are, for example, Redis or AWS DynamoDB.
A complete list can be found here.
Publish and Subscribe
Dapr supports
the subscribe pattern,
in which a sender can write a message in a queue and a recipient can retrieve this message and handle it.
Dapr supports the common providers here
like NATS Streaming
or Kafka.
A full list can be found here.
Resource Bindings and Triggers
This makes it possible to connect an application to an external cloud or on-premise system.
Dapr enables you to call external systems with the binding API, or that your own application through events from connected systems
can be triggered.
Supported components are, for example, Cron, HTTP or Apple Push-Notifications.
More can be found here.
Actors
An actor is an isolated and independent unit that enables code and data to be separated from one another.
Observability
Dapr system components and runtime output metrics, logs and traces to identify Dapr system services, components and
Debug, operate, and monitor user applications.
Secrets
Dapr offers a Secrets Building Block API and can be used in all common secret stores such as AWS Secrets Manager or Kubernetes.
The Secrets API can then be used to save secrets in the code and then again recall them.
A full list of all supported secret stores can be found here.
If you need further building blocks, you can use the extension framework to create new blocks by yourself.
Is Dapr a service mesh?
Since Dapr also offers similar functionalities to a service mesh (like Service-to-Service communication with mTLS),
and is also deployed as a sidecar container, the question arises whether Dapr will ultimately not just be another service mesh.
Dapr himself gives the following answer:
Unlike a service mesh, which focuses on network matters, Dapr focuses on to offer what are known as building blocks (see above),
which make it easy for the developer to build and deliver applications as microservice.
Dapr is developer-centric while a service mesh is infrastructure-centric.
Thus, Dapr is not a service mesh.
Logically, it follows that Dapr can be used together with a service mesh.
It is only recommended that service-to-service communication with mTLS is only allowed with one of the two frameworks,
as this functionality is provided by both Dapr and common service mesh implementations
like Linkerd
or Istio.
Dapr in practice
Now let’s see Dapr in practice.
First of all we have to install the Dapr CLI.
On the Mac this can be done very easily via the terminal or via Brew.
Here I choose the route via the terminal.
1
|
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash
|
Then we can use the command dapr
to verify whether the CLI has been installed correctly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
dapr
__
____/ /___ _____ _____
/ __ / __ '/ __ \/ ___/
/ /_/ / /_/ / /_/ / /
\__,_/\__,_/ .___/_/
/_/
===============================
Distributed Application Runtime
Usage:
dapr [command]
Available Commands:
build-info Print build info of Dapr CLI and runtime
completion Generates shell completion scripts
components List all Dapr components. Supported platforms: Kubernetes
configurations List all Dapr configurations. Supported platforms: Kubernetes
dashboard Start Dapr dashboard. Supported platforms: Kubernetes and self-hosted
help Help about any command
init Install Dapr on supported hosting platforms. Supported platforms: Kubernetes and self-hosted
invoke Invoke a method on a given Dapr application. Supported platforms: Self-hosted
list List all Dapr instances. Supported platforms: Kubernetes and self-hosted
logs Get Dapr sidecar logs for an application. Supported platforms: Kubernetes
mtls Check if mTLS is enabled. Supported platforms: Kubernetes
publish Publish a pub-sub event. Supported platforms: Self-hosted
run Run Dapr and (optionally) your application side by side. Supported platforms: Self-hosted
status Show the health status of Dapr services. Supported platforms: Kubernetes
stop Stop Dapr instances and their associated apps. Supported platforms: Self-hosted
uninstall Uninstall Dapr runtime. Supported platforms: Kubernetes and self-hosted
upgrade Upgrades or downgrades a Dapr control plane installation in a cluster. Supported platforms: Kubernetes
Flags:
-h, --help help for dapr
--log-as-json Log output in JSON format
-v, --version version for dapr
Use "dapr [command] --help" for more information about a command.
|
Now we can install Dapr locally.
Normally, Dapr runs as a sidecar container.
That means locally, it works as a separate process.
With the following command the sidecar binaries are pulled, placed and installed on the local client.
When everything is installed, we can use dapr --version
to check that everything is installed correctly.
1
2
3
|
dapr --version
CLI version: 1.4.0
Runtime version: 1.4.2
|
So far so good.
Let’s start our first Dapr Sidecar Container.
The command dapr run
helps us here.
We now start a sidecar container with an empty myapp application.
Here the standard components are used which we find in the Dapr configuration folder (~/.dapr/components/).
For example, the local Redis Docker container is used as state storage and message broker.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# dapr run --app-id myapp --dapr-http-port 3500
WARNING: no application command found.
ℹ️ Starting Dapr with id myapp. HTTP Port: 3500. gRPC Port: 58355
ℹ️ Checking if Dapr sidecar is listening on HTTP port 3500
INFO[0000] starting Dapr Runtime -- version 1.4.2 -- commit 786e808a98ea0cc51948cff04196604ef3728565 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] log level set to: info app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] metrics server started on :58356/ app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.metrics type=log ver=1.4.2
INFO[0000] standalone mode configured app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] app id: myapp app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] mTLS is disabled. Skipping certificate request and tls validation app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] local service entry announced: myapp -> 192.168.178.20:58360 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.contrib type=log ver=1.4.2
INFO[0000] Initialized name resolution to mdns app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] loading components app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] component loaded. name: pubsub, type: pubsub.redis/v1 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] waiting for all outstanding components to be processed app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] component loaded. name: statestore, type: state.redis/v1 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] all outstanding components processed app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] enabled gRPC tracing middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.grpc.api type=log ver=1.4.2
INFO[0000] enabled gRPC metrics middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.grpc.api type=log ver=1.4.2
INFO[0000] API gRPC server is running on port 58355 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] enabled metrics http middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.http type=log ver=1.4.2
INFO[0000] enabled tracing http middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.http type=log ver=1.4.2
INFO[0000] http server is running on port 3500 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] The request body size parameter is: 4 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] enabled gRPC tracing middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.grpc.internal type=log ver=1.4.2
INFO[0000] enabled gRPC metrics middleware app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.grpc.internal type=log ver=1.4.2
INFO[0000] internal gRPC server is running on port 58360 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.actor type=log ver=1.4.2
WARN[0000] app channel not initialized, make sure -app-port is specified if pubsub subscription is required app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
WARN[0000] failed to read from bindings: app channel not initialized app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] dapr initialized. Status: Running. Init Elapsed 29.511ms app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
INFO[0000] placement tables updated, version: 0 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime.actor.internal.placement type=log ver=1.4.2
ℹ️ Checking if Dapr sidecar is listening on GRPC port 58355
ℹ️ Dapr sidecar is up and running.
✅ You're up and running! Dapr logs will appear here.
|
Let’s now write something in our state memory.
To do this, we use the following command:
1
|
curl -X POST -H "Content-Type: application/json" -d '[{ "key": "message", "value": "Hello from b-nova"}]' http://localhost:3500/v1.0/state/statestore
|
As we can see, we are using the Dapr API to write directly to the Redis State Store.
If we want to display the message again, we can do this with the following command:
1
2
|
curl http://localhost:3500/v1.0/state/statestore/message
"Hello from b-nova!"%
|
Now we can still verify whether Dapr really uses the Redis Docker container as state storage.
To do this, connect directly with our Docker container as follows.
1
|
docker exec -it dapr_redis redis-cli
|
Since we are now directly in the redis-cli, we can then call up all keys via keys *
.
1
2
|
127.0.0.1:6379> keys *
1) "myapp||message"
|
Now we also want to display the value.
To do this, we enter the following:
1
2
3
4
5
|
127.0.0.1:6379> hgetall "myapp||message"
1) "data"
2) "\"Hello from b-nova!\""
3) "version"
4) "1"
|
Use your own component
Now we have seen how standard components can be used with Dapr.
Next, let’s see how to use its own component as a KeyValue store.
First we create a file in the tmp folder /tmp/secrets.json
with the following content:
1
2
3
|
{
"my-secret": "supersecret"
}
|
Then we create a folder my-components
.
In this folder we will create a file
my-local-secret-store.yaml
with the following content
1
2
3
4
5
6
7
8
9
10
11
12
13
|
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: my-local-secret-store
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: /tmp/secrets.json
- name: nestedSeparator
value: ":"
|
That’s all.
All we have to do now is to enter the path to your component into the command to create the sidecar container.
We should then see the specified string in the output.
1
2
3
|
dapr run --app-id myapp --dapr-http-port 3500 --components-path ./my-components
INFO[0000] component loaded. name: my-local-secret-store, type: secretstores.local.file/v1 app_id=myapp instance=Stefan-Welsch-MacBook-Pro.fritz.box scope=dapr.runtime type=log ver=1.4.2
|
Let’s see if our secret is really read from our own new store.
1
2
|
curl http://localhost:3500/v1.0/secrets/my-local-secret-store/my-secret
{"my-secret":"supersecret"}%
|
🚀
Dapr Go SDK
Now we have written and used our first own component.
Next we want to have a look at Dapr SDK.
So let’s write a very simple Go program.
The first thing we do is create a new Go project.
In addition we give the following commands (Go Modules have to be enabled)
1
2
3
4
|
mkdir my-go-dapr-sdk
cd my-go-dapr-sdk
go mod init github.com/b-nova-techhub/my-go-dapr-sdk
go get github.com/dapr/go-sdk/client
|
Next we create the main.go
file and write the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package main
import (
"context"
"fmt"
dapr "github.com/dapr/go-sdk/client"
"log"
)
const (
stateStoreName = `my-local-secret-store`
daprPort = "3500"
)
func main() {
client, err := dapr.NewClient()
if err != nil {
panic(err)
}
defer client.Close()
ctx := context.Background()
item, err := client.GetSecret(ctx, "my-local-secret-store", "my-secret", make(map[string]string))
if err != nil {
fmt.Printf("Failed to get state: %v\n", err)
return
}
log.Printf(item["my-secret"])
}
|
Now we copy the created folder for our custom component into the Go project. The folder structure should then look like this.
1
2
3
4
5
6
|
my-go-dapr-sdk
- go.mod
- go.sum
- main.go
- my-components
- my-local-secret-store.yaml
|
In order to test our program now, we still have to start Dapr together with the application.
As we see we have successfully retrieved the secret from our custom secret store via the SDK.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
dapr run --app-id myapp --dapr-http-port 3500 --components-path ./my-components go run main.go
....
INFO[0000] dapr initialized. Status: Running. Init Elapsed 14.037ms app_id=myapp instance=stefan-welsch-macbook-pro.home scope=dapr.runtime type=log ver=1.4.2
INFO[0000] placement tables updated, version: 0 app_id=myapp instance=stefan-welsch-macbook-pro.home scope=dapr.runtime.actor.internal.placement type=log ver=1.4.2
ℹ️ Checking if Dapr sidecar is listening on GRPC port 64963
ℹ️ Dapr sidecar is up and running.
ℹ️ Updating metadata for app command: go run main.go
✅ You're up and running! Both Dapr and your app logs will appear here.
== APP == dapr client initializing for: 127.0.0.1:64963
== APP == 2021/10/14 07:52:15 supersecret
✅ Exited App successfully
ℹ️
terminated signal received: shutting down
✅ Exited Dapr successfully
|
Conclusion
Dapr looks like a very interesting approach to making Cloud Native applications easy to develop.
The beginning takes a bit of getting used to, but once you understand the concept, it works really easy.
We at b-nova will definitely continue to follow the development of Dapr and of course using it in an internal b-nova project.
😀
This text was automatically translated with our golang markdown translator.