Table of Contents
- Introduction
- .NET Aspire Overview
- Dapr Overview
- Setting Up the Project
- Understanding Dapr Building Blocks
- Pub-Sub
- HTTP Invocation
- State Management
- Actor Model
- Overview of apphost.cs in .NET Aspire
- Advantages of Combining .NET Aspire and Dapr
- Conclusion
1. Introduction
Building scalable microservices comes with challenges like managing the state, handling inter-service communication, and ensuring resilience. This article explores how .NET Aspire and Dapr simplify these concerns by providing a seamless integration of cloud-native features.
Source code here
2. .NET Aspire Overview
.NET Aspire is a cloud-native development stack that helps developers build distributed applications with built-in observability, service discovery, and lifecycle management. It provides:
- Service Composition: Easily compose microservices with service defaults.
- Observability: Native support for logs, metrics, and traces.
- Integrated Hosting: Applications can run locally with cloud-compatible configurations.
.NET Aspire is particularly useful for microservices that need to scale efficiently in Kubernetes or cloud-based environments.
3. Dapr Overview
Dapr (Distributed Application Runtime) is an open-source, event-driven runtime designed for microservices. It provides:
- Building Blocks for Microservices, including:
- Pub-Sub Messaging: Asynchronous event-driven architecture.
- Service Invocation: Secure service-to-service communication.
- State Management: Store data across microservices.
- Actor Model: Encapsulate logic in stateful, distributed actors.
- Platform Agnostic: Can run on Kubernetes, VMs, or even locally.
- Cloud and Language Agnostic: Works with any cloud provider and supports multiple programming languages.
Why Combine .NET Aspire with Dapr?
- .NET Aspire focuses on simplifying cloud-native application development.
- Dapr focuses on simplifying microservices communication and state management.
- Together, they provide a powerful and scalable foundation for microservices.
4. Setting Up the Project
The repository is structured as follows:
- AspireAndDaprClientVerify: Publishes messages to the Pub-Sub topic.
- AspireAndDaprVerify.AppHost: Contains API endpoints and subscriber logic.
- AspireAndDaprVerify.ServiceDefaults: Defines reusable configurations.
Before running the project, ensure you have:
- .NET 8 or later installed
- Dapr CLI installed (dapr --version)
- Docker running (for local Dapr components)
5. Understanding Dapr Building Blocks
5.1. Pub-Sub Messaging. Asynchronous Event-Driven Architecture
The Publish-Subscribe (Pub-Sub) pattern enables event-driven communication between microservices. It decouples services, allowing them to communicate asynchronously without knowing each other’s details.
Why Use Pub-Sub?
- Loose Coupling: Publishers and subscribers don’t need to be aware of each other.
- Scalability: Events can be consumed by multiple subscribers.
- Resilience: If a service is down, it can process events later when it recovers.
Dapr Pub-Sub Flow
- A publisher sends a message to a topic.
- Dapr routes the message to all subscribed services.
- Subscribers process the message asynchronously.
Dapr Pub-Sub Implementation
Publishing a Message
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly DaprClient _daprClient;
public WeatherForecastController(ILogger<WeatherForecastController> logger, DaprClient daprClient)
{
_logger = logger;
_daprClient = daprClient;
}
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var result= await _daprClient.InvokeMethodAsync<IEnumerable< WeatherForecast>>(httpMethod: HttpMethod.Get, "aspireanddaprverify", "weatherforecast");
await _daprClient.PublishEventAsync("servicebus-pubsub", "ganeshmahadev", result.First());
return result;
}
}
Subscribing to a Topic
[Route("subscribe")]
[ApiController]
public class SubscribeController : ControllerBase
{
[HttpPost]
[Topic("azure-servicebus-subscription", "ganeshmahadev")]
public IActionResult SubscribeToQueue([FromBody] WeatherForecast message)
{
// Process the message
Console.WriteLine($"Received message: {message}");
return Ok();
}
}
The subscriber listens for messages without needing to know the publisher’s details.
Pub-Sub Component Configuration (azure-servicebus-subscription.yaml)
apiVersion: dapr.io/v1alpha1
kind: Subscription
metadata:
name: azure-servicebus-subscription
spec:
topic: ganeshmahadev
route: /subscribe # Route defined in the controller
pubsubname: servicebus-pubsub
servicebus-pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: servicebus-pubsub
namespace: default
spec:
type: pubsub.azure.servicebus
version: v1
metadata:
- name: connectionString
value: ""
Register your dependencies in the App.Host project in the program.cs
var serviceBus = builder.AddDaprPubSub("servicebus-pubsub", new DaprComponentOptions
{
LocalPath = "component/servicebus-pubsub.Yaml"
});
var sb1 = builder.AddDaprPubSub("azure-servicebus-subscription", new DaprComponentOptions
{
LocalPath = "component/azure-servicebus-subscription.yaml"
});
5.2. Service Invocation. Secure Service-to-Service Communication
Service-to-service communication is crucial in microservices, but managing service discovery, retries, and load balancing can be complex.
Why Use Dapr Service Invocation?
- No Hardcoded URLs: Services invoke each other by name.
- Automatic Retries & Load Balancing: Handles transient failures.
- Secure Communication: Supports mTLS for encrypted communication.
How Service Invocation Works?
- A service calls another via Dapr’s service invocation API.
- Dapr resolves the service name dynamically and forwards the request.
- The target service processes the request and sends a response.
Calling a Service Using Dapr
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly DaprClient _daprClient;
public WeatherForecastController(ILogger<WeatherForecastController> logger, DaprClient daprClient)
{
_logger = logger;
_daprClient = daprClient;
}
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var result= await _daprClient.InvokeMethodAsync<IEnumerable< WeatherForecast>>(httpMethod: HttpMethod.Get, "aspireanddaprverify", "weatherforecast");
await _daprClient.PublishEventAsync("servicebus-pubsub", "ganeshmahadev", result.First());
return result;
}
Join the conversation! Your thoughts help the community grow.