Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

relayering... #2747

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft

relayering... #2747

wants to merge 37 commits into from

Conversation

brettsam
Copy link
Member

@brettsam brettsam commented Oct 2, 2024

Note this is a draft... this is being decomposed into parts:

kshyju and others added 30 commits September 9, 2024 17:18
* Setting _ToolingSuffix for V9.0 TargetFrameworkVersion (#2499)

Setting `_ToolingSuffix` value to net9-isolated when the Targetframework of the function app is `net9.0`.

* feature branch release notes
* Adding net9.0 TFM

Update build YAML to install net9

pin to preview6 in global.json

* Bump SDK package version

* Adding Net9 sample app.

* Updating the below packages to use 9x version for net9.0 tfm
 - Microsoft.Extensions.Hosting
 - Microsoft.Extensions.Hosting.Abstractions
 - System.Collections.Immutable
 - System.Diagnostics.DiagnosticSource

* Move condition to ItemGroup element instead of Choose-When pattern.

* Updated ApplicationInsights package version in Sample app to latest stable (1.3.0)
…uild DotnetWorker.Extensions.sln as well for tests
…tartup options. (#2693)

* Removing the fallback command line argument reading code for grpc worker startup options.

* Add release notes.
…#2701)

* Set IncludeEmptyEntriesInMessagePayload to true by default

* Update release notes
@@ -0,0 +1,15 @@
namespace Microsoft.Extensions.Hosting
{
public class FunctionsApplication
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In AspNet... this implements IHost and others and hangs stuff off of it. Will we ever need to do that? Right now, I don't even use this other than for the static CreateBuilder()...

Reference: https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/WebApplication.cs#L25

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'll be a question of what extension methods we want to have hanging off of it. I know ASP.NET Core does a bunch of things only after the builder is built. We could probably get away without these for now and add them in if needed down the line.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ASP.NET Core implements a couple of different interfaces for the following:

  • IApplicationBuilder to support registering ASP.NET Core middleware directly (app.UseAuthorization)
  • IEndpointRouteBuilder to support calling the minimal API handlers directly (app.MapGet)
  • IHost to support exposing the configuration of the underlying HostApplicationBuilder inner builder used in WeApplicationBuilder

You can probably get away with not implementing any of these although I imagine a possibly interesting feature where we can explore what it would look like for FunctionsApplication to implement IApplicationBuilder and support ASP.NET Core middleware more directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@captainsafia -- Yeah we'd like to do that. We just haven't thought through the ramifications of it just yet (i.e. how can we prevent people from shooting themselves in the foot?).

But I do see us exposing the ASP.NET pipeline directly at some point in the near future.

Comment on lines 6 to 7
FunctionsApplicationBuilder builder = FunctionsWebApplication.CreateBuilder(args);
IHost app = builder.Build();
Copy link
Member Author

@brettsam brettsam Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's how you'd build a aspnet app...

Comment on lines 6 to 7
FunctionsApplicationBuilder builder = FunctionsApplication.CreateBuilder(args);
IHost app = builder.Build();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and a non-asp.net...


// Grab the HostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then
// grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
//var hostContext = (HostBuilderContext)bootstrapHostBuilder.Properties[typeof(HostBuilderContext)];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything isn't quite wired up yet... need to see exactly what we need here.

Copy link
Contributor

@mattchenderson mattchenderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a premature question, but where (if at all) do we expose the IFunctionsWorkerApplicationBuilder here? Or more specifically, the extension methods that hang off of it? Would we just have FunctionsApplicationBuilder also implement that interface? Or is this an example of something that should instead hang off of the built FunctionsApplication, similar to how ASP.NET Core does middleware?

I had been thinking that in the new HostApplicationBuilder world, our canonical middleware example snippet might look like:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

// Register our custom middlewares with the worker
builder.UseMiddleware<ExceptionHandlingMiddleware>();
builder.UseMiddleware<MyCustomMiddleware>();
builder.UseWhen<StampHttpHeaderMiddleware>((context) =>
{
    // We want to use this middleware only for http trigger invocations.
    return context.FunctionDefinition.InputBindings.Values
                    .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
});

builder.Build().Run();

For F#, we'd similarly want the per-extension config methods to be available:

    builder.ConfigureCosmosDBExtension() |> ignore
    builder.ConfigureBlobStorageExtension() |> ignore
    builder.ConfigureTablesExtension() |> ignore

@jviau
Copy link
Contributor

jviau commented Oct 3, 2024

Perhaps a premature question, but where (if at all) do we expose the IFunctionsWorkerApplicationBuilder here? Or more specifically, the extension methods that hang off of it? Would we just have FunctionsApplicationBuilder also implement that interface? Or is this an example of something that should instead hang off of the built FunctionsApplication, similar to how ASP.NET Core does middleware?

I had been thinking that in the new HostApplicationBuilder world, our canonical middleware example snippet might look like:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

// Register our custom middlewares with the worker
builder.UseMiddleware<ExceptionHandlingMiddleware>();
builder.UseMiddleware<MyCustomMiddleware>();
builder.UseWhen<StampHttpHeaderMiddleware>((context) =>
{
    // We want to use this middleware only for http trigger invocations.
    return context.FunctionDefinition.InputBindings.Values
                    .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
});

builder.Build().Run();

For F#, we'd similarly want the per-extension config methods to be available:

    builder.ConfigureCosmosDBExtension() |> ignore
    builder.ConfigureBlobStorageExtension() |> ignore
    builder.ConfigureTablesExtension() |> ignore

In AspNetCore, configuring of the 'app' is done later:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseMiddleware<MyMiddleware>();

app.Start();

Do we want to follow a similar patter?

@mattchenderson
Copy link
Contributor

Do we want to follow a similar patter?

Yeah, I should have included that example originally. That's what I had meant with:

Or is this an example of something that should instead hang off of the built FunctionsApplication, similar to how ASP.NET Core does middleware?

I think there are advantages to that symmetry. There are a few things that give me pause, though:

  • I don't know how this impacts future ASP.NET Core pipeline exposure. Without having that fully designed, though, we probably shouldn't overfocus on the "what-if".
  • Moving to the application makes things ever so slightly more verbose. That's not a huge deal, but it has some ripple effects for templates and documentation, potentially. I'd want that to be because we're gaining something. I value symmetry, but this is only partial symmetry (see next point), so I wonder how much value that provides.
  • There are tons of extension methods for IApplicationBuilder out there focused on ASP.NET Core. I realize that FunctionsApplication currently doesn't implement that (though discussed in PR comments), but would users have an expectation of this? That confusion will always be possible, but making our experience confront that more directly could be a downside. I personally don't think we'd want to give them access to these methods that wouldn't behave as intended, but that's also true with many IServiceCollection extensions. Are there other downsides to us fully avoiding IApplicationBuilder, too?

Regardless, I would argue that those methods needed by F# (e.g.,ConfigureCosmosDBExtension()) would be still on the builder. I'd also like to revisit those in general, but that's a separate conversation.

/// <summary>
/// Only available to AspNetCore extension (and maybe not even there in the future)
/// </summary>
internal IHostBuilder HostBuilder => _bootstrapHostBuilder;
Copy link
Member Author

@brettsam brettsam Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this for now so that only the AspNetCore extension has access to this. We could do something similar to what AspNet does with the ConfigureHostBuilder they expose which imposes some limitations on the builder. We'd need to do something similar to ensure this is never built and some things are not touched, etc. But for now, this gets us unblocked.

For reference: https://github.com/dotnet/aspnetcore/blob/559ada655240c2e6a0023ced978b291b05673b2c/src/DefaultBuilder/src/ConfigureHostBuilder.cs

@brettsam brettsam mentioned this pull request Oct 10, 2024
7 tasks
// Log levels can also be configured using appsettings.json. For more information, see https://learn.microsoft.com/en-us/azure/azure-monitor/app/worker-service#ilogger-logs
LoggerFilterRule toRemove = options.Rules.FirstOrDefault(rule => rule.ProviderName
== "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");
FunctionsApplicationBuilder builder = FunctionsApplication.CreateBuilder(args);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we initialize FunctionsApplicationBuilder here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. That was an accident in a last-second refactor. I'll remove it.

In fact, I think we're going to add a new "FunctionApplicationBuilder" sample just so we don't swap out these existing ones and confuse anyone that may be linking to them. And some of the docs are generated from these, so I shouldn't change them without a bigger review.

}

// we skipped this; make sure we add it at the end
this.UseDefaultWorkerMiddleware();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What guarantee do we have that it was skipped?

Slightly related: in ASP.NET Core we use static properties on the IApplicatonBuilder to track what middleware has already been registered by user code to avoid re-registering. I assume something like this is not necessary of UseDefaultWorkerMiddleware is idempotent.

Copy link
Member Author

@brettsam brettsam Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What guarantee do we have that it was skipped?

I guess we don't really have a guarantee, it's a good point. We need to figure out a good way to make sure that these are registered in the correct place, even if called at the wrong time. Even if we "locked" the middleware after this was called and threw if someone tried registering something else, it'd at least give us a guarantee that this made it to the right position. I'll come back to this in a follow-up (we've technically had this problem for years and I just ran into it with this PR).

I assume something like this is not necessary of UseDefaultWorkerMiddleware is idempotent.

The problem here wasn't with re-registering as we do check if these have already bene registered. The problem was with the ordering of the registration.

Because now we're changing how we compose all the wire-ups, we call ConfigureFunctionsWorkerDefaults() twice now with the AspNetCore extension, which generally is fine. But this call to add the default middleware meant that our terminating (function execution) middleware was in the wrong spot -- not at the end. It'd get added at the first call, then all the AspNetCore middleware was added after it. This meant that we couldn't reach any of the middleware to do all the fun AspNetCore stuff.

}

/// <inheritdoc/>
public IFunctionsWorkerApplicationBuilder Use(Func<FunctionExecutionDelegate, FunctionExecutionDelegate> middleware)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the comment above, to more closely align with the ASP.NET Core model maybe we want to make these middleware be something that's registered on the application and not the builder? Unless there's a particular reason they need to be finalized before Build is called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we started discussing this also. Today we generate the pipeline via DI but I don't think there's anything strictly requiring this. We discussed briefly what'd it take to mirror AspNet here and move this to the application. We'll follow up on this separately.

@fabiocav fabiocav force-pushed the feature/2.x branch 2 times, most recently from 4a90745 to 606a30c Compare November 13, 2024 23:06
Base automatically changed from feature/2.x to main November 13, 2024 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants