Skip to content

Add WithChildRelationship Methods for Intuitive Parent-Child Resource Relationships #11839

@jomaxso

Description

@jomaxso

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");
Image

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 methods

Working 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 WithParentRelationship calls 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 WithParentRelationship calls
  • Same internal ResourceRelationshipAnnotation mechanism 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 (WithChildRelationship mirrors WithParentRelationship)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions