Describe the bug
CommunityToolkit.Aspire.Hosting.Dapr Version=13.0.0
I tried following 3 edge cases:
- 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<Projects.AspireDapr_Demo_ApiService>("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
- 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<Projects.AspireDapr_Demo_ApiService>("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
- 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<Projects.AspireDapr_Demo_ApiService>("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
.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
CommunityToolkit.Aspire.Hosting.Dapr Version=13.0.0
Additional context
No response
Help us help you
No, just wanted to report this
Describe the bug
CommunityToolkit.Aspire.Hosting.Dapr Version=13.0.0
I tried following 3 edge cases:
`
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<Projects.AspireDapr_Demo_ApiService>("apiservice")
.WithHttpHealthCheck("/health")
.WithDaprSidecar(sidecar => sidecar
.WithOptions(new DaprSidecarOptions
{
AppId = "apiservice",
DaprHttpPort = 3500
})
.WithReference(stateStore));
builder.Build().Run();
`
Observations:
`
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<Projects.AspireDapr_Demo_ApiService>("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:
`
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
// API Service
var apiService = builder.AddProject<Projects.AspireDapr_Demo_ApiService>("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.
Regression
No response
Steps to reproduce
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
Additional context
No response
Help us help you
No, just wanted to report this