Featured image of post Dapr Unveiled: Getting Started with Core Concepts

Dapr Unveiled: Getting Started with Core Concepts

Jump into the world of Dapr and microservices in this hands-on guide. Learn how Dapr simplifies microservices development as we build a real application using Python and JavaScript. Explore the sidecar pattern, service invocation, and see Dapr in action.

Introduction

Hey there, Everyone! Welcome back to our Dapr journey. If you’re joining us for the first time, no worries – hop right in! In our last article, we explored why microservices are becoming increasingly popular and how Dapr can help tackle some of the challenges they bring. Today, we’re rolling up our sleeves and diving into the nuts and bolts of Dapr.

Quick Recap

Just to make sure we’re all on the same page, let’s quickly revisit what we covered last time:

  1. We talked about the limitations of monolithic architectures and why many developers are moving towards microservices.
  2. We discussed the challenges that come with microservices – things like service communication, data management, and operational complexity.
  3. We introduced Dapr as a toolkit designed to simplify microservices development.
  4. We took a high-level tour of Dapr’s key features and building blocks.

If any of this sounds unfamiliar, you might want to check out our first article for a more detailed overview.

What’s Coming Up?

In this article, we’re taking our first steps into the world of practical Dapr development. Here’s what you can expect:

  1. We’ll demystify the sidecar pattern – a key concept in understanding how Dapr works.
  2. You’ll set up your local development environment for Dapr.
  3. We’ll take a closer look at some of Dapr’s core building blocks, focusing on service invocation and state management.
  4. You’ll create your very first Dapr application – a simple but powerful demonstration of Dapr in action.

By the end of this article, you’ll have Dapr running on your machine and you’ll have built a real, working microservice using Dapr’s building blocks. You’ll see firsthand how Dapr simplifies service-to-service communication and state management in a distributed system.

So, grab your favorite beverage, fire up your code editor, and let’s dive into the world of Dapr development. Trust me, it’s going to be an exciting ride!

Understanding the Sidecar Pattern

Before we dive into Dapr’s implementation, let’s talk about the sidecar pattern - a key concept in understanding how Dapr works.

What is a Sidecar?

The term “sidecar” originates from motorcycles. A sidecar is an attachment to the side of a motorcycle that provides an extra seat. It’s separate from the main vehicle but moves along with it, providing additional functionality without changing the core vehicle.

In software architecture, the sidecar pattern follows a similar principle:

  1. Main Application: This is like the motorcycle – it’s your core service or application.
  2. Sidecar: This is a separate process or container that runs alongside your main application.
  3. Shared Lifecycle: The sidecar is deployed and scales with your main application. When your app starts, the sidecar starts. When your app stops, so does the sidecar.
  4. Extended Functionality: The sidecar provides additional features or capabilities to your main application without you having to modify its code.

Dapr and the Sidecar Pattern

Dapr implements the sidecar pattern to provide powerful functionality to your applications. Here’s how it works:

  1. Dapr Sidecar: When you run an application with Dapr, it creates a sidecar process (or container in Kubernetes) that runs alongside your application.

  2. API Communication: Your application communicates with the Dapr sidecar via a simple HTTP or gRPC API.

  3. Building Blocks: The Dapr sidecar provides all of Dapr’s building blocks – service invocation, state management, pub/sub messaging, and more.

  4. Infrastructure Abstraction: The sidecar handles communication with backend services and infrastructure.

Here’s a simple diagram to visualize this:

Dapr Sidecar Pattern

In this diagram, you can see how your application (which can be in any language) communicates with the Dapr sidecar. The sidecar, in turn, interacts with your infrastructure (like databases) using Dapr’s building blocks.

By using the sidecar pattern, Dapr allows you to focus on your business logic while it handles the complex, repetitive tasks of distributed systems. It’s like having an expert systems engineer working alongside each of your microservices, managing all the intricate distributed computing challenges for you.

In the next section, we’ll explain two core components of Dapr: service invocation and state management. These concepts will help you better understand our application, which we’ll build together later in this article.

Core Concepts of Dapr: Service Invocation and State Management

Now that we understand the sidecar pattern and how Dapr implements it, let’s look at two key capabilities that make Dapr shine: Service Invocation and State Management. We’ll use simple Python examples to show how Dapr simplifies these common microservices tasks.

Service Invocation

In a microservices architecture, different services often need to communicate with each other. Service Invocation is the process of one service calling another to request data or trigger an action. It sounds simple, but in practice, it can involve complex tasks like service discovery, load balancing, and error handling.

How Dapr Simplifies Service Invocation

Dapr provides a straightforward HTTP/gRPC API for service invocation. Here’s how it works:

  1. Your service calls the Dapr sidecar to invoke another service.
  2. Dapr handles service discovery to find the target service.
  3. Dapr makes the call to the target service, handling retries and errors.
  4. The response is returned to your service.

Simple Example

Let’s say we have two services: an order service and an inventory service. Without Dapr, invoking the inventory service might look like this:

1
2
3
4
5
6
7
# Without Dapr
import requests

def check_inventory(item_id):
    inventory_service_url = "http://inventory-service:5000"  # Hard-coded URL
    response = requests.get(f"{inventory_service_url}/inventory/{item_id}")
    return response.json()

With Dapr, it becomes:

1
2
3
4
5
6
7
8
9
# With Dapr
import requests
import os

def check_inventory(item_id):
    dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
    dapr_url = f"http://localhost:{dapr_port}"
    response = requests.get(f"{dapr_url}/v1.0/invoke/inventory-service/method/inventory/{item_id}")
    return response.json()

Notice how Dapr simplifies service invocation:

  1. No hard-coded service URLs - Dapr handles service discovery.
  2. Consistent invocation pattern across all service calls.
  3. Built-in retries, timeouts, and error handling.

State Management

State management involves storing and retrieving data that your application needs to remember across transactions or sessions. In a microservices architecture, managing state can be challenging because data might need to be consistent across multiple services and databases.

How Dapr Simplifies State Management

Dapr provides a simple key-value interface for state management, abstracting away the complexities of different databases. Here’s how it works:

  1. Your service uses Dapr’s state management API to save or retrieve state.
  2. Dapr handles the interaction with the underlying database.
  3. You can switch between different state stores (e.g., Redis, Cosmos DB) without changing your application code.

Simple Example

Let’s see how we might use state management in our order service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Without Dapr
import redis
import json

def save_order(order_id, order_data):
    r = redis.Redis(host='localhost', port=6379, db=0)
    r.set(f"order:{order_id}", json.dumps(order_data))

def get_order(order_id):
    r = redis.Redis(host='localhost', port=6379, db=0)
    order_data = r.get(f"order:{order_id}")
    return json.loads(order_data) if order_data else None

With Dapr, it becomes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# With Dapr
import requests
import os

def save_order(order_id, order_data):
    dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
    dapr_url = f"http://localhost:{dapr_port}"
    state = [{"key": f"order:{order_id}", "value": order_data}]
    requests.post(f"{dapr_url}/v1.0/state/statestore", json=state)

def get_order(order_id):
    dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
    dapr_url = f"http://localhost:{dapr_port}"
    response = requests.get(f"{dapr_url}/v1.0/state/statestore/order:{order_id}")
    return response.json() if response.status_code == 200 else None

Here’s what makes the Dapr approach powerful:

  1. We’re using a generic state store API instead of Redis-specific code.
  2. This same code works whether your state store is Redis, Azure Cosmos DB, MongoDB, or any other supported database.
  3. If you decide to switch state stores, you don’t need to change your application code at all. You’d only need to update Dapr’s configuration.

By providing these abstractions, Dapr significantly simplifies microservices development. It allows developers to focus on business logic while Dapr handles the complexities of distributed systems.

In the next sections, we’ll set up our development environment and start building a real application using these Dapr concepts. You’ll see firsthand how Dapr makes it easier to build robust, scalable microservices.

Setting Up Your Development Environment

Before we dive into building our Dapr application, let’s set up our development environment. We’ll focus on the tools specific to Dapr development, assuming you have some common development tools already installed.

Prerequisites

To follow along with this tutorial, you’ll need:

  1. Docker: Docker Desktop for Mac and Windows, or Docker Engine for Linux.

  2. Python 3.12: We’re using Python 3.12 in this tutorial, though earlier 3.x versions should work as well.

  3. Node.js 20.x: We’ll use Node.js for one of our microservices.

It’s a good practice to use virtual environments in Python. Here’s how to set one up:

  1. Open a terminal or command prompt.
  2. Create a new virtual environment:
    1
    
    python -m venv myenv
    
  3. Activate the virtual environment:
    • On Windows:
      1
      
      myenv\Scripts\activate
      
    • On macOS and Linux:
      1
      
      source myenv/bin/activate
      

Installing and Initializing Dapr CLI

Now we’re getting to the heart of our Dapr setup – the Dapr CLI. This tool is essential for local Dapr development, allowing us to initialize Dapr, run our applications with Dapr sidecars, and manage Dapr components. Let’s get it installed and set up!

Installing Dapr CLI

The installation process is slightly different depending on your operating system:

For Windows:
  1. Open PowerShell as administrator and run:
    1
    
    powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
    
For macOS:
  1. Using Homebrew:

    1
    
    brew install dapr/tap/dapr-cli
    
  2. If you don’t have Homebrew, you can use this command:

    1
    
    curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash
    
For Linux:
  1. Run this command:
    1
    
    wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
    

Verification

After installation, verify that the Dapr CLI is installed correctly:

1
dapr --version

You should see the version number of the Dapr CLI.

Initializing Dapr

Now that we have the Dapr CLI installed, let’s initialize Dapr on our local machine:

1
dapr init

This command does several important things:

  1. It pulls necessary Docker images for Dapr.
  2. It sets up default Dapr components for local development.
  3. It starts a few containers that Dapr needs to run.

You’ll see output indicating that Docker images are being pulled and containers are being started. This might take a few minutes, especially if it’s your first time running the command.

Peeking Under the Hood

After initialization, let’s take a look at what Dapr has set up for us. Run:

1
docker ps

You should see output similar to this:

1
2
3
4
CONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS         PORTS                              NAMES
xxxxxxxxxxxxxx dapr/dapr:1.10.0     "./daprd"                2 minutes ago   Up 2 minutes   50001-50002/tcp                    dapr_redis
xxxxxxxxxxxxxx openzipkin/zipkin    "start-zipkin"           2 minutes ago   Up 2 minutes   9410/tcp, 0.0.0.0:9411->9411/tcp   dapr_zipkin
xxxxxxxxxxxxxx redis:alpine         "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   0.0.0.0:6379->6379/tcp             dapr_placement

Let’s break down what these containers are:

  • dapr_redis: A Redis container that Dapr uses for state management and pub/sub messaging in your local environment.
  • dapr_zipkin: Zipkin is a distributed tracing system. Dapr uses it to provide observability features.
  • dapr_placement: This is Dapr’s actor placement service, used for Dapr’s actor building block.

These containers form the backbone of your local Dapr development environment.

Congratulations! You’ve now got the Dapr CLI installed and initialized. This powerful tool will be your constant companion as we dive deeper into Dapr development.

Now that we have our development environment set up, we’re ready to start building our Dapr application. In the next section, we’ll create our first microservices and see how Dapr makes it easy for them to communicate and manage state.

Building Our First Dapr Microservices Application

Now that we’ve explored the core concepts of Dapr and set up our development environment, it’s time to put theory into practice. We’re going to build a simple yet functional microservices application that demonstrates the power and simplicity of Dapr. This hands-on example will show you how Dapr’s building blocks - particularly service invocation and state management - can streamline your microservices development.

Application Architecture Overview

In this tutorial, we’re going to build a simple e-commerce application using Dapr to manage microservices. Our application consists of two main services:

  1. Inventory Service (Python): This service is responsible for managing the inventory of our products. It will:

    • Store and retrieve product quantities using Dapr’s state management.
    • Handle requests to check and update inventory levels.
  2. Order Service (JavaScript): This service processes customer orders. It will:

    • Receive order requests from clients.
    • Check product availability by communicating with the Inventory Service.
    • Place orders and update inventory when there’s sufficient stock.

Here’s how Dapr fits into our architecture:

  • Each service will have its own Dapr sidecar.
  • The Dapr sidecars will handle communication between services using Dapr’s service invocation building block.
  • The Inventory Service will use Dapr’s state management building block to store and retrieve inventory data.

This setup demonstrates Dapr’s language-agnostic nature (Python and JavaScript) and its ability to simplify microservices communication and state management.

Here’s a visual representation of our application architecture:

Dapr Microservices Architecture

In the following sections, we’ll build these services step by step, demonstrating how Dapr simplifies microservices development.

Setting Up the Project Structure

Before we dive into coding our microservices, let’s set up a clean and organized project structure. This will help us keep our code tidy and make it easier to navigate between our services.

Follow these steps to create the necessary directories for our Dapr microservices demo:

  1. Open your terminal or command prompt.

  2. Create the main project directory and navigate into it:

    1
    2
    
    mkdir dapr-microservices-demo
    cd dapr-microservices-demo
    
  3. Create subdirectories for each of our services:

    1
    2
    
    mkdir inventory-service
    mkdir order-service
    

Your project structure should now look like this:

1
2
3
dapr-microservices-demo/
├── inventory-service/
└── order-service/

Here’s what each directory is for:

  • dapr-microservices-demo/: This is our main project directory that will contain all the files and subdirectories for our application.
  • inventory-service/: This directory will contain all the files related to our Python-based Inventory Service.
  • order-service/: This directory will house all the files for our JavaScript-based Order Service.

This structure keeps our services separate, which is a key principle in microservices architecture. It allows us to develop, test, and deploy each service independently.

In the next sections, we’ll populate these directories with the necessary files for each service. We’ll start with the Inventory Service, creating our Python application with Dapr’s state management capabilities.

Creating the Inventory Service (Python)

Now that we have our project structure set up, let’s create our Inventory Service using Python. This service will manage our product inventory, demonstrating Dapr’s state management capabilities.

Setting Up the Python Environment

  1. Navigate to the inventory service directory:

    1
    
    cd inventory-service
    
  2. Create a Python virtual environment (assuming Python 3 is installed):

    1
    
    python -m venv venv
    
  3. Activate the virtual environment:

    • On Windows:
      1
      
      venv\Scripts\activate
      
    • On macOS and Linux:
      1
      
      source venv/bin/activate
      
  4. Install the required dependencies:

    1
    
    pip install flask==3.0.3 requests
    
  5. Ensure the correct Python interpreter is used:

    • When working in your development environment, make sure the Python interpreter is set to the one in your virtual environment (venv). This is crucial for avoiding issues with dependency management and ensuring your code runs as expected. In VS Code, you can select the interpreter by opening the Command Palette (Ctrl+Shift+P or Cmd+Shift+P on macOS), typing “Python: Select Interpreter,” and then choosing the interpreter located in your venv folder.
    • If the Python interpreter for your virtual environment is not listed, enter the path manually:
      • In the Command Palette, after selecting “Python: Select Interpreter,” choose “Enter interpreter path…” and then navigate to the venv/bin/python (or venv/Scripts/python.exe on Windows) in your project’s directory.

Creating the Inventory Service Code

Create a new file named inventory_service.py in the inventory-service directory and add 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
30
31
32
33
34
35
36
37
38
from flask import Flask, request, jsonify
import os
import requests

app = Flask(__name__)

# Dapr configuration
DAPR_HTTP_PORT = os.getenv("DAPR_HTTP_PORT", 3500)
DAPR_STATE_STORE_NAME = "statestore"

# Helper functions for state management
def get_state(key):
    url = f"http://localhost:{DAPR_HTTP_PORT}/v1.0/state/{DAPR_STATE_STORE_NAME}/{key}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    return None

def set_state(key, value):
    url = f"http://localhost:{DAPR_HTTP_PORT}/v1.0/state/{DAPR_STATE_STORE_NAME}"
    data = [{"key": key, "value": value}]
    requests.post(url, json=data)

# API endpoints
@app.route('/inventory/<item_id>', methods=['GET'])
def get_inventory(item_id):
    quantity = get_state(item_id)
    return jsonify({"itemId": item_id, "quantity": quantity or 0})

@app.route('/inventory/<item_id>', methods=['POST'])
def update_inventory(item_id):
    data = request.json
    new_quantity = data.get('quantity', 0)
    set_state(item_id, new_quantity)
    return jsonify({"itemId": item_id, "quantity": new_quantity})

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)

Understanding the Code

Let’s break down the key parts of our Inventory Service:

  1. Dapr Configuration: We set up the Dapr HTTP port and state store name. These will be used to interact with the Dapr sidecar.

  2. State Management:

    • get_state(): This function retrieves the state (inventory quantity) for a given item ID using Dapr’s state management API.
    • set_state(): This function updates the state for a given item ID.
  3. API Endpoints:

    • GET /inventory/<item_id>: Retrieves the current quantity of an item.
    • POST /inventory/<item_id>: Updates the quantity of an item.
  4. Dapr Integration: Instead of directly interacting with a database, we use Dapr’s state management API. This abstraction allows us to easily switch between different state stores (e.g., Redis, MongoDB) without changing our application code.

In the next section, we’ll create our Order Service using JavaScript, which will interact with this Inventory Service through Dapr’s service invocation.

Creating the Order Service (JavaScript)

Now that we have our Inventory Service set up, let’s create our Order Service using JavaScript (Node.js). This service will process orders and interact with the Inventory Service using Dapr’s service invocation.

Setting Up the Node.js Environment

  1. Navigate to the order service directory:

    1
    
    cd ../order-service
    
  2. Initialize a new Node.js project:

    1
    
    npm init -y
    
  3. Install the required dependencies:

    1
    
    npm install express axios
    

Creating the Order Service Code

Create a new file named order_service.js in the order-service directory and add 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
30
31
32
33
34
35
const express = require('express');
const axios = require('axios');

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

const DAPR_HTTP_PORT = process.env.DAPR_HTTP_PORT || 3501; // Ensure this port matches the Dapr HTTP port
const INVENTORY_SERVICE_NAME = "inventory-service";

app.post('/order', async (req, res) => {
    const { itemId, quantity } = req.body;

    try {
        // Invoke the inventory service using Dapr
        const inventoryResponse = await axios.get(`http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/${INVENTORY_SERVICE_NAME}/method/inventory/${itemId}`);
        const availableQuantity = inventoryResponse.data.quantity;

        if (availableQuantity >= quantity) {
            // Update inventory
            await axios.post(`http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/${INVENTORY_SERVICE_NAME}/method/inventory/${itemId}`, {
                quantity: availableQuantity - quantity
            });

            res.json({ message: `Order placed for ${quantity} units of item ${itemId}` });
        } else {
            res.status(400).json({ message: `Insufficient inventory for item ${itemId}` });
        }
    } catch (error) {
        console.error('Error processing order:', error);
        res.status(500).json({ message: 'Error processing order', error: error.message });
    }
});

const PORT = process.env.PORT || 5002; // Ensure this port matches the Dapr app port
app.listen(PORT, () => console.log(`Order service listening on port ${PORT}`));

Understanding the Code

Let’s break down the key parts of our Order Service:

  1. Dapr Configuration: We set up the Dapr HTTP port and the name of the Inventory Service. These will be used for service invocation through Dapr.

  2. Order Processing Endpoint: The POST /order endpoint handles order requests. It performs the following steps:

    • Checks inventory availability by invoking the Inventory Service.
    • If sufficient inventory exists, it updates the inventory and places the order.
    • If inventory is insufficient, it returns an error message.
  3. Dapr Service Invocation: We use Dapr’s service invocation API to communicate with the Inventory Service. This is done through HTTP requests to the Dapr sidecar, which then routes the request to the appropriate service.

In the next section, we’ll use the Dapr CLI to run and test our application, seeing how these two services work together through Dapr.

Running and Testing Our Dapr Application

Now that we have both our Inventory Service and Order Service ready, let’s use the Dapr CLI to run our application and see how these microservices interact through Dapr.

Starting the Services with Dapr

To start each service with a Dapr sidecar, follow the steps below in separate terminal windows. This approach ensures that each service runs independently, allowing you to monitor and interact with them as needed.

  1. Start the Inventory Service:

    • Open a new terminal window.

    • Navigate to the inventory-service directory by running:

      1
      
      cd /path/to/your/project/inventory-service
      
    • Start the service with Dapr:

      1
      
      dapr run --app-id inventory-service --app-port 5001 --dapr-http-port 3500 python inventory_service.py
      

    Keep this terminal open to view logs.

  2. Start the Order Service:

    • Open another terminal window.

    • Navigate to the order-service directory by running:

      1
      
      cd /path/to/your/project/order-service
      
    • Start the service with Dapr:

      1
      
      dapr run --app-id order-service --app-port 5002 --dapr-http-port 3501 node order_service.js
      

    Keep this terminal open to view logs.

  3. Prepare for Testing:

    • Open a third terminal window.
    • This terminal will be used for testing the services. Make sure that both services are up and running before proceeding with Testing the Application section coming up.

Make sure to replace /path/to/your/project/ with the actual path to your project directories.

Let’s break down these Dapr CLI commands:

  • --app-id: A unique identifier for the application in the Dapr runtime.
  • --app-port: The port on which our application is listening.
  • --dapr-http-port: The HTTP port for the Dapr sidecar.

Understanding the Port Mapping

You might have noticed that we’re using different ports for our applications and the Dapr sidecars. Let’s clarify this port mapping:

  • Inventory Service: App port 5000, Dapr sidecar port 3500
  • Order Service: App port 5001, Dapr sidecar port 3501

Here’s what’s happening:

  1. Application Ports (5000 and 5001): These are the ports on which our services (Inventory and Order) are actually running. Our application code listens on these ports.

  2. Dapr Sidecar Ports (3500 and 3501): These are the ports that the Dapr sidecars use to communicate with our applications and with other Dapr-enabled services.

  3. Port Mapping: Dapr creates a mapping between these ports. When a request comes to the Dapr sidecar port (e.g., 3500), Dapr forwards it to the corresponding application port (e.g., 5000).

  4. Why Different Ports?: This separation allows Dapr to intercept and handle cross-cutting concerns (like service discovery, state management, etc.) before passing requests to our application.

  5. Service Invocation: When one service wants to call another through Dapr, it uses the Dapr sidecar port. For example, the Order Service would call http://localhost:3500/... to invoke the Inventory Service, not http://localhost:5000/....

This port mapping is a key part of how Dapr implements the sidecar pattern, allowing it to provide its rich set of features without requiring changes to our application code.

Testing the Application

Now that our services are running with Dapr, let’s test them to see how they work together.

  1. Add inventory: Let’s add some inventory for an item. Run this curl command in the third terminal window we have open earlier:

    1
    
    curl -X POST -H "Content-Type: application/json" -d '{"quantity": 100}' http://localhost:3500/v1.0/invoke/inventory-service/method/inventory/item1
    

    This should return: {"itemId":"item1","quantity":100}

  2. Check inventory: Verify the inventory was added:

    1
    
    curl http://localhost:3500/v1.0/invoke/inventory-service/method/inventory/item1
    

    This should return: {"itemId":"item1","quantity":100}

  3. Place an order: Now, let’s place an order for this item:

    1
    
    curl -X POST -H "Content-Type: application/json" -d '{"itemId": "item1", "quantity": 10}' http://localhost:3501/v1.0/invoke/order-service/method/order
    

    This should return: {"message":"Order placed for 10 units of item item1"}

  4. Check updated inventory: Verify that the inventory has been updated:

    1
    
    curl http://localhost:3500/v1.0/invoke/inventory-service/method/inventory/item1
    

    This should now return: {"itemId":"item1","quantity":90}

What’s Happening Behind the Scenes

  1. Service Discovery: When the Order Service invokes the Inventory Service, it doesn’t need to know its address or port. Dapr handles service discovery, routing the request to the correct service based on the app-id.

  2. State Management: The Inventory Service uses Dapr’s state management API to store and retrieve inventory data. By default, Dapr uses a local Redis container for state storage in development.

  3. Service Invocation: The Order Service uses Dapr’s service invocation building block to communicate with the Inventory Service. This provides a consistent way to call services, regardless of where they’re hosted.

  4. Sidecar Pattern: Each service runs alongside a Dapr sidecar. The sidecar intercepts all network calls, handling the complexities of service-to-service communication and state management.

By using Dapr, we’ve created a microservices application where services can easily communicate and manage state, without needing to implement complex distributed systems patterns ourselves.

Voila! You’ve just built and run your first Dapr-powered microservices application. Congratulations on taking this significant step into the world of modern, distributed application development!

Conclusion

In this article, we’ve taken a deep dive into the world of Dapr (Distributed Application Runtime) and seen firsthand how it simplifies the development of microservices. Let’s recap what we’ve accomplished:

  1. We explored the core concepts of Dapr, including its sidecar pattern, state management, and service invocation capabilities.
  2. We set up a local development environment with the Dapr CLI, enabling us to leverage Dapr’s features in our application.
  3. We built two microservices - an Inventory Service in Python and an Order Service in JavaScript - demonstrating Dapr’s language-agnostic nature.
  4. We used Dapr’s state management to handle inventory data without directly coupling our application to a specific database.
  5. We implemented service-to-service communication using Dapr’s service invocation, abstracting away the complexities of direct network calls.
  6. Finally, we ran and tested our application using the Dapr CLI, seeing how these services interact seamlessly through Dapr.

Through this hands-on experience, we’ve seen how Dapr addresses common challenges in microservices development:

  • It simplifies service discovery and invocation.
  • It provides a consistent interface for state management across different backends.
  • It enables polyglot development, allowing services written in different languages to communicate easily.
  • It abstracts away many of the complexities involved in building distributed systems.

But this is just the beginning of our Dapr journey! In our next article, we’ll explore even more powerful features of Dapr. We’ll dive into Dapr’s publish-subscribe (Pub/Sub) capabilities, allowing for event-driven architectures. Additionally, we’ll take our application to the cloud, demonstrating how to deploy Dapr applications to Azure Container Apps.

By combining Dapr with cloud-native platforms like Azure Container Apps, we’ll see how we can build scalable, resilient, and easily manageable microservices applications that are ready for production environments.

Stay tuned for more Dapr adventures, and happy coding!