-
Notifications
You must be signed in to change notification settings - Fork 732
Description
API Proposal: Add WithChildRelationship Methods for Intuitive Parent-Child Resource Relationships
Background and Motivation
Currently, Aspire provides WithParentRelationship methods to establish parent-child relationships between resources from the child's perspective. However, when working with complex applications where a parent resource logically "owns" or manages multiple child resources (such as parameters, secrets, or dependent services), it would be more natural and intuitive to express these relationships from the parent's perspective.
The current approach requires developers to break the fluent API chain and go back to child resources after defining the parent, which disrupts the natural flow of resource definition. This is particularly problematic when defining databases with their associated parameters, connection strings, or when setting up microservices with their dependencies.
This API proposal introduces WithChildRelationship extension methods that allow developers to establish parent-child relationships from the parent's perspective, maintaining the fluent API flow and providing a more intuitive way to express resource ownership patterns.
Proposed API
namespace Aspire.Hosting;
public static class ResourceBuilderExtensions
{
public static IResourceBuilder<T> WithChildRelationship<T>(
this IResourceBuilder<T> builder,
IResourceBuilder<IResource> child) where T : IResource
public static IResourceBuilder<T> WithChildRelationship<T>(
this IResourceBuilder<T> builder,
IResource child) where T : IResource
}Usage Examples
Basic Usage with Database Parameters
Before (Current API):
var builder = DistributedApplication.CreateBuilder(args);
var dbUsername = builder.AddParameter("dbUserName");
var dbPassword = builder.AddParameter("dbPassword");
var database = builder.AddPostgres("postgres", dbUsername, dbPassword)
.AddDatabase("my-db");
// Must break fluent chain to establish relationships
dbUsername.WithParentRelationship(database);
dbPassword.WithParentRelationship(database);After (Proposed API):
var builder = DistributedApplication.CreateBuilder(args);
var dbUsername = builder.AddParameter("dbUserName");
var dbPassword = builder.AddParameter("dbPassword");
var database = builder.AddPostgres("postgres", dbUsername, dbPassword)
.WithChildRelationship(dbUsername) // Fluent chain maintained
.WithChildRelationship(dbPassword) // Natural parent → child expression
.AddDatabase("my-db");
Advanced Usage with Multiple Resource Types
var builder = DistributedApplication.CreateBuilder(args);
var config = builder.AddParameter("config");
var secret = builder.AddConnectionString("secret");
var cache = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api")
.WithChildRelationship(config) // Using IResourceBuilder<IResource>
.WithChildRelationship(secret.Resource) // Using IResource overload
.WithChildRelationship(cache) // Multiple children supported
.WithReference(cache); // Can be combined with other methodsWorking with Existing WithParentRelationship
var builder = DistributedApplication.CreateBuilder(args);
var database = builder.AddPostgres("postgres");
var api = builder.AddProject<Projects.Api>("api");
var worker = builder.AddProject<Projects.Worker>("worker");
var monitor = builder.AddProject<Projects.Monitor>("monitor");
// Mix and match both approaches as needed
database.WithChildRelationship(api) // Parent → Child
.WithChildRelationship(worker); // Multiple children
monitor.WithParentRelationship(database); // Child → Parent (still works)Nested Relationships
var builder = DistributedApplication.CreateBuilder(args);
var infrastructure = builder.AddContainer("infrastructure", "infra:latest");
var database = builder.AddPostgres("postgres");
var cache = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api");
var frontend = builder.AddProject<Projects.Frontend>("frontend");
// Create hierarchical relationships
infrastructure
.WithChildRelationship(database)
.WithChildRelationship(cache);
database
.WithChildRelationship(api);
api
.WithChildRelationship(frontend);Risks
Breaking Changes
Risk Level: None
- This is a purely additive API that doesn't modify existing functionality
- All existing
WithParentRelationshipcalls continue to work unchanged - Both approaches can be used together in the same application
Performance Implications
Risk Level: Minimal
- The implementation delegates to existing relationship infrastructure
- No additional overhead compared to current
WithParentRelationshipcalls - Same internal
ResourceRelationshipAnnotationmechanism is used
API Confusion
Risk Level: Low
- Developers might be unsure which approach to use (parent→child vs child→parent)
- Mitigation: Clear documentation and examples showing both approaches are equivalent
- Mitigation: Consistent naming pattern (
WithChildRelationshipmirrorsWithParentRelationship)
Circular Dependencies
Risk Level: None
- Same validation logic applies as existing parent/child relationships
- No new opportunity for circular dependencies since the underlying relationship model is unchanged
Maintenance Burden
Risk Level: Minimal
- Simple delegation to existing infrastructure
- Comprehensive test coverage provided
- Follows existing patterns in the codebase