dotnet / aspire

An opinionated, cloud ready stack for building observable, production ready, distributed applications in .NET
https://learn.microsoft.com/dotnet/aspire
MIT License
3.63k stars 408 forks source link

API Proposal: builder.When(predicate, callback) #5192

Open mitchdenny opened 1 month ago

mitchdenny commented 1 month ago

Background and Motivation

When using the fluent-like API in the .NET Aspire app model developers sometimes find themselves wanting to conditionally apply a method to a resource builder. For example, you might want to only add a reference to a resource in publish mode, or for a particular environment.

Today to do this you need to break out of the fluent pattern to add conditional blocks to do this because C# doesn't have an inline conditional method invocation (as far as I know). Example of current approach:

var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pgsql").AddDatabase("db");
var myapp = builder.AddProject<Projects.MyApp>("myapp")
                   .WithReference(db);

if (builder.ExecutionContext.Operation = DistributedApplicationOperation.Publish)
{
    var ai = builder.AddAzureApplicationInsights("ai");
    myapp.WithReference(ai);
}

This makes the code look a lot less concise. Here is an example which inspired this API proposal:

https://github.com/dotnet/eShop/pull/464

Proposed API

namespace Aspire.Hosting;

public static class ResourceBuilderExtensions
{
+    public static IResourceBuilder<T> When<T>(
+        this IResourceBuilder<T> builder,
+        Func<bool> predicate,
+        Action<IResourceBuilder<T>> callback) where T: IResource
}

Usage Examples

Applying the example above to this new API design:

var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pgsql").AddDatabase("db");
var myapp = builder.AddProject<Projects.MyApp>("myapp")
                   .WithReference(db);
                   .When(builder.Execution.Operation == DistributedApplicationOperation.Publish, b => {
                       var ai = builder.AddAzureApplicationInsights("ai");
                       b.WithReference(ai);
                   });

To help make things even more concise we could add reusable conditional functions:

var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pgsql").AddDatabase("db");
var myapp = builder.AddProject<Projects.MyApp>("myapp")
                   .WithReference(db);
                   .When(builder.Execution.PublishCondition, b => {
                       var ai = builder.AddAzureApplicationInsights("ai");
                       b.WithReference(ai);
                   });

Alternative Designs

We could add conditional logic to methods like WithReference and WithEnvironment. However this would further complicate the set of overloads on these methods so this general purpose helper method is probably the most flexible building block that doesn't require additional ongoing work to implement.

Risks

One day we might get a more elaborate method invocation syntax for C# which makes this redundant.

mitchdenny commented 1 month ago

@samsp-msft @mattmccleary @eerhardt @davidfowl @maddymontaquila @DamianEdwards

mattmccleary commented 1 month ago

@rajkumar-rangaraj @TimothyMothra @vishweshbankwar, FYI, feedback welcome.

davidfowl commented 1 month ago

I’d love to see a more convincing example. Something that goes from ugly ifs to clean When calls.