Open Source .NET Aspire and Dapr: What Are They and How Do They Complement Each Other For Distributed Apps?
This article covers both .NET Aspire and Dapr, the problems they solve, their differences, and why .NET developers should use them together.
Join the DZone community and get the full member experience.
Join For FreeOver the last weeks, I've seen many questions from the .NET community on how .NET Aspire compares to Dapr, the Distributed Application Runtime. Some say the features appear to be very similar and think Aspire is a replacement for Dapr (which it isn’t). The TL;DR is: .NET Aspire is a set of tools for local development, while Dapr is a runtime offering building block APIs and is used during local development and running in production. This article covers both .NET Aspire and Dapr, the problems they solve, their differences, and why .NET developers should use them together when building distributed applications that can run on any cloud.
What Problem Does .NET Aspire Solve?
.NET Aspire was created to solve a problem that many distributed application developers face: running and debugging multiple cloud native apps locally. .NET developers can now use a language they understand well, C#, to configure their microservices and infrastructure dependencies, such as state stores and message brokers. Developers can run and debug their .NET applications using Visual Studio or VS Code with the C# Dev Kit extension.
How Does .NET Aspire Work?
To run and debug multiple .NET applications locally, developers need a way to configure all the applications and related infrastructure dependencies. With .NET Aspire, this is done with an App Host project that orchestrates all the resources.
Source: .NET Aspire orchestration overview
This is the csproj file of an App Host project for a .NET Aspire + Dapr solution that consists of two Dapr applications that communicate asynchronously via a message broker:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.0.1" />
<PackageReference Include="Aspire.Hosting.Dapr" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\checkout\DaprAspirePubsub.Checkout.csproj" />
<ProjectReference Include="..\order-processor\DaprAspirePubsub.OrderProcessor.csproj" />
</ItemGroup>
</Project>
This is the Program.cs file of the App Host project:
var builder = DistributedApplication.CreateBuilder(args);
var pubsubComponent = builder.AddDaprPubSub("orderpubsub");
builder.AddProject("checkout")
.WithDaprSidecar()
.WithReference(pubsubComponent);
builder.AddProject("order-processor")
.WithDaprSidecar()
.WithReference(pubsubComponent);
builder.Build().Run();
Once the App Model is defined in the App Host project, and this project is set as the start-up project, the entire solution can be run and debugged as usual. The App Host project is started as a separate process and orchestrates which other resources will be started.
.NET Aspire Terminology
- App model: A collection of resources that defines the distributed application
- App host: The .NET project that orchestrates the app model; by convention, these projects use the AppHost suffix in their name
- Resource: A part of a distributed application; this can be a .NET project, a container, an executable, a cloud resource, or an external service
- Reference: A connection between resources, such as a dependency to a state store, or a reference to another .NET project
- Integration (previously Component): A .NET library that delivers a specific integration with an infrastructure resource, such as a Kafka message broker or a Cosmos DB database; Developers can add these components as NuGet packages to the AppHost or application projects. Components typically register a specific client object in the dependency injection container, so developers can use that client in their application code. Both .NET Aspire and Dapr use the term components, but they are very different in their implementation.
Example for Adding the .NET Aspire Cosmos DB Integration
1. First add the Cosmos DB integration to the project that requires a connection with Cosmos DB:
dotnet add package Aspire.Microsoft.Azure.Cosmos
2. The component can now be registered in the Program.cs file of in the project:
builder.AddAzureCosmosClient("cosmosdb");
3. The CosmosDB client can now be used in the application code:
public class ExampleService(CosmosClient client)
{
// Use client...
}
For running and debugging the above example, a corresponding hosting package needs to be added to the AppHost.
4. Add the Cosmos DB hosting package:
dotnet add package Aspire.Hosting.Azure.CosmosDB
5. Register the Cosmos DB host in the AppHost project:
var builder = DistributedApplication.CreateBuilder(args);
var cosmos = builder.AddAzureCosmosDB("cosmos");
var cosmosdb = cosmos.AddDatabase("cosmosdb");
var exampleProject = builder.AddProject()
.WithReference(cosmosdb);
Note that this configuration requires a Cosmos DB resource in Azure to run the code locally. For this specific resource, a local emulator is available that can be added with cosmosdb.RunAsEmulator();
.
What Do You Need To Run .NET Aspire With Dapr?
- .NET 8
- The .NET Aspire workload
- Visual Studio 22 17.10 or VS Code with the C# Dev Kit
- A container runtime such as Docker Desktop or Podman
- Dapr CLI
What Is Dapr (Distributed Application Runtime)?
Dapr is a runtime that provides a set of APIs developers can use to build distributed applications quickly. These APIs are decoupled from the underlying infrastructure, which enables developers (or platform engineers) to switch between different infrastructure implementations without changing application source code.
Dapr runs in a separate process (sidecar) next to the application, and has built-in observability, resiliency, and security features, all configurable and with default settings. Since all communication between services takes place via Dapr, application developers do not need additional code or dependencies to apply these cross-cutting concerns.
What Problem Does Dapr Solve?
Dapr was created to streamline secure and reliable distributed application development, regardless of the programming language or cloud provider. Developers can use the suite of Dapr APIs via HTTP/gRPC, or via the many language SDKs, to build microservices quickly. Dapr is not only used during development, Dapr is also running in production, providing secure and reliable communication between services and resources on any cloud or on-premise.
Developers claim to save about 30% of development time when using Dapr. This is due to the built-in cross-cutting concerns, and the decoupling of the APIs and underlying infrastructure. Developers can use in-memory or container-based resources locally, and switch to cloud-based or on-premise resources by replacing Dapr component files (YAML) when moving to production. An example of a Dapr component file is provided in the next section.
How Does Dapr Work?
As mentioned before, Dapr runs in a separate process next to your application. For each application in your distributed application landscape, a Dapr sidecar is present.
During application development, the Dapr CLI with the Multi-App-run feature is used to run multiple Dapr applications at the same time and configure resources the applications depend on.
This is the multi-app run file for two Dapr applications that communicate asynchronously via a message broker:
version: 1
common:
resourcesPath: resources
apps:
- appID: order-processor
appDirPath: ./order-processor/
appPort: 7006
command: ["dotnet", "run"]
- appID: checkout-sdk
appDirPath: ./checkout/
command: ["dotnet", "run"]
The resourcesPath
attribute points to a resources folder. This folder contains a Dapr component file (pubsub.yaml) that describes which specific Pub/Sub implementation Dapr will use:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
Once the multi-app run file and component files are in place, all Dapr apps can be run with the Dapr CLI: dapr run -f .
Dapr Terminology
- App/Application: An application that a developer writes and runs
- Building block: A Dapr API that provides a specific functionality, such as State Management, or Pub/Sub, that a developer can use when writing an application
- Configuration: A Dapr setting or policy to change the behavior of Dapr applications or the global behavior of the Dapr control plane
- Sidecar: A type of architecture that Dapr uses. Dapr runs in a separate process, the sidecar, next to your application
- Component: An implementation of one of the Dapr APIs that delivers a specific integration with an infrastructure resource, such as a Kafka message broker or a Cosmos DB database. Dapr Components are configured via YAML files.
Example for Adding the Dapr Cosmos DB component
1. A YAML file (component file) for Azure Cosmos DB is added to the solution:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: mystatestore
spec:
type: state.azure.cosmosdb
version: v1
metadata:
- name: url
value:
- name: masterKey
value:
- name: database
value:
- name: collection
value:
Note that for production use, a secret store should be used instead of plain text values.
2. If the Dapr .NET SDK is used, add the Dapr.AspNetCore NuGet package to the project:
dotnet add package Dapr.AspNetCore
3. In the Program.cs file, the DaprClient
can now be registered:
builder.Services.AddDaprClient();
4. The DaprClient
can now be used in application code:
public class ExampleService(DaprClient daprClient)
{
await daprClient.SaveStateAsync("mystatestore", order.Id, order.ToString())
}
Note that there is no Cosmos DB specific client code used in the application. The application only uses the Dapr SDK, and even this is optional, since raw HTTP/gRPC calls can be made to the Dapr sidecar. The Dapr sidecar is responsible for communicating with the Cosmos DB resource.
What Do You Need To Run .NET Applications Using the Dapr CLI With Multi-App Run?
- .NET Core 3.1 or .NET 5 or higher
- A terminal
- Any IDE that supports .NET
- A container runtime such as Docker Desktop or Podman
- Dapr CLI
.NET Aspire and Dapr Similarities and Differences
The goal of .NET Aspire and Dapr is similar: make it easier and quicker for developers to build cloud native distributed applications. The way both projects are doing this is very different, and the scope of Dapr is much wider. .NET Aspire is focused on the local development experience, while Dapr covers both local development and running distributed apps in production. Let’s take a look at some feature similarities and differences in the next section. Note that both projects are extensive and this is not a comparison of all the features. It’s also not completely fair to compare the two, since .NET Aspire is a "local dev tool" and Dapr is a runtime. Use this information to understand how you can combine the two solutions to speed up your distributed application development.
Feature Similarities
Some features that appear very similar between .NET Aspire and Dapr are as follows:
Service Discovery
.NET Aspire provides local service discovery via a configuration-based endpoint resolver in the AppHost project by adding Aspire references to the .NET projects. When deploying to Kubernetes, a Service resource definition YAML file needs to be created, and the AppHost code requires to be updated to use the DNS SRV service endpoint resolver.
Dapr provides service discovery via a name resolution component, and unique IDs for the Dapr sidecars. When running locally, name resolution can be configured to use either mDNS or SQLite. In production, this is handled by Kubernetes automatically, or HashiCorp Consul can be configured when running on VMs.
Observability
.NET Aspire uses the .NET OpenTelemetry SDK to enable observability. Dapr uses the OpenTelemetry protocol as well (the Zipkin protocol can also be configured).
Resiliency
Both .NET Aspire and Dapr enable development of resilient applications, but the implementation is quite different. With NET Aspire (or any .NET project), resiliency for HTTP communication can be added via the Microsoft.Extensions.Http.Resilience NuGet package. Some .NET Aspire components also allow resiliency configuration, but those are Aspire component specific and set in code. The application itself is responsible for resilient connections with services or resources it communicates with.
Dapr has built-in resiliency, and the Dapr sidecar is responsible for resilient connections between services and resources. This means that you don't need extra packages or application code. Dapr comes with default settings for retries and circuit breakers and can be customized with YAML files. These resiliency policies can be scoped to specific services or resources, so fine-grained control is possible.
The Concept of Components/Integrations
Although the implementation and scope is completely different, the concept of components to specify the dependency to other resources is similar. With .NET Aspire, NuGet packages are used to inject resource-specific libraries that enable the applications to use certain resources.
With Dapr, developers use component YAML files to configure the underlying resources. The .NET Aspire components, however, do not share a common API as Dapr components do. So, with .NET Aspire, developers still need to use resource-specific SDKs and cannot swap components without changing application source code.
Feature Differences
Although there are some similarities between .NET Aspire and Dapr, they are very different solutions for building distributed applications quicker.
APIs
The most significant difference is that Dapr offers many building block APIs, which are decoupled from the underlying infrastructure. Developers use these APIs to build distributed applications quickly. This is something that .NET Aspire does not offer. With Aspire, you still need to use resource-specific SDKs in your source code when interacting with state stores, secret stores, message brokers etc.
Languages
Another significant difference is that since Dapr runs in a sidecar, and offers the APIs via an HTTP/gRPC interface, Dapr can be used with any programming language. .NET Aspire is really focused on .NET, as the name implies. It is possible to run .NET Aspire apps that include a front-end with React/Vue/Angular, but it always requires an AppHost .NET project.
Security Policies
Dapr has built-in security policies that can be customized via YAML files. The security policies allow developers to configure which applications have access to other applications or resources. These policies can be applied globally or scoped to specific resources.
.NET Aspire does not provide security policies to control service-to-service or service-to-resource communication.
Application Deployment
.NET Aspire offers integrated application deployment options via Visual Studio (right-click publish), or the Azure Developer CLI (azd). Currently, these options are focused on Azure, limited to Azure Container Apps, but other environments will be added over time, according to the docs. The deployment process involves the creation of manifest files (JSON) that describe the resources. Kubernetes deployments are also supported, but this requires mapping the .NET Aspire JSON manifest files to Kubernetes YAML files.
Dapr does not provide integrated application deployment options with any IDE. The Dapr control plane can be installed on Kubernetes via the Dapr CLI, but this does not include application deployment. Since Dapr applications are typically run on Kubernetes, container images are created with tools as Docker, Skaffold, or Tilt. Deployment of the containers is done via CI/CD tooling to either a managed Kubernetes environment in the cloud, or a Kubernetes cluster on premise. Dapr applications can also be deployed to Azure Container Apps using the Azure CLI.
Dashboard
Although both .NET Aspire and Dapr offer dashboards, the Aspire dashboard to view resources, console logs, traces, and metrics is more comprehensive compared to the Dapr dashboard which is limited to Dapr resources only.
The Aspire dashboard also is sleek compared to the Dapr dashboard, which was designed by a back-end developer.
There are other Dapr tools available, such as Diagrid Conductor Free, that offers extensive features including cluster management, application monitoring and alerting with in-depth metrics, and an advisor for best practices, security, and reliability.
Better Together
If you’re developing distributed apps in .NET, Aspire offers a really smooth experience for local development. You don’t have to choose between .NET Aspire or Dapr to build distributed applications quicker. These two solutions complement each other, and save you even more development time.
Why are they better together? Because .NET Aspire gives you easy composability of your apps using C# and effortless debugging, while Dapr provides the building block APIs and built-in cross-cutting concerns such as security and resiliency. The Dapr APIs allow you to quickly build and run applications without depending on specific infrastructure SDKs in your code. This enables you to switch components between environments (local vs production), and even between different cloud providers, without requiring any changes to application code. The policies for security and resiliency you get with Dapr help you to develop production grade distributed applications.
More Info
Watch the .NET Aspire introduction video by David Fowler and Phillip Hoff at one of the Dapr Community Calls.
Published at DZone with permission of Marc Duiker. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments