Attribute | Details |
---|---|
Dapr runtime version | v0.11.3 |
Language | Javascript, Python, Go |
Environment | Local or Kubernetes |
This is a sample application built using Dapr as a proof-of-concept. I have experimented with the state store, pubsub and output bindings features available with Dapr. I have used multiple languages for writing the different parts of this calendar app. This demonstrates the language-agnostic nature of Dapr and the flexibility that it bings to developing applications.
I am really enthusiastic about cool open source projects and I'm a fan of Azure. When I learnt about Dapr early this year, I knew I needed to get my hands dirty playing around with what Dapr had to offer. I wanted to explore Dapr an experience building a distributed application with it to understand what it brought to the table in comparison to conventional applications.
I had built a SpringBoot app on MVCS architecture before; it was a monolith application, all written in Java. Building a roughly similar architecture as a distributed applicaiton would intuitively require some additional work pertaining to service discovery, inter-pod communication and network security. Things could get complicated if I needed additional checks, statestores or other controls which I would have to implement on my own. This, in addition to the actual application itself.
I wanted to find out how Dapr simplified this process and what additional work I would have to put in to get a distributed version of the same applciation using Dapr.
I have tried to model this system on the Model View Controller Service (MVCS) architecture, as already mentioned.
-
The controller supports creation of new events and deletion of existing events. It forwards these requests to the Go code using service invocation.
Shown below is the add event flow.
app.post('/newevent', (req, res) => { const data = req.body.data; const eventId = data.id; console.log("New event registration! Event ID: " + eventId); console.log("Data passed as body to Go", JSON.stringify(data)) fetch(invokeUrl+`/addEvent`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" } })
where the invokeURL is defined as:
const invokeUrl = `http://localhost:${daprPort}/v1.0/invoke/${eventApp}/method`;
-
On creation of a new event, it publishes a message to a pubsub topic which is then picked up by the Python subscriber.
Pubishing to the topic
function send_notif(data) { var message = { "data": { "message": data, } }; console.log("Message: ", message) request( { uri: publishUrl, method: 'POST', json: JSON.stringify(message) } ); }
where the publish URL is:
const publishUrl = `http://localhost:${daprPort}/v1.0/publish/${pubsub_name}/${topic}`;
The services handle the requests forwarded by the controller. Each of the tasks listed with the controller is handled by a service written in a different language. I'll detail the implementation below.
-
Event Service (written in Go): This service uses the statestore component Redis for storing and deleting events from memory. The code snippet shown below is from
go_events.go
and demonstrates adding an event to the state store.var data = make([]map[string]string, 1) data[0] = map[string]string{ "key": event.ID, "value": event.Name + " " + event.Date, } state, _ := json.Marshal(data) log.Printf(string(state)) resp, err := http.Post(stateURL, "application/json", bytes.NewBuffer(state))
where the stateURL is defined as:
var stateURL = fmt.Sprintf(`http://localhost:%s/v1.0/state/%s`, daprPort, stateStoreName)
-
Messaging Service (written in Python):
This service subscribes to the topic that we post messages to, from the controller. It then uses the [SendGrid](https://docs.dapr.io/operations/components/setup- bindings/supported-bindings/sendgrid/) output binding to send an email about creation of a new event. I have used the Dapr client for Python while writing this service.
The code below shows how the service registers as a subscriber with Dapr for a specific topic.
@app.route('/dapr/subscribe', methods=['GET']) def subscribe(): subscriptions = [{'pubsubname': 'pubsub', 'topic': 'events-topic', 'route': 'getmsg'}] return jsonify(subscriptions)
The Dapr runtime calls the
/dapr/subscribe
endpoint to register new apps as subscribers. The other way to do this would be defining a configuration file, linked here.The following code receives the message posted to the topic and then calls the
send_email
function.@app.route('/getmsg', methods=['POST']) def subscriber(): print(request.json, flush=True) jsonRequest = request.json data = jsonRequest["data"]["data"]["message"] print(data, flush=True) send_email()
The send_email functions calls the SendGrid binding with the message payload:
def send_email(): with DaprClient() as d: req_data = { 'metadata': { 'emailTo': emailTo, 'subject': subject }, 'data': data } print(req_data, flush=True) # Create a typed message with content type and body resp = d.invoke_binding(binding_name, 'create', json.dumps(req_data))
where invoke_binding is a library function from the Dapr client. In the previous cases, we had called the endpoints directly; here we use a function already implemented for us.
The project has three different apps, in Go, Python and Node. We have to build them and then utilise the dapr run
command to start these apps.
First, make sure that your component definitions are present under $HOME/.dapr/components
if you're on Linux and under %USERPROFILE%\.dapr\components
if you're using Windows. This is because the dapr run
command makes use of the yaml definitions provided here at runtime.
You can find the component definitions I've used in this project under components
of the root directory. The password and api keys have been removed and will need to be provided at runtime. In Kubernetes, you can make use of Secrets for these values. Check out secret stores component of Dapr!
After the components are created with the correct fields, we can build and run the individual apps.
- Go inside the
go
directory and build the project Make sure you havegorilla/mux
package installed. If not, run the following command:
go get -u github.com/gorilla/mux
- Build the app.
go build go_events.go
- Run Dapr
dapr run --app-id go-events --app-port 6000 --dapr-http-port 3503 ./go-events
- Install required dependencies.
pip3 install wheel python-dotenv flask_cors flask
- Set environment variable for Flask.
#Linux/Mac OS:
export FLASK_RUN_PORT=5000
#Windows:
set FLASK_RUN_PORT=5000
- Start Dapr.
dapr run --app-id messages --app-port 5000 --dapr-http-port 3501 flask run
- Install dependencies.
npm install
- Start Dapr.
dapr run --app-id controller --app-port 3000 --dapr-http-port 3500 node node_controller.js