> >
.NET core4 min read16 Oct 2023

Running Dapr Locally for Microservices Communication

Microservices architectures have become the backbone of modern applications, allowing for scalable, independent, and modular development. However, microservices communication, state management, and event-driven interactions can be complex. Dapr (Distributed Application Runtime) simplifies these challenges by providing a set of building blocks to enable seamless microservices communication.

This article walks through installing and configuring Dapr CLI, implementing service-to-service communication, and leveraging Dapr for state management and pub/sub messaging.


1. Installing and Configuring Dapr CLI

Dapr CLI is a command-line tool that simplifies running and managing Dapr applications. Follow these steps to install and configure it locally.

Step 1: Install Dapr CLI

On Windows (PowerShell):

wget -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | Invoke-Expression

On macOS and Linux:

wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash

After installation, verify that Dapr CLI is installed:

dapr --version

Step 2: Initialize Dapr Runtime Locally

Run the following command to initialize Dapr with a self-hosted environment:

dapr init

This installs the Dapr runtime, Redis for state management, and Zipkin for distributed tracing.

To verify the installation, use:

dapr list

If Dapr is running correctly, you should see an output listing the Dapr system services.


2. Implementing Service-to-Service Communication

Step 1: Create Two Sample Microservices

We'll create two microservices: Order Service (sender) and Payment Service (receiver). These services will communicate using Dapr's service invocation API.

Order Service (Python Flask Example)

from flask import Flask, request
import requests

app = Flask(__name__)

@app.route("/create-order", methods=["POST"])
def create_order():
    order_data = {"orderId": "1234", "amount": 100}
    service_url = "http://localhost:3500/v1.0/invoke/payment-service/method/process-payment"
    response = requests.post(service_url, json=order_data)
    return response.json()

if __name__ == "__main__":
    app.run(port=5001)

Payment Service (Node.js Example)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/process-payment', (req, res) => {
    console.log('Processing payment for order:', req.body.orderId);
    res.json({ status: 'Payment processed successfully' });
});

app.listen(5002, () => console.log('Payment service running on port 5002'));

Step 2: Run Microservices with Dapr Sidecar

Start the Order Service:

dapr run --app-id order-service --app-port 5001 --dapr-http-port 3500 -- python order_service.py

Start the Payment Service:

dapr run --app-id payment-service --app-port 5002 --dapr-http-port 3600 -- node payment_service.js

Now, you can test the communication by sending a request to Order Service:

curl -X POST http://localhost:5001/create-order

If successful, you should see the payment service log:

Processing payment for order: 1234

3. Using Dapr for State Management

Dapr provides key-value state management out-of-the-box. By default, it uses Redis, but it supports various backends like Azure Cosmos DB, AWS DynamoDB, and PostgreSQL.

Step 1: Implement State Management in Payment Service

Modify the Payment Service to store order payment status using Dapr's state management API.

Update payment_service.js

const fetch = require('node-fetch');
const express = require('express');
const app = express();
app.use(express.json());

const DAPR_STATE_STORE = "statestore";

app.post('/process-payment', async (req, res) => {
    const { orderId, amount } = req.body;
    const paymentStatus = { orderId, amount, status: 'Completed' };

    await fetch(`http://localhost:3600/v1.0/state/${DAPR_STATE_STORE}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify([{ key: orderId, value: paymentStatus }])
    });

    res.json({ message: 'Payment stored successfully' });
});

app.listen(5002, () => console.log('Payment service running on port 5002'));

Step 2: Retrieve Stored Payment Status

To retrieve stored payment details, add the following endpoint:

app.get('/payment-status/:orderId', async (req, res) => {
    const orderId = req.params.orderId;
    const response = await fetch(`http://localhost:3600/v1.0/state/${DAPR_STATE_STORE}/${orderId}`);
    const data = await response.json();
    res.json(data);
});

Start the services and test:

curl http://localhost:5002/payment-status/1234

You should get a JSON response with the payment details.


4. Using Dapr for Publish-Subscribe Messaging

Dapr supports pub/sub messaging using different brokers (Redis, Kafka, RabbitMQ). By default, it uses Redis.

Step 1: Configure Pub/Sub Component

Create a file pubsub.yaml in components/ directory:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
  namespace: default
spec:
  type: pubsub.redis
  version: v1
  metadata:
    - name: redisHost
      value: localhost:6379

Step 2: Publish Events from Order Service

Modify order_service.py:

@app.route("/create-order", methods=["POST"])
def create_order():
    order_data = {"orderId": "1234", "amount": 100}
    pubsub_url = "http://localhost:3500/v1.0/publish/pubsub/orders"
    requests.post(pubsub_url, json=order_data)
    return {"message": "Order published"}

Step 3: Subscribe to Events in Payment Service

Modify payment_service.js:

app.post('/orders', (req, res) => {
    console.log('Received Order:', req.body);
    res.sendStatus(200);
});

Start services with:

dapr run --app-id payment-service --app-port 5002 --dapr-http-port 3600 --components-path ./components -- node payment_service.js

Test by sending:

curl -X POST http://localhost:5001/create-order

You should see the payment service log received events.


Conclusion

Dapr simplifies microservices communication, state management, and pub/sub messaging without modifying business logic. Running Dapr locally helps developers prototype and test distributed applications efficiently before deploying to production.

Key takeaways:

  • Service-to-service communication is made easy via Dapr’s sidecar model.
  • State management abstracts away the complexity of choosing a storage backend.
  • Pub/Sub messaging enhances event-driven microservices.

Dapr is a powerful addition to any microservices architecture, improving scalability and resilience while reducing boilerplate code.

Sandip Mhaske
Senior Engineer • Author of 675 guides • Follow for System Design & .NET deep-dives
About