Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 124 additions & 11 deletions docs/guide/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ If you are experiencing noticeable startup lags or seeing spikes in memory utili
Wolverine, you will want to pursue using either the `Auto` or `Static` modes for code generation as explained in this guide.
:::

::: tip
This blog post from Oskar Dudycz will apply to Wolverine as well: [How to create a Docker image for the Marten application](https://event-driven.io/en/marten_and_docker/)
:::

Wolverine uses runtime code generation to create the "adaptor" code that Wolverine uses to call into
your message handlers. Wolverine's [middleware strategy](/guide/handlers/middleware) also uses this strategy to "weave" calls to
middleware directly into the runtime pipeline without requiring the copious usage of adapter interfaces
Expand All @@ -25,7 +21,7 @@ Not to worry though, Wolverine has several facilities to either preview the gene
really understand how Wolverine is interacting with your code and to optimize the "cold start" by generating the dynamic
code ahead of time so that it can be embedded directly into your application's main assembly and discovered from there.

By default, Wolverine runs with "dynamic" code generation where all the necessary generated types are built on demand
By default, Wolverine runs with "dynamic" code generation where a l the necessary generated types are built on demand
the first time they are needed. This is perfect for a quick start to Wolverine, and might be fine in smaller projects even
at production time.

Expand Down Expand Up @@ -81,6 +77,8 @@ Most of the facilities shown here will require the [Oakton command line integrat

## Embedding Codegen in Docker

This blog post from Oskar Dudycz will apply to Wolverine as well: [How to create a Docker image for the Marten application](https://event-driven.io/en/marten_and_docker/)

At this point, the most successful mechanism and sweet spot is to run the codegen as `Dynamic` at development time, but generating
the code artifacts just in time for production deployments. From Wolverine's sibling project Marten, see this section on [Application project setup](https://martendb.io/devops/devops.html#application-project-set-up)
for embedding the code generation directly into your Docker images for deployment.
Expand Down Expand Up @@ -124,6 +122,122 @@ using var host = Host.CreateDefaultBuilder()
If the assembly choice is correct, and the expected code files are really in `Internal/Generated` exactly as you'd expect, make
sure there's no accidental `<Exclude />` nodes in your project file. *Don't laugh, that's actually happened to Wolverine users*

## Wolverine Code Generation and IoC <Badge type="tip" text="5.0" />

::: info
Why, you ask, does Wolverine do any of this? Wolverine was originally conceived of as the successor to the
[FubuMVC & FubuTransportation](https://fubumvc.github.io) projects from the early 2010's. A major lesson learned
from FubuMVC was that we needed to reduce object allocations, layering, runaway `Exception` stack traces, and allow
for more flexible and streamlined handler or endpoint method signatures. To that end we fully embraced using runtime code
generation -- and this was built well before source generators were available.

As for the IoC part of this strategy, we ask you, what's the very fastest IoC tool in .NET? The answer of course, is
"no IoC container."
:::

Wolverine's code generation uses the configuration of your IoC tool to create the generated code wrappers
around your raw message handlers, HTTP endpoints, and middleware methods. Whenever possible, Wolverine is trying to
completely eliminate your application's IoC tool from the runtime code by generating the necessary constructor function
invocations to exactly mimic your application's IoC configuration.

::: info
Because you should care about this, Wolverine is absolutely generating `using` or `await using` for any objects it
creates through constructor calls that implements `IDisposable` or `IAsyncDisposable`.
:::

When generating the adapter classes, Wolverine can infer which method arguments or type dependencies can be sourced
from your application's IoC container configuration. If Wolverine can determine a way to generate all the necessary
constructor calls to create any necessary services registered with a `Scoped` or `Transient` lifetime, Wolverine will generate
code with the constructors. In this case, any IoC services that are registered with a `Singleton` lifetime
will be "inlined" as constructor arguments into the generated adapter class itself for a little better efficiency.

::: warning
The usage of a service locator within the generated code will naturally be a little less efficient just because there
is more runtime overhead. More dangerously, the service locator usage can sometimes foul up the scoping of services
like Wolverine's `IMessageBus` or Marten's `IDocumentSession` that are normally built outside of the IoC container
:::

If Wolverine cannot determine a path to generate
code for raw constructor construction of any registered services for a message handler or HTTP endpoint, Wolverine
will fall back to generating code with the [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern)
using a scoped container (think [IServiceScopeFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-9.0-pp)).

Here's some facts you do need to know about this whole process:

* The adapter classes generated by Wolverine for both message handlers and HTTP endpoints are effectively singleton
scoped and only ever built once
* Wolverine will try to bring `Singleton` scoped services through the generated adapter type's constructor function *one time*
* Wolverine will have to fall back to the service locator usage if any service dependency that has a `Scoped` or `Transient`
lifetime is either an `internal` type or uses an "opaque" Lambda registration (think `IServiceCollection.AddScoped(s => {})`)

::: tip
The code generation using IoC configuration is tested with both the built in .NET `ServiceProvider` and [Lamar](https://jasperfx.github.io/lamar). It
is theoretically possible to use other IoC tools with Wolverine, but only if you are *only* using `IServiceCollection`
for your IoC configuration.
:::

As of Wolverine 5.0, you now have the ability to better control the usage of the service locator in Wolverine's
code generation to potentially avoid unwanted usage:

<!-- snippet: sample_configuring_ServiceLocationPolicy -->
<a id='snippet-sample_configuring_servicelocationpolicy'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// This is the default behavior. Wolverine will allow you to utilize
// service location in the codegen, but will warn you through log messages
// when this happens
opts.ServiceLocationPolicy = ServiceLocationPolicy.AllowedButWarn;

// Tell Wolverine to just be quiet about service location and let it
// all go. For any of you with small children, I defy you to get the
// Frozen song out of your head now...
opts.ServiceLocationPolicy = ServiceLocationPolicy.AlwaysAllowed;

// Wolverine will throw exceptions at runtime if it encounters
// a message handler or HTTP endpoint that would require service
// location in the code generation
// Use this option to disallow any undesirably service location
opts.ServiceLocationPolicy = ServiceLocationPolicy.NotAllowed;
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/ServiceLocationUsage.cs#L11-L33' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_servicelocationpolicy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: note
[Wolverine.HTTP has some additional control over the service locator](/guide/http/#using-the-httpcontext-requestservices) to utilize the shared scoped container
with the rest of the AspNetCore pipeline.
:::

## Allow List for Service Location <Badge type="tip" text="5.0" />

Wolverine always reverts to using a service locator when it encounters an "opaque" Lambda registration that has either
a `Scoped` or `Transient` service lifetime. You can explicitly create an "allow" list of service types that can use
a service locator pattern while allowing the rest of the code generation for the message handler or HTTP endpoint to use
the more predictable and efficient generated constructor functions with this syntax:

<!-- snippet: sample_always_use_service_location -->
<a id='snippet-sample_always_use_service_location'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// other configuration

// Use a service locator for this service w/o forcing the entire
// message handler adapter to use a service locator for everything
opts.CodeGeneration.AlwaysUseServiceLocationFor<IServiceGatewayUsingRefit>();
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/CodeGeneration/service_location_assertions.cs#L45-L57' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_always_use_service_location' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

For example, this functionality might be helpful for:

* [Refit proxies](https://github.com/reactiveui/refit) that are registered in IoC with a Lambda registration, but might not use any other services
* EF Core `DbContext` types that might require some runtime configuration to construct themselves, but don't use other services (a [JasperFx Software](https://jasperfx.net) client
ran into this needing to conditionally opt into read replica usage, so hence, this feature made it into Wolverine 5.0)

## Environment Check for Expected Types

Expand Down Expand Up @@ -157,7 +271,8 @@ await host.StartAsync();
Do note that you would have to opt into using the environment checks on application startup, and maybe even force .NET
to make hosted service failures stop the application.

See [Oakton's Environment Check functionality](https://jasperfx.github.io/oakton/guide/host/environment.html) for more information.
See [Oakton's Environment Check functionality](https://jasperfx.github.io/oakton/guide/host/environment.html) for more information (the old Oakton documentation is still relevant for
JasperFx).

## Previewing the Generated Code

Expand Down Expand Up @@ -185,11 +300,9 @@ directly under the root of your project folder.

## Optimized Workflow

::: info
Optimized Workflow overrides the storage migration [AutoBuildMessageStorageOnStartup](./durability/managing#disable-automatic-storage-migration) option, making it enabled for "Development" environment and disabled for other environments
:::

Or as a short hand option, use this:
Wolverine and [Marten](https://martendb.io) both use the shared JasperFx library for their code generation,
and you can configure different behavior for production versus development time for both tools (and any future
"CritterStack" tools) with this usage:

<!-- snippet: sample_use_optimized_workflow -->
<a id='snippet-sample_use_optimized_workflow'></a>
Expand Down
5 changes: 1 addition & 4 deletions docs/guide/durability/efcore/sagas.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,9 @@ public class OrdersDbContext : DbContext
modelBuilder.Entity<Order>(map =>
{
map.ToTable("orders", "sample");
map.HasKey(x => x.Id);
map.Property(x => x.OrderStatus)
.HasConversion(v => v.ToString(), v => Enum.Parse<OrderStatus>(v));

// enable optimistic concurrency
map.Property(x => x.Version)
.IsConcurrencyToken();
});
}
}
Expand Down
8 changes: 8 additions & 0 deletions docs/guide/durability/marten/event-sourcing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ need in a message handler or HTTP endpoint is a read-only copy of an event strea
instead that has a little bit lighter weight runtime within Marten.
:::

::: info
If your message handler or HTTP endpoint uses more than one declarative attribute for retrieving Marten data,
Wolverine 5.0+ is able to utilize [Marten's Batch Querying capability](https://martendb.io/documents/querying/batched-queries.html#batched-queries) for more efficient interaction with the database.

This batching behavior is also supported for all the declarative attributes and the "aggregate handler workflow" in general
described in this page.
:::

See the [OrderEventSourcingSample project on GitHub](https://github.com/JasperFx/wolverine/tree/main/src/Persistence/OrderEventSourcingSample) for more samples.

That Wolverine + Marten combination is optimized for efficient and productive development using a [CQRS architecture style](https://martinfowler.com/bliki/CQRS.html) with [Marten's event sourcing](https://martendb.io/events/) support.
Expand Down
5 changes: 5 additions & 0 deletions docs/guide/durability/marten/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ Using the `IntegrateWithWolverine()` extension method behind your call to `AddMa

## Entity Attribute Loading

::: info
If your message handler or HTTP endpoint uses more than one declarative attribute for retrieving Marten data,
Wolverine 5.0+ is able to utilize [Marten's Batch Querying capability](https://martendb.io/documents/querying/batched-queries.html#batched-queries) for more efficient interaction with the database.
:::

The Marten integration is able to completely support the [Entity attribute usage](/guide/handlers/persistence.html#automatically-loading-entities-to-method-parameters).

## Marten as Outbox
Expand Down
68 changes: 68 additions & 0 deletions docs/guide/http/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,73 @@ return await app.RunJasperFxCommands(args);
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/CrazyStartingWebApp/Program.cs#L21-L29' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_eager_http_warmup' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Using the HttpContext.RequestServices <Badge type="tip" text="5.0" />

::: tip
The opt in behavior to share the scoped services with the rest of the AspNetCore pipeline is useful
for using Wolverine endpoints underneath AspNetCore middleware that "smuggles" state through the IoC container.

Custom multi-tenancy middleware or custom authorization or other security middleware frequently does this. We think
this will be helpful for mixed systems where Wolverine.HTTP is used for some routes while other routes are served
by MVC Core or Minimal API or even some other kind of AspNetCore `Endpoint`.
:::

By default, any time [Wolverine has to revert to using a service locator](/guide/codegen.html#wolverine-code-generation-and-ioc)
to generate the adapter code for an HTTP endpoint, Wolverine is using an isolated `IServiceScope` (or Lamar `INestedContainer`) within the generated code.

But, with Wolverine 5.0+ you can opt into Wolverine just using the `HttpContext.RequestServices` so that you
can share services with AspNetCore middleware. You can also configure *some* service types to be pulled from
the `HttpContext.RequestServices` even if Wolverine is otherwise generating more efficient constructor calls
for all other dependencies. Here's an example using both of these opt in behaviors:

<!-- snippet: sample_bootstrapping_with_httpcontext_request_services -->
<a id='snippet-sample_bootstrapping_with_httpcontext_request_services'></a>
```cs
var builder = WebApplication.CreateBuilder();

builder.UseWolverine(opts =>
{
// more configuration
});

// Just pretend that this IUserContext is being
builder.Services.AddScoped<IUserContext, UserContext>();
builder.Services.AddWolverineHttp();

var app = builder.Build();

// Custom middleware that is somehow configuring our IUserContext
// that might be getting used within
app.UseMiddleware<MyCustomUserMiddleware>();

app.MapWolverineEndpoints(opts =>
{
// Opt into using the shared HttpContext.RequestServices scoped
// container any time Wolverine has to use a service locator
opts.ServiceProviderSource = ServiceProviderSource.FromHttpContextRequestServices;

// OR this is the default behavior to be backwards compatible:
opts.ServiceProviderSource = ServiceProviderSource.IsolatedAndScoped;

// We're telling Wolverine that the IUserContext should always
// be pulled from HttpContext.RequestServices
// and this happens regardless of the ServerProviderSource!
opts.SourceServiceFromHttpContext<IUserContext>();
});

return await app.RunJasperFxCommands(args);
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/CodeGeneration/service_location_assertions.cs#L396-L432' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_httpcontext_request_services' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Notice the call to `SourceServiceFromHttpContext<T>()`. That directs Wolverine.HTTP to always pull the service
`T` from the `HttpContext.RequestServices` scoped container so that Wolverine.HTTP can play nicely with custom AspNetCore
middleware or whatever else you have around your Wolverine.HTTP endpoints.

::: warning
The Wolverine team believes that smuggling important state between upstream middleware and downstream handlers
leads to code that is hard to reason about and hence, potentially buggy in real life usage. Alas, you could easily
need this functionality in the real world, so here you go.
:::


Loading
Loading