CommunityToolkit / CommunityToolkit/Aspire
Resource Management - Dapr & Aspire
- Dominant language
- C#
- Stars
- 627
- Forks
- 196
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 35
Description
### Describe the bug
CommunityToolkit.Aspire.Hosting.Dapr Version=13.0.0
I tried following 3 edge cases:
1. When dependencies are managed outside of Aspire
`
using CommunityToolkit.Aspire.Hosting.Dapr;
var builder = DistributedApplication.CreateBuilder(args);
// The components directory is where the Dapr components will be placed for the Dapr sidecar to consume.
var componentsPath = Path.Combine(builder.AppHostDirectory, "components");
// The YAML file points to an external state store instance
var stateStore = builder.AddDaprStateStore("statestore", new DaprComponentOptions
{
// Path to your Dapr component YAML file - PostgreSQL state store
LocalPath = Path.Combine(componentsPath, "statestore.yaml")
});
// API Service
var apiService = builder.AddProject("apiservice")
.WithHttpHealthCheck("/health")
.WithDaprSidecar(sidecar => sidecar
.WithOptions(new DaprSidecarOptions
{
AppId = "apiservice",
DaprHttpPort = 3500
})
.WithReference(stateStore));
builder.Build().Run();
`
Observations:
- Incomplete resource visibility on Aspire dashboard e.g., PostgreSQL is not available on dashboard
- Add cognitive overhead on developer to know the dependencies and it may not get all benefits of Aspire deployment
2. When dependencies are managed by Aspire
`
using CommunityToolkit.Aspire.Hosting.Dapr;
var builder = DistributedApplication.CreateBuilder(args);
// The components directory is where the Dapr components will be placed for the Dapr sidecar to consume.
var componentsPath = Path.Combine(builder.AppHostDirectory, "components");
// PostgreSQL
var postgres = builder.AddPostgres("postgres")
.WithImageTag("17.6")
.WithContainerName("aspiredapr-demo-postgres")
.WithDataVolume("aspiredapr-demo-postgres-data")
.WithLifetime(ContainerLifetime.Persistent);
var appDb = postgres.AddDatabase("appdb");
// Get the endpoint information for the PostgreSQL component to use in the Dapr component configuration
var postgresEndpoint = postgres.GetEndpoint("tcp");
var stateStore = builder.AddDaprComponent(
"statestore",
"state.postgresql",
new DaprComponentOptions
{
LocalPath = Path.Combine(componentsPath, "statestore.yaml")
})
.WithMetadata("host", postgresEndpoint.Property(EndpointProperty.Host))
.WithMetadata("port", postgresEndpoint.Property(EndpointProperty.Port))
.WithMetadata("database", appDb.Resource.DatabaseName)
.WithMetadata("user", postgres.Resource.UserNameReference)
.WithMetadata("password", postgres.Resource.PasswordParameter!);
// API Service
var apiService = builder.AddProject("apiservice")
.WithReference(appDb)
.WaitFor(postgres)
.WaitFor(appDb)
.WithHttpHealthCheck("/health")
.WithDaprSidecar(sidecar => sidecar
.WithOptions(new DaprSidecarOptions
{
AppId = "apiservice",
DaprHttpPort = 3500
})
.WithReference(stateStore)
.WaitFor(postgres)
.WaitFor(appDb));
builder.Build().Run();
`
Observations:
- Race condition on first run e.g., application fails on first run as the Dapr Sidecar does not wait for dependent resources to reach a ready state before proceeding
- Without a built-in readiness mechanism, the Dapr Sidecar start independently, make the first run unreliable but on subsequent run, it runs fine and the dashboard displays all resource properly
3. When dependencies are managed by Aspire & Explicit Dapr Sidecar
`
var builder = DistributedApplication.CreateBuilder(args);
// The components directory is where the Dapr components will be placed for the Dapr sidecar to consume.
var componentsPath = Path.Combine(builder.AppHostDirectory, "components");
const string appDatabaseName = "appdb";
const string postgresContainerName = "aspiredapr-demo-postgres";
// PostgreSQL
var postgres = builder.AddPostgres("postgres")
.WithImageTag("17.6")
.WithEnvironment("POSTGRES_DB", appDatabaseName)
.WithContainerName(postgresContainerName)
.WithDataVolume("aspiredapr-demo-postgres-data")
.WithLifetime(ContainerLifetime.Persistent);
var appDb = postgres.AddDatabase(appDatabaseName, appDatabaseName);
// Get the endpoint information for the PostgreSQL component to use in the Dapr component configuration
var postgresEndpoint = postgres.GetEndpoint("tcp");
var postgresReady = builder.AddExecutable(
"postgres-ready",
"sh",
builder.AppHostDirectory,
"-c",
"""
until nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do
sleep 1
done
until docker exec aspiredapr-demo-postgres sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U postgres -d postgres -v ON_ERROR_STOP=1 -c "select 1"' >/dev/null 2>&1; do
sleep 1
done
if ! docker exec aspiredapr-demo-postgres sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U postgres -d postgres -tAc "select 1 from pg_database where datname = '"'"'appdb'"'"'"' | grep -q 1; then
docker exec aspiredapr-demo-postgres sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U postgres -d postgres -v ON_ERROR_STOP=1 -c "create database appdb"'
fi
until docker exec aspiredapr-demo-postgres sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U postgres -d appdb -v ON_ERROR_STOP=1 -c "select 1"' >/dev/null 2>&1; do
sleep 1
done
""")
.WithEnvironment("POSTGRES_HOST", "127.0.0.1")
.WithEnvironment("POSTGRES_PORT", postgresEndpoint.Property(EndpointProperty.Port))
.WaitFor(postgres);
// API Service
var apiService = builder.AddProject("apiservice")
.WithReference(appDb)
.WithEnvironment("DAPR_HTTP_PORT", "3500")
.WithEnvironment("DAPR_GRPC_PORT", "50001")
.WaitForCompletion(postgresReady)
.WithHttpHealthCheck("/health");
var apiEndpoint = apiService.GetEndpoint("http");
builder.AddExecutable(
"apiservice-dapr",
"dapr",
builder.AppHostDirectory,
"run",
"--app-id",
"apiservice",
"--resources-path",
componentsPath,
"--app-port",
apiEndpoint.Property(EndpointProperty.Port),
"--dapr-http-port",
"3500",
"--dapr-grpc-port",
"50001",
"--app-channel-address",
"localhost",
"--app-protocol",
"http")
.WithEnvironment("STATESTORE_HOST", "127.0.0.1")
.WithEnvironment("STATESTORE_PORT", postgresEndpoint.Property(EndpointProperty.Port))
.WithEnvironment("STATESTORE_USER", postgres.Resource.UserNameReference)
.WithEnvironment("postgres-password", postgres.Resource.PasswordParameter!)
.WaitForCompletion(postgresReady)
.WaitFor(apiService);
builder.Build().Run();
`
Observations:
This approach is not using CommunityToolkit.Aspire.Hosting.Dapr. Here Dapr runs as an explicit executable and additonal code needed to check the readiness check. It allows Aspire to properly sequence the startup order and dashboard look OK but not perfect.
- Excessive custom code
- Cluttered Aspire dashboard
### Regression
_No response_
### Steps to reproduce
```text
.NET Version 10
Aspire version 13.4.0
```
### Expected behavior
I would suggest to make fix for 2. When dependencies are managed by Aspire
The Dapr Sidecar should wait for dependent resources readiness.
### Screenshots
_No response_
### IDE and version
VS Code
### IDE version
_No response_
### Nuget packages
```text
CommunityToolkit.Aspire.Hosting.Dapr Version=13.0.0
```
### Additional context
_No response_
### Help us help you
No, just wanted to report this
Contributor guide
Research direction
Start by locating the CommunityToolkit.Aspire.Hosting.Dapr implementation behind AddDaprComponent and WithDaprSidecar, then reproduce the first-run startup with .NET 10, Aspire 13.4.0, and PostgreSQL. Done means the Dapr sidecar waits for dependent Aspire resources to become ready before starting, without requiring the explicit executable and custom readiness checks shown in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, docker, postgresql
- Domain
- backend, devops
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100