diff --git a/DomainEventsWithEfCore/BackLogService/Scraping/Code.cs b/DomainEventsWithEfCore/BackLogService/Scraping/Code.cs index 97a24e424..3d11f5a59 100644 --- a/DomainEventsWithEfCore/BackLogService/Scraping/Code.cs +++ b/DomainEventsWithEfCore/BackLogService/Scraping/Code.cs @@ -8,6 +8,8 @@ namespace BackLogService.Scraping; +#region sample_Entity_layer_super_type + // Of course, if you're into DDD, you'll probably // use many more marker interfaces than I do here, // but you do you and I'll do me in throwaway sample code @@ -21,6 +23,10 @@ public void Publish(object @event) } } +#endregion + +#region sample_BacklogItem + public class BacklogItem : Entity { public Guid Id { get; private set; } @@ -36,6 +42,8 @@ public void CommitTo(Sprint sprint) } } +#endregion + public class ItemsDbContext : DbContext { public DbSet BacklogItems { get; set; } @@ -44,6 +52,8 @@ public class ItemsDbContext : DbContext public record CommitToSprint(Guid BacklogItemId, Guid SprintId); +#region sample_CommitToSprintHandler + public static class CommitToSprintHandler { public static void Handle( @@ -54,7 +64,7 @@ public static void Handle( // from the incoming command [Entity] BacklogItem item, [Entity] Sprint sprint - ) + ) { // This method would cause an event to be published within // the BacklogItem object here that we need to gather up and @@ -66,6 +76,8 @@ [Entity] Sprint sprint } } +#endregion + public static class RelayEvents { public static async ValueTask PublishEventsAsync(DbContext dbContext, IMessageContext context) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 61d773537..c61e5bdb7 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -227,7 +227,8 @@ const config: UserConfig = { {text: 'Validation', link: '/guide/http/validation'}, {text: 'Fluent Validation', link: '/guide/http/fluentvalidation'}, {text: 'Problem Details', link: '/guide/http/problemdetails'}, - {text: 'Caching', link: '/guide/http/caching'} + {text: 'Caching', link: '/guide/http/caching'}, + {text: 'HTTP Messaging Transport', link: '/guide/http/transport'} ] }, { @@ -258,7 +259,8 @@ const config: UserConfig = { {text: 'Transactional Inbox and Outbox', link: '/guide/durability/efcore/outbox-and-inbox'}, {text: 'Operation Side Effects', link: '/guide/durability/efcore/operations'}, {text: 'Saga Storage', link: '/guide/durability/efcore/sagas'}, - {text: 'Multi-Tenancy', link: '/guide/durability/efcore/multi-tenancy'} + {text: 'Multi-Tenancy', link: '/guide/durability/efcore/multi-tenancy'}, + {text: 'Domain Events', link: '/guide/durability/efcore/domain-events'} ]}, {text: 'Managing Message Storage', link: '/guide/durability/managing'}, diff --git a/docs/guide/basics.md b/docs/guide/basics.md index c30bb6bc9..e3c352233 100644 --- a/docs/guide/basics.md +++ b/docs/guide/basics.md @@ -10,7 +10,7 @@ something happened. Just know that as far as Wolverine is concerned, those are r Here's a couple simple samples: - + ```cs // A "command" message public record DebitAccount(long AccountId, decimal Amount); @@ -18,14 +18,14 @@ public record DebitAccount(long AccountId, decimal Amount); // An "event" message public record AccountOverdrawn(long AccountId); ``` -snippet source | anchor +snippet source | anchor The next concept in Wolverine is a message handler, which is just a method that "knows" how to process an incoming message. Here's an extremely simple example: - + ```cs public static class DebitAccountHandler { @@ -35,7 +35,7 @@ public static class DebitAccountHandler } } ``` -snippet source | anchor +snippet source | anchor Wolverine can act as a completely local mediator tool that allows your code to invoke the handler for a message at any time without having diff --git a/docs/guide/codegen.md b/docs/guide/codegen.md index 5b9a621be..82181e1eb 100644 --- a/docs/guide/codegen.md +++ b/docs/guide/codegen.md @@ -203,7 +203,7 @@ As of Wolverine 5.0, you now have the ability to better control the usage of the code generation to potentially avoid unwanted usage: - + ```cs var builder = Host.CreateApplicationBuilder(); builder.UseWolverine(opts => @@ -225,7 +225,7 @@ builder.UseWolverine(opts => opts.ServiceLocationPolicy = ServiceLocationPolicy.NotAllowed; }); ``` -snippet source | anchor +snippet source | anchor ::: note diff --git a/docs/guide/command-line.md b/docs/guide/command-line.md index 89f2e4bd6..d59574063 100644 --- a/docs/guide/command-line.md +++ b/docs/guide/command-line.md @@ -7,7 +7,7 @@ tools. To get started, apply Oakton as the command line parser in your applicati sample application bootstrapping from Wolverine's [Getting Started](/tutorials/getting-started): - + ```cs using JasperFx; using Quickstart; @@ -49,7 +49,7 @@ app.MapGet("/", () => Results.Redirect("/swagger")); // your Wolverine application return await app.RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor From this project's root in the command line terminal tool of your choice, type: diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 987450865..94efe4398 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -30,7 +30,7 @@ Below is a sample of adding Wolverine to an ASP.NET Core application that is boo `WebApplicationBuilder`: - + ```cs using JasperFx; using Quickstart; @@ -72,7 +72,7 @@ app.MapGet("/", () => Results.Redirect("/swagger")); // your Wolverine application return await app.RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor ## "Headless" Applications diff --git a/docs/guide/diagnostics.md b/docs/guide/diagnostics.md index 1b1873866..9a08910c4 100644 --- a/docs/guide/diagnostics.md +++ b/docs/guide/diagnostics.md @@ -9,7 +9,7 @@ to utilize this command line integration, you need to apply JasperFx as your com sample `Program.cs` file: - + ```cs using JasperFx; using Quickstart; @@ -51,7 +51,7 @@ app.MapGet("/", () => Results.Redirect("/swagger")); // your Wolverine application return await app.RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor ## Command Line Description diff --git a/docs/guide/durability/dead-letter-storage.md b/docs/guide/durability/dead-letter-storage.md index 8a94ebf09..b291042bf 100644 --- a/docs/guide/durability/dead-letter-storage.md +++ b/docs/guide/durability/dead-letter-storage.md @@ -79,10 +79,9 @@ app.MapDeadLettersEndpoints() // or OpenAPI metadata configuration you need // for just these endpoints //.RequireAuthorization("Admin") - ; ``` -snippet source | anchor +snippet source | anchor ### Using the Dead Letters REST API diff --git a/docs/guide/durability/efcore/domain-events.md b/docs/guide/durability/efcore/domain-events.md new file mode 100644 index 000000000..ae3f7cf13 --- /dev/null +++ b/docs/guide/durability/efcore/domain-events.md @@ -0,0 +1,265 @@ +# Publishing Domain Events + +::: info +This section is all about using the traditional .NET "Domain Events" approach commonly used with EF Core, +but piping the domain events raised through Wolverine messaging. +::: + +Wolverine's integration with EF Core also includes support for the typical "Domain Events" publishing that +folks like to do with EF Core `DbContext` classes and some sort of `DomainEntity` [layer supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html). + +See Jeremy's post [“Classic” .NET Domain Events with Wolverine and EF Core](https://jeremydmiller.com/2025/12/04/classic-net-domain-events-with-wolverine-and-ef-core/) for much more background. + +Jumping right into an example, let's say that you like to use a layer supertype in your domain model that +gives your `Entity` types a chance to "raise" domain events like this one: + + + +```cs +// Of course, if you're into DDD, you'll probably +// use many more marker interfaces than I do here, +// but you do you and I'll do me in throwaway sample code +public abstract class Entity +{ + public List Events { get; } = new(); + + public void Publish(object @event) + { + Events.Add(@event); + } +} +``` +snippet source | anchor + + +Now, let's say we're building some kind of software project planning software (as if the world doesn't have enough +"Jira but different" applications) where we'll have an entity like this one: + + + +```cs +public class BacklogItem : Entity +{ + public Guid Id { get; private set; } + + public string Description { get; private set; } + public virtual Sprint Sprint { get; private set; } + public DateTime CreatedAtUtc { get; private set; } = DateTime.UtcNow; + + public void CommitTo(Sprint sprint) + { + Sprint = sprint; + Publish(new BackLotItemCommitted(Id, sprint.Id)); + } +} +``` +snippet source | anchor + + +Let’s utilize this a little bit within a Wolverine handler, first with explicit code: + + + +```cs +public static class CommitToSprintHandler +{ + public static void Handle( + CommitToSprint command, + + // There's a naming convention here about how + // Wolverine "knows" the id for the BacklogItem + // from the incoming command + [Entity] BacklogItem item, + [Entity] Sprint sprint + ) + { + // This method would cause an event to be published within + // the BacklogItem object here that we need to gather up and + // relay to Wolverine later + item.CommitTo(sprint); + + // Wolverine's transactional middleware handles + // everything around SaveChangesAsync() and transactions + } +} +``` +snippet source | anchor + + +Now, let’s add some Wolverine configuration to just make this pattern work: + +```csharp +builder.Host.UseWolverine(opts => +{ + // Setting up Sql Server-backed message storage + // This requires a reference to Wolverine.SqlServer + opts.PersistMessagesWithSqlServer(connectionString, "wolverine"); + + // Set up Entity Framework Core as the support + // for Wolverine's transactional middleware + opts.UseEntityFrameworkCoreTransactions(); + + // THIS IS A NEW API IN Wolverine 5.6! + opts.PublishDomainEventsFromEntityFrameworkCore(x => x.Events); + + // Enrolling all local queues into the + // durable inbox/outbox processing + opts.Policies.UseDurableLocalQueues(); +}); +``` + +In the Wolverine configuration above, the EF Core transactional middleware now “knows” how to +scrape out possible domain events from the active DbContext.ChangeTracker and publish them through +Wolverine. Moreover, the [EF Core transactional middleware](/guide/durability/efcore/transactional-middleware) is doing all the operation ordering for +you so that the events are enqueued as outgoing messages as part of the transaction and potentially +persisted to the transactional inbox or outbox (depending on configuration) before the transaction is committed. + +::: tip +To make this as clear as possible, this approach is completely reliant on the EF Core transactional middleware. +::: + +Also note that this domain event “scraping” is also supported and tested with the `IDbContextOutbox` service +if you want to use this in application code outside of Wolverine message handlers or HTTP endpoints. + +If I were building a system that embeds domain event publishing directly in domain model entity classes, I would prefer this approach. But, let’s talk about another option that will not require any changes to Wolverine… + +## Relay Events from Entity to Wolverine Cascading Messages + +In this approach, which I’m granting that some people won’t like at all, we’ll simply pipe the event messages from the domain entity right to +Wolverine and utilize Wolverine’s [cascading message](/guide/handlers/cascading) feature. + +This time I’m going to change the BacklogItem entity class to something like this: + +```csharp +public class BacklogItem +{ + public Guid Id { get; private set; } + + public string Description { get; private set; } + public virtual Sprint Sprint { get; private set; } + public DateTime CreatedAtUtc { get; private set; } = DateTime.UtcNow; + + // The exact return type isn't hugely important here + public object[] CommitTo(Sprint sprint) + { + Sprint = sprint; + return [new BackLotItemCommitted(Id, sprint.Id)]; + } +} +``` + +With the handler signature: + +```csharp +public static class CommitToSprintHandler +{ + public static object[] Handle( + CommitToSprint command, + + // There's a naming convention here about how + // Wolverine "knows" the id for the BacklogItem + // from the incoming command + [Entity] BacklogItem item, + [Entity] Sprint sprint + ) + { + return item.CommitTo(sprint); + } +} +``` + +The approach above let’s you make the handler be a single pure function which is always great for unit +testing, eliminates the need to do any customization of the DbContext type, makes it unnecessary to +bother with any kind of IEventPublisher interface, and let’s you keep the logic for what event messages +should be raised completely in your domain model entity types. + +I’d also argue that this approach makes it more clear to later developers that “hey, additional messages may be published as part of handling the CommitToSprint command” and I think that’s invaluable. I’ll harp on this more later, but I think the traditional, MediatR-flavored approach to domain events from the first +example at the top makes application code harder to reason about and therefore more buggy over time. + +## Embedding IEventPublisher into the Entities + +Lastly, let’s move to what I think is my least favorite approach that I will from this moment be recommending against for any JasperFx clients but is now completely supported by Wolverine. +Let’s use an `IEventPublisher` interface like this: + +```csharp +// Just assume that this little abstraction +// eventually relays the event messages to Wolverine +// or whatever messaging tool you're using +public interface IEventPublisher +{ + void Publish(T @event) where T : IDomainEvent; +} + +// Using a Nullo just so you don't have potential +// NullReferenceExceptions +public class NulloEventPublisher : IEventPublisher +{ + public void Publish(T @event) where T : IDomainEvent + { + // Do nothing. + } +} + +public abstract class Entity +{ + public IEventPublisher Publisher { get; set; } = new NulloEventPublisher(); +} + +public class BacklogItem : Entity +{ + public Guid Id { get; private set; } = Guid.CreateVersion7(); + + public string Description { get; private set; } + + // ZOMG, I forgot how annoying ORMs are. Use a document database + // and stop worrying about making things virtual just for lazy loading + public virtual Sprint Sprint { get; private set; } + + public void CommitTo(Sprint sprint) + { + Sprint = sprint; + Publisher.Publish(new BackLotItemCommitted(Id, sprint.Id)); + } +} +``` + +Now, on to a Wolverine implementation for this pattern. You’ll need to do just a couple things. First, add this line of configuration to Wolverine, and note there are no generic arguments here: + +```csharp +// This will set you up to scrape out domain events in the +// EF Core transactional middleware using a special service +// I'm just about to explain +opts.PublishDomainEventsFromEntityFrameworkCore(); +``` + +Now, build a real implementation of that IEventPublisher interface above: + +```csharp +public class EventPublisher(OutgoingDomainEvents Events) : IEventPublisher +{ + public void Publish(T e) where T : IDomainEvent + { + Events.Add(e); + } +} +``` + +`OutgoingDomainEvents` is a service from the WolverineFx.EntityFrameworkCore Nuget that is registered as Scoped by the usage of the EF Core transactional middleware. Next, register your custom IEventPublisher with the Scoped lifecycle: + +```csharp +opts.Services.AddScoped(); +``` + +How you wire up `IEventPublisher` to your domain entities getting loaded out of the your EF Core `DbContext`? Frankly, I don’t want to know. Maybe a repository abstraction around your DbContext types? Dunno. I hate that kind of thing in code, but I perfectly trust *you* to do that and to not make me see that code. + +What’s important is that within a message handler or HTTP endpoint, if you resolve the `IEventPublisher` through DI and use +the EF Core transactional middleware, the domain events published to that interface will be piped correctly into Wolverine’s active messaging context. + +Likewise, if you are using `IDbContextOutbox`, the domain events published to `IEventPublisher` will be correctly piped to Wolverine if you: + +1. Pull both `IEventPublisher` and `IDbContextOutbox` from the same scoped service provider (nested container in Lamar / StructureMap parlance) +2. Call `IDbContextOutbox.SaveChangesAndFlushMessagesAsync()` + +3. So, we’re going to have to do some sleight of hand to keep your domain entities synchronous + +Last note, in unit testing you might use a stand in “Spy” like this: diff --git a/docs/guide/durability/efcore/multi-tenancy.md b/docs/guide/durability/efcore/multi-tenancy.md index 1453e89a7..1b57e1216 100644 --- a/docs/guide/durability/efcore/multi-tenancy.md +++ b/docs/guide/durability/efcore/multi-tenancy.md @@ -173,7 +173,7 @@ Then you can _still_ use those EF Core `DbContext` services with Wolverine messa this sample code: - + ```cs public class MyMessageHandler { @@ -203,7 +203,7 @@ public class MyMessageHandler } } ``` -snippet source | anchor +snippet source | anchor The important thing to note above is just that this pattern and service will work with any .NET code and not just within Wolverine diff --git a/docs/guide/durability/efcore/operations.md b/docs/guide/durability/efcore/operations.md index a41203a29..5f02801d7 100644 --- a/docs/guide/durability/efcore/operations.md +++ b/docs/guide/durability/efcore/operations.md @@ -5,7 +5,7 @@ Just know that Wolverine completely supports the concept of [Storage Operations] Assuming you have an EF Core `DbContext` type like this registered in your system: - + ```cs public class TodoDbContext : DbContext { @@ -27,14 +27,14 @@ public class TodoDbContext : DbContext } } ``` -snippet source | anchor +snippet source | anchor You can use storage operations in Wolverine message handlers or HTTP endpoints like these samples from the Wolverine test suite: - + ```cs public static class TodoHandler { @@ -134,7 +134,7 @@ public static class TodoHandler } } ``` -snippet source | anchor +snippet source | anchor ::: warning diff --git a/docs/guide/durability/efcore/sagas.md b/docs/guide/durability/efcore/sagas.md index 91f943097..e0e4af151 100644 --- a/docs/guide/durability/efcore/sagas.md +++ b/docs/guide/durability/efcore/sagas.md @@ -92,7 +92,7 @@ public class Order : Saga And a matching `OrdersDbContext` that can persist that type like so: - + ```cs public class OrdersDbContext : DbContext { @@ -119,7 +119,7 @@ public class OrdersDbContext : DbContext } } ``` -snippet source | anchor +snippet source | anchor There's no other registration to do other than adding the `OrdersDbContext` to your IoC container and enabling diff --git a/docs/guide/durability/idempotency.md b/docs/guide/durability/idempotency.md index c417867fd..74f4aeb1c 100644 --- a/docs/guide/durability/idempotency.md +++ b/docs/guide/durability/idempotency.md @@ -14,7 +14,7 @@ Instead of immediately deleting message storage for a successfully completed mes that message in storage for a default of 5 minutes to protect against duplicate incoming messages. To override that setting, you have this option: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -25,5 +25,5 @@ using var host = await Host.CreateDefaultBuilder() opts.Durability.KeepAfterMessageHandling = 10.Minutes(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/durability/index.md b/docs/guide/durability/index.md index f7ba93a45..38273094b 100644 --- a/docs/guide/durability/index.md +++ b/docs/guide/durability/index.md @@ -27,7 +27,7 @@ getting lost en route. Consider this sample message handler from Wolverine's [AppWithMiddleware sample project](https://github.com/JasperFx/wolverine/tree/main/src/Samples/Middleware): - + ```cs [Transactional] public static async Task Handle( @@ -62,7 +62,7 @@ public static async Task Handle( new DeliveryOptions { DeliverWithin = 5.Seconds() }); } ``` -snippet source | anchor +snippet source | anchor The handler code above is committing changes to an `Account` in the underlying database and potentially sending out additional messages based on the state of the `Account`. @@ -283,3 +283,40 @@ var host = await Host.CreateDefaultBuilder() This might be an important setting for [modular monolith architectures](/tutorials/modular-monolith). + +## Stale Inbox and Outbox Thresholds + +::: info +This is more a "defense in depth" feature than a common problem with the inbox/outbox mechanics. These +flags are "opt in" only because they require database schema changes. +::: + +It should not ever be possible for messages to get "stuck" in the transactional inbox or outbox, but it's an +imperfect world and occasionally there are hiccups that might lead to that situation. To that end, you have +these "opt in" settings to tell Wolverine to "bump" apparently stalled or stale messages back into play *just in case*: + + + +```cs +using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // configure the actual message persistence... + + // This directs Wolverine to "bump" any messages marked + // as being owned by a specific node but older than + // these thresholds as being open to any node pulling + // them in + + // TL;DR: make Wolverine go grab stale messages and make + // sure they are processed or sent to the messaging brokers + opts.Durability.InboxStaleTime = 5.Minutes(); + opts.Durability.OutboxStaleTime = 5.Minutes(); + }).StartAsync(); +``` +snippet source | anchor + + +::: info +These settings will be defaults in Wolverine 6.0. +::: diff --git a/docs/guide/durability/managing.md b/docs/guide/durability/managing.md index e4245eb24..1d8295eb5 100644 --- a/docs/guide/durability/managing.md +++ b/docs/guide/durability/managing.md @@ -199,7 +199,7 @@ You can now do that with the option shown below as part of an [Alba](https://jas ```cs -using var host = await AlbaHost.For(builder => +using var host = await AlbaHost.For(builder => { builder.ConfigureServices(services => { diff --git a/docs/guide/durability/marten/ancillary-stores.md b/docs/guide/durability/marten/ancillary-stores.md index 064c2b956..152961b51 100644 --- a/docs/guide/durability/marten/ancillary-stores.md +++ b/docs/guide/durability/marten/ancillary-stores.md @@ -105,7 +105,7 @@ Now, moving to message handlers or HTTP endpoints, you will have to explicitly t individual messages with the `[MartenStore(store type)]` attribute like this simple example below: - + ```cs // This will use a Marten session from the // IPlayerStore rather than the main IDocumentStore @@ -119,7 +119,7 @@ public static class PlayerMessageHandler } } ``` -snippet source | anchor +snippet source | anchor ::: info diff --git a/docs/guide/durability/marten/event-sourcing.md b/docs/guide/durability/marten/event-sourcing.md index d38fd7572..c969a6033 100644 --- a/docs/guide/durability/marten/event-sourcing.md +++ b/docs/guide/durability/marten/event-sourcing.md @@ -31,7 +31,7 @@ And then lastly, you're going to want some resiliency and selective retry capabi Let's just right into an example order management system. I'm going to model the order workflow with this aggregate model: - + ```cs public class Item { @@ -75,19 +75,19 @@ public class Order } } ``` -snippet source | anchor +snippet source | anchor At a minimum, we're going to want a command handler for this command message that marks an order item as ready to ship and then evaluates whether or not based on the current state of the `Order` aggregate whether or not the logical order is ready to be shipped out: - + ```cs // OrderId refers to the identity of the Order aggregate public record MarkItemReady(Guid OrderId, string ItemName, int Version); ``` -snippet source | anchor +snippet source | anchor In the code above, we're also utilizing Wolverine's [outbox messaging](/guide/durability/) support to both order and guarantee the delivery of a `ShipOrder` message when @@ -96,7 +96,7 @@ the Marten transaction Before getting into Wolverine middleware strategies, let's first build out an MVC controller method for the command above: - + ```cs [HttpPost("/orders/itemready")] public async Task Post( @@ -147,7 +147,7 @@ public async Task Post( await session.SaveChangesAsync(); } ``` -snippet source | anchor +snippet source | anchor Hopefully, that code is easy to understand, but there's some potentially repetitive code @@ -160,7 +160,7 @@ pattern with Marten using the `[AggregateHandler]` middleware. Using that middleware, we get this slimmer code: - + ```cs [AggregateHandler] public static IEnumerable Handle(MarkItemReady command, Order order) @@ -187,7 +187,7 @@ public static IEnumerable Handle(MarkItemReady command, Order order) } } ``` -snippet source | anchor +snippet source | anchor In the case above, Wolverine is wrapping middleware around our basic command handler to @@ -250,7 +250,7 @@ Alternatively, there is also the newer `[WriteAggregate]` usage, with this examp mark up: - + ```cs public static IEnumerable Handle( // The command @@ -281,7 +281,7 @@ public static IEnumerable Handle( } } ``` -snippet source | anchor +snippet source | anchor The `[WriteAggregate]` attribute also opts into the "aggregate handler workflow", but is placed at the parameter level @@ -299,7 +299,7 @@ By default, the "aggregate handler workflow" does no validation on whether or no exists at runtime, and it's possible to receive a null for the aggregate in this example if the aggregate does not exist: - + ```cs public static IEnumerable Handle( // The command @@ -330,7 +330,7 @@ public static IEnumerable Handle( } } ``` -snippet source | anchor +snippet source | anchor As long as you handle the case where the requested is null, you can even effectively start a new stream by emitting events @@ -408,7 +408,7 @@ The Marten workflow command handler method signature needs to follow these rules in the Marten `IEventStream` type (`IEventStream`). There is an example of that usage below: - + ```cs [AggregateHandler] public static void Handle(OrderEventSourcingSample.MarkItemReady command, IEventStream stream) @@ -437,7 +437,7 @@ public static void Handle(OrderEventSourcingSample.MarkItemReady command, IEvent } } ``` -snippet source | anchor +snippet source | anchor Just as in other Wolverine [message handlers](/guide/handlers/), you can use @@ -458,7 +458,7 @@ As for the return values from these handler methods, you can use: Here's an alternative to the `MarkItemReady` handler that uses `Events`: - + ```cs [AggregateHandler] public static async Task<(Events, OutgoingMessages)> HandleAsync(MarkItemReady command, Order order, ISomeService service) @@ -498,7 +498,7 @@ public static async Task<(Events, OutgoingMessages)> HandleAsync(MarkItemReady c return (events, messages); } ``` -snippet source | anchor +snippet source | anchor @@ -511,12 +511,12 @@ where the `OrderId` property is assumed to be the identity of the `Order` aggreg by appending "Id" to the aggregate type name (it's not case sensitive if you were wondering): - + ```cs // OrderId refers to the identity of the Order aggregate public record MarkItemReady(Guid OrderId, string ItemName, int Version); ``` -snippet source | anchor +snippet source | anchor Or if you want to use a different member, bypass the convention, or just don't like conventional @@ -524,7 +524,7 @@ magic, you can decorate a public member on the command class with Marten's `[Identity]` attribute like so: - + ```cs public class MarkItemReady { @@ -535,7 +535,7 @@ public class MarkItemReady public string ItemName { get; init; } } ``` -snippet source | anchor +snippet source | anchor ## Validation @@ -561,7 +561,7 @@ public static string GetLetter2([ReadAggregate(Required = false)] LetterAggregat [WolverineGet("/letters3/{id}")] public static LetterAggregate GetLetter3([ReadAggregate(OnMissing = OnMissing.ProblemDetailsWith404)] LetterAggregate letters) => letters; ``` -snippet source | anchor +snippet source | anchor ## Forwarding Events @@ -606,7 +606,7 @@ taking the `MarkItemReady` command handler we've used earlier in this guide and produces a response of the latest aggregate: - + ```cs [AggregateHandler] public static ( @@ -644,7 +644,7 @@ public static ( return (new UpdatedAggregate(), events); } ``` -snippet source | anchor +snippet source | anchor Note the usage of the `Wolverine.Marten.UpdatedAggregate` response in the handler. That type by itself is just a directive @@ -652,7 +652,7 @@ to Wolverine to generate the necessary code to call `FetchLatest` and respond wi us to use the command in a mediator usage like so: - + ```cs public static Task update_and_get_latest(IMessageBus bus, MarkItemReady command) { @@ -662,7 +662,7 @@ public static Task update_and_get_latest(IMessageBus bus, MarkItemReady c return bus.InvokeAsync(command); } ``` -snippet source | anchor +snippet source | anchor Likewise, you can use `UpdatedAggregate` as the response body of an HTTP endpoint with Wolverine.HTTP [as shown here](/guide/http/marten.html#responding-with-the-updated-aggregate~~~~). @@ -716,7 +716,7 @@ If you want to inject the current state of an event sourced aggregate as a param a message handler method strictly for information and don't need the heavier "aggregate handler workflow," use the `[ReadAggregate]` attribute like this: - + ```cs public record FindAggregate(Guid Id); @@ -741,7 +741,7 @@ public static class FindLettersHandler */ } ``` -snippet source | anchor +snippet source | anchor If the aggregate doesn't exist, the HTTP request will stop with a 404 status code. @@ -772,7 +772,7 @@ public static string GetLetter2([ReadAggregate(Required = false)] LetterAggregat [WolverineGet("/letters3/{id}")] public static LetterAggregate GetLetter3([ReadAggregate(OnMissing = OnMissing.ProblemDetailsWith404)] LetterAggregate letters) => letters; ``` -snippet source | anchor +snippet source | anchor There is also an option with `OnMissing` to throw a `RequiredDataMissingException` exception if a required data element @@ -819,18 +819,18 @@ public class Account And you need to handle a command like this: - + ```cs public record TransferMoney(Guid FromId, Guid ToId, double Amount); ``` -snippet source | anchor +snippet source | anchor Using the `[WriteAggregate]` attribute to denote the event streams we need to work with, we could write this message handler + HTTP endpoint: - + ```cs public static class TransferMoneyHandler { @@ -851,7 +851,7 @@ public static class TransferMoneyHandler } } ``` -snippet source | anchor +snippet source | anchor The `IEventStream` abstraction comes from Marten’s `FetchForWriting()` API that is our diff --git a/docs/guide/durability/marten/operations.md b/docs/guide/durability/marten/operations.md index daacb5ac5..2688a6bd7 100644 --- a/docs/guide/durability/marten/operations.md +++ b/docs/guide/durability/marten/operations.md @@ -14,7 +14,7 @@ The `Wolverine.Marten` library includes some helpers for Wolverine [side effects Marten with the `IMartenOp` interface: - + ```cs /// /// Interface for any kind of Marten related side effect @@ -24,7 +24,7 @@ public interface IMartenOp : ISideEffect void Execute(IDocumentSession session); } ``` -snippet source | anchor +snippet source | anchor The built in side effects can all be used from the `MartenOps` static class like this HTTP endpoint example: diff --git a/docs/guide/durability/marten/outbox.md b/docs/guide/durability/marten/outbox.md index 99de4784a..f2b1eb834 100644 --- a/docs/guide/durability/marten/outbox.md +++ b/docs/guide/durability/marten/outbox.md @@ -75,7 +75,7 @@ The Wolverine outbox is also usable from within ASP.Net Core (really any code) c handling code would be: - + ```cs public class CreateOrderController : ControllerBase { @@ -102,7 +102,7 @@ public class CreateOrderController : ControllerBase } } ``` -snippet source | anchor +snippet source | anchor From a Minimal API, that could be this: diff --git a/docs/guide/durability/marten/subscriptions.md b/docs/guide/durability/marten/subscriptions.md index 0eb4da484..e1f6f8b49 100644 --- a/docs/guide/durability/marten/subscriptions.md +++ b/docs/guide/durability/marten/subscriptions.md @@ -201,7 +201,7 @@ example usage where I'm using [event carried state transfer](https://martinfowle publish batches of reference data about customers being activated or deactivated within our system: - + ```cs public record CompanyActivated(string Name); @@ -269,7 +269,7 @@ public class CompanyTransferSubscription : BatchSubscription } } ``` -snippet source | anchor +snippet source | anchor And the related code to register this subscription: diff --git a/docs/guide/durability/marten/transactional-middleware.md b/docs/guide/durability/marten/transactional-middleware.md index 3ea0d5f34..5ba58d37f 100644 --- a/docs/guide/durability/marten/transactional-middleware.md +++ b/docs/guide/durability/marten/transactional-middleware.md @@ -102,7 +102,7 @@ name ends with "Command" to use the Marten transaction middleware. You could acc with a handler policy like this: - + ```cs public class CommandsAreTransactional : IHandlerPolicy { @@ -116,13 +116,13 @@ public class CommandsAreTransactional : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor Then add the policy to your application like this: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -131,7 +131,7 @@ using var host = await Host.CreateDefaultBuilder() opts.Policies.Add(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Using IDocumentOperations @@ -145,7 +145,7 @@ the ability to commit the ongoing unit of work with a `SaveChangesAsync` API. Here's an example: - + ```cs public class CreateDocCommand2Handler { @@ -163,6 +163,6 @@ public class CreateDocCommand2Handler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/durability/postgresql.md b/docs/guide/durability/postgresql.md index 4cf70ab1a..ad1911849 100644 --- a/docs/guide/durability/postgresql.md +++ b/docs/guide/durability/postgresql.md @@ -221,7 +221,7 @@ Aspire will register `NpgsqlDataSource` services as `Singleton` scoped in your I that utilizes the IoC container to register Wolverine like so: - + ```cs public class OurFancyPostgreSQLMultiTenancy : IWolverineExtension { @@ -244,7 +244,7 @@ public class OurFancyPostgreSQLMultiTenancy : IWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor And add that to the greater application like so: diff --git a/docs/guide/durability/sagas.md b/docs/guide/durability/sagas.md index 68c8f3780..6e17ab94e 100644 --- a/docs/guide/durability/sagas.md +++ b/docs/guide/durability/sagas.md @@ -30,7 +30,7 @@ Jumping right into an example, consider a very simple order management service t For the moment, I’m going to ignore the underlying persistence and just focus on the Wolverine message handlers to implement the order saga workflow with this simplistic saga code: - + ```cs public record StartOrder(string OrderId); @@ -80,7 +80,7 @@ public class Order : Saga } ``` -snippet source | anchor +snippet source | anchor A few explanatory notes on this code before we move on to detailed documentation: @@ -132,7 +132,7 @@ var app = builder.Build(); // Just delegating to Wolverine's local command bus for all app.MapPost("/start", (StartOrder start, IMessageBus bus) => bus.InvokeAsync(start)); -app.MapPost("/complete", (CompleteOrder start, IMessageBus bus) => bus.InvokeAsync(start)); +app.MapPost("/complete", (CompleteOrder complete, IMessageBus bus) => bus.InvokeAsync(complete)); app.MapGet("/all", (IQuerySession session) => session.Query().ToListAsync()); app.MapGet("/", (HttpResponse response) => { @@ -260,7 +260,7 @@ identity. In order of precedence, Wolverine first looks for a member decorated w `[SagaIdentity]` attribute like this: - + ```cs public class ToyOnTray { @@ -271,13 +271,13 @@ public class ToyOnTray [SagaIdentity] public int OrderId { get; set; } } ``` -snippet source | anchor +snippet source | anchor After that, you can also use a new `[SagaIdentityFrom]` (as of 5.9) attribute on~~~~ a handler parameter: - + ```cs public class SomeSaga { @@ -286,7 +286,7 @@ public class SomeSaga public void Handle([SagaIdentityFrom(nameof(SomeSagaMessage5.Hello))] SomeSagaMessage5 message) { } } ``` -snippet source | anchor +snippet source | anchor Next, Wolverine looks for a member named "{saga type name}Id." In the case of our `Order` @@ -473,13 +473,13 @@ Wolverine really wants you to be able to use pure functions as much as possible, for any logical message that will be scheduled in the future like so: - + ```cs // This message will always be scheduled to be delivered after // a one minute delay public record OrderTimeout(string Id) : TimeoutMessage(1.Minutes()); ``` -snippet source | anchor +snippet source | anchor That `OrderTimeout` message can be published with normal cascaded messages (or by calling `IMessageBus.PublishAsync()` if you prefer) diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index c9f15d4bc..a71cfbecc 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -9,7 +9,7 @@ in the IoC container at bootstrapping time. Wolverine supports the concept of extensions for modularizing Wolverine configuration with implementations of the `IWolverineExtension` interface: - + ```cs /// /// Use to create loadable extensions to Wolverine applications @@ -23,13 +23,13 @@ public interface IWolverineExtension void Configure(WolverineOptions options); } ``` -snippet source | anchor +snippet source | anchor Here's a sample: - + ```cs public class SampleExtension : IWolverineExtension { @@ -44,7 +44,7 @@ public class SampleExtension : IWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor Extensions can be applied programmatically against the `WolverineOptions` like this: @@ -99,7 +99,7 @@ await using var host = await AlbaHost.For(x => Behind the scenes, Wolverine has a small extension like this: - + ```cs internal class DisableExternalTransports : IWolverineExtension { @@ -109,7 +109,7 @@ internal class DisableExternalTransports : IWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor And that extension is just added to the application's IoC container at test bootstrapping time like this: @@ -123,7 +123,7 @@ public static IServiceCollection DisableAllExternalWolverineTransports(this ISer return services; } ``` -snippet source | anchor +snippet source | anchor In usage, the `IWolverineExtension` objects added to the IoC container are applied *after* the inner configuration @@ -179,7 +179,7 @@ There is also any option for creating Wolverine extensions that need to use asyn the `WolverineOptions` using the `IAsyncWolverineExtension` library. A sample is shown below: - + ```cs public class SampleAsyncExtension : IAsyncWolverineExtension { @@ -203,7 +203,7 @@ public class SampleAsyncExtension : IAsyncWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor Which can be added to your application with this extension method on `IServiceCollection`: @@ -234,7 +234,7 @@ this method in your `Program` file *after* building the `WebApplication`, but be `MapWolverineEndpoints()` like so: - + ```cs var app = builder.Build(); @@ -242,7 +242,7 @@ var app = builder.Build(); // you will need to explicitly call this *before* MapWolverineEndpoints() await app.Services.ApplyAsyncWolverineExtensions(); ``` -snippet source | anchor +snippet source | anchor ## Wolverine Plugin Modules diff --git a/docs/guide/handlers/batching.md b/docs/guide/handlers/batching.md index 0554fed83..e0d7e42c6 100644 --- a/docs/guide/handlers/batching.md +++ b/docs/guide/handlers/batching.md @@ -172,7 +172,7 @@ To teach Wolverine how to batch up our `SubTaskCompleted` messages into our cust type: - + ```cs /// /// Plugin strategy for creating custom grouping of messages @@ -193,13 +193,13 @@ public interface IMessageBatcher Type BatchMessageType { get; } } ``` -snippet source | anchor +snippet source | anchor A custom implementation of that interface in this case would look like this: - + ```cs public class SubTaskCompletedBatcher : IMessageBatcher { @@ -231,13 +231,13 @@ public class SubTaskCompletedBatcher : IMessageBatcher public Type BatchMessageType => typeof(SubTaskCompletedBatch); } ``` -snippet source | anchor +snippet source | anchor And of course, this doesn't work without a matching message handler for our custom message type: - + ```cs public static class SubTaskCompletedBatchHandler { @@ -254,7 +254,7 @@ public static class SubTaskCompletedBatchHandler } } ``` -snippet source | anchor +snippet source | anchor And finally, we need to tell Wolverine about the batching and the strategy for batching the `SubTaskCompleted` diff --git a/docs/guide/handlers/cascading.md b/docs/guide/handlers/cascading.md index 1db2c82ff..52a9f8844 100644 --- a/docs/guide/handlers/cascading.md +++ b/docs/guide/handlers/cascading.md @@ -17,7 +17,7 @@ maybe you need to trigger a subsequent action, or send out additional messages t your handler class use the `IMessageContext` interface as shown in this sample: - + ```cs public class NoCascadingHandler { @@ -36,14 +36,14 @@ public class NoCascadingHandler } } ``` -snippet source | anchor +snippet source | anchor The code above certainly works and this is consistent with most of the competing service bus tools. However, Wolverine supports the concept of _cascading messages_ that allow you to automatically send out objects returned from your handler methods without having to use `IMessageContext` as shown below: - + ```cs public class CascadingHandler { @@ -53,7 +53,7 @@ public class CascadingHandler } } ``` -snippet source | anchor +snippet source | anchor When Wolverine executes `CascadingHandler.Consume(MyMessage)`, it "knows" that the `MyResponse` return value should be sent through the @@ -83,7 +83,7 @@ Sometimes you'll want to explicitly send messages to specific endpoints rather t You can still use cascading messages to an endpoint by name or by the destination `Uri` like so: - + ```cs public class ManuallyRoutedResponseHandler { @@ -97,7 +97,7 @@ public class ManuallyRoutedResponseHandler } } ``` -snippet source | anchor +snippet source | anchor You can also add optional `DeliveryOptions` to the outgoing messages to fine tune how the message is to be published. @@ -115,7 +115,7 @@ helps Wolverine "know" that these messages should be cascaded after the initial The usage of this is shown below: - + ```cs public static OutgoingMessages Handle(Incoming incoming) { @@ -141,7 +141,7 @@ public static OutgoingMessages Handle(Incoming incoming) return messages; } ``` -snippet source | anchor +snippet source | anchor Do note that the value of `OutgoingMessages` is probably greatest when being used in a tuple response from a handler that's a mix @@ -192,7 +192,7 @@ Let's say that we have two running service bus nodes named "Sender" and "Receive is called from the "Sender" node: - + ```cs public class Requester { @@ -209,13 +209,13 @@ public class Requester } } ``` -snippet source | anchor +snippet source | anchor and inside Receiver we have this code: - + ```cs public class CascadingHandler { @@ -225,7 +225,7 @@ public class CascadingHandler } } ``` -snippet source | anchor +snippet source | anchor Assuming that `MyMessage` is configured to be sent to "Receiver," the following steps take place: @@ -244,7 +244,7 @@ different types of cascading messages based on some kind of logic, you can still be `object` like this sample shown below: - + ```cs public class ConditionalResponseHandler { @@ -263,7 +263,7 @@ public class ConditionalResponseHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -272,7 +272,7 @@ public class ConditionalResponseHandler You may want to raise a delayed or scheduled response. In this case you will need to return an `Envelope` for the response as shown below: - + ```cs public class ScheduledResponseHandler { @@ -288,7 +288,7 @@ public class ScheduledResponseHandler } } ``` -snippet source | anchor +snippet source | anchor ## Multiple Cascading Messages @@ -298,7 +298,7 @@ cast to `IEnumerable`, and Wolverine will treat each element as a separa An empty enumerable is just ignored. - + ```cs public class MultipleResponseHandler { @@ -312,7 +312,7 @@ public class MultipleResponseHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -325,7 +325,7 @@ C# tuples to better denote the cascading message types. This handler cascading a pair of messages: - + ```cs public class MultipleResponseHandler { @@ -339,13 +339,13 @@ public class MultipleResponseHandler } } ``` -snippet source | anchor +snippet source | anchor can be rewritten with C# 7 tuples to: - + ```cs public class TupleResponseHandler { @@ -357,7 +357,7 @@ public class TupleResponseHandler } } ``` -snippet source | anchor +snippet source | anchor The sample above still treats both `GoNorth` and the `ScheduledResponse` as cascading messages. The Wolverine team thinks that the diff --git a/docs/guide/handlers/discovery.md b/docs/guide/handlers/discovery.md index 0bec1b496..c41dc8e49 100644 --- a/docs/guide/handlers/discovery.md +++ b/docs/guide/handlers/discovery.md @@ -128,7 +128,7 @@ In all cases, Wolverine assumes that the first argument is the incoming message. To make that concrete, here are some valid handler method signatures: - + ```cs [WolverineHandler] public class ValidMessageHandlers @@ -181,7 +181,7 @@ public class ValidMessageHandlers } } ``` -snippet source | anchor +snippet source | anchor The valid method names are: @@ -214,7 +214,7 @@ You can completely turn off any automatic discovery of message handlers through using this syntax in your `WolverineOptions`: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -223,7 +223,7 @@ using var host = await Host.CreateDefaultBuilder() opts.Discovery.DisableConventionalDiscovery(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Replacing the Handler Discovery Rules @@ -256,7 +256,7 @@ You can force Wolverine to disregard a candidate message handler action at eithe level by using the `[WolverineIgnore]` attribute like this: - + ```cs public class NetflixHandler : IMovieSink { @@ -304,7 +304,7 @@ public class BlockbusterHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -321,7 +321,7 @@ from those naming conventions you can either supplement the handler discovery or At a minimum, you can disable the built in discovery, add additional type filtering criteria, or register specific handler classes with the code below: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -344,5 +344,5 @@ using var host = await Host.CreateDefaultBuilder() .IncludeType(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/error-handling.md b/docs/guide/handlers/error-handling.md index 7ca07228e..1148c298e 100644 --- a/docs/guide/handlers/error-handling.md +++ b/docs/guide/handlers/error-handling.md @@ -229,7 +229,7 @@ public class MyErrorCausingHandler To specify global error handling rules, use the fluent interface directly on `WolverineOptions.Handlers` as shown below: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -244,7 +244,7 @@ using var host = await Host.CreateDefaultBuilder() .ScheduleRetry(5.Seconds()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor TODO -- link to chain policies, after that exists:) @@ -253,7 +253,7 @@ Lastly, you can use chain policies to add error handling policies to a selected a sample policy that applies an error handling policy based on `SqlException` errors for all message types from a certain namespace: - + ```cs // This error policy will apply to all message types in the namespace // 'MyApp.Messages', and add a "requeue on SqlException" to all of these @@ -269,7 +269,7 @@ public class ErrorHandlingPolicy : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor ## Exception Filtering @@ -326,7 +326,7 @@ Optionally, you can implement a new type to handle this same custom logic by subclassing the `Wolverine.ErrorHandling.UserDefinedContinuation` type like so: - + ```cs public class ShippingOrderFailurePolicy : UserDefinedContinuation { @@ -346,7 +346,7 @@ public class ShippingOrderFailurePolicy : UserDefinedContinuation } } ``` -snippet source | anchor +snippet source | anchor and register that secondary action like this: diff --git a/docs/guide/handlers/index.md b/docs/guide/handlers/index.md index 8167859b3..a402547ec 100644 --- a/docs/guide/handlers/index.md +++ b/docs/guide/handlers/index.md @@ -37,14 +37,14 @@ allow *you* to just write plain old .NET code without any framework specific art Back to the handler code, at the point which you pass a new message into Wolverine like so: - + ```cs public static async Task publish_command(IMessageBus bus) { await bus.PublishAsync(new MyMessage()); } ``` -snippet source | anchor +snippet source | anchor Between the call to `IMessageBus.PublishAsync()` and `MyMessageHandler.Handle(MyMessage)` there's a couple things @@ -58,7 +58,7 @@ going on: Before diving into the exact rules for message handlers, here are some valid handler methods: - + ```cs [WolverineHandler] public class ValidMessageHandlers @@ -111,7 +111,7 @@ public class ValidMessageHandlers } } ``` -snippet source | anchor +snippet source | anchor It's also valid to use class instances with constructor arguments for your handlers: @@ -186,7 +186,7 @@ Now though, you can flip this switch in one place to ensure that Wolverine alway into completely separate Wolverine message handlers and message subscriptions: - + ```cs using var host = Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -195,7 +195,7 @@ using var host = Host.CreateDefaultBuilder() opts.MultipleHandlerBehavior = MultipleHandlerBehavior.Separated; }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor This makes a couple changes. For example, let's say that you have these handlers for the same message type of `MyApp.Orders.OrderCreated`: @@ -247,7 +247,7 @@ Some add ons or middleware add other possibilities as well. Handler methods can be instance methods on handler classes if it's desirable to scope the handler object to the message: - + ```cs public class ExampleHandler { @@ -263,7 +263,7 @@ public class ExampleHandler } } ``` -snippet source | anchor +snippet source | anchor When using instance methods, the containing handler type will be scoped to a single message and be @@ -271,7 +271,7 @@ disposed afterward. In the case of instance methods, it's perfectly legal to use to resolve IoC registered dependencies as shown below: - + ```cs public class ServiceUsingHandler { @@ -291,7 +291,7 @@ public class ServiceUsingHandler } } ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -302,7 +302,7 @@ improvement by avoiding the need to create and garbage collect new objects at ru As an alternative, you can also use static methods as message handlers: - + ```cs public static class ExampleHandler { @@ -318,7 +318,7 @@ public static class ExampleHandler } } ``` -snippet source | anchor +snippet source | anchor The handler classes can be static classes as well. This technique gets much more useful when combined with Wolverine's @@ -333,7 +333,7 @@ arguments that will be passed into your method by Wolverine when a new message i Below is an example action method that takes in a dependency on an `IDocumentSession` from [Marten](https://jasperfx.github.io/marten/): - + ```cs public static class MethodInjectionHandler { @@ -346,7 +346,7 @@ public static class MethodInjectionHandler } } ``` -snippet source | anchor +snippet source | anchor So, what can be injected as an argument to your message handler? @@ -384,7 +384,7 @@ data for both the order itself and the customer information in order to figure o Using Wolverine's compound handler feature, that might look like this: - + ```cs public static class ShipOrderHandler { @@ -414,7 +414,7 @@ public static class ShipOrderHandler } } ``` -snippet source | anchor +snippet source | anchor ::: warning @@ -453,7 +453,7 @@ To access the `Envelope` for the current message being handled in your message h argument like this: - + ```cs public class EnvelopeUsingHandler { @@ -464,7 +464,7 @@ public class EnvelopeUsingHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -475,7 +475,7 @@ or maybe to enqueue local commands within the current outbox scope, just take in like in this example: - + ```cs using Messages; using Microsoft.Extensions.Logging; @@ -492,8 +492,8 @@ public class PingHandler } } ``` -snippet source | anchor - +snippet source | anchor + ```cs public static class PingHandler { @@ -522,6 +522,6 @@ public static class PingHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/middleware.md b/docs/guide/handlers/middleware.md index 10f2a10b1..157fef73c 100644 --- a/docs/guide/handlers/middleware.md +++ b/docs/guide/handlers/middleware.md @@ -50,7 +50,7 @@ to HTTP endpoints, see [HTTP endpoint middleware](/guide/http/middleware). As an example middleware using Wolverine's conventional approach, here's the stopwatch functionality from above: - + ```cs public class StopwatchMiddleware { @@ -69,7 +69,7 @@ public class StopwatchMiddleware } } ``` -snippet source | anchor +snippet source | anchor and that can be added to our application at bootstrapping time like this: @@ -174,7 +174,7 @@ Here's an example from the [custom middleware tutorial](/tutorials/middleware) t by the incoming message and aborts the message processing if it is not found: - + ```cs // This is *a* way to build middleware in Wolverine by basically just // writing functions/methods. There's a naming convention that @@ -207,7 +207,7 @@ public static class AccountLookupMiddleware } } ``` -snippet source | anchor +snippet source | anchor Notice that the middleware above uses a tuple as the return value so that it can both pass an `Account` entity to the inner handler and also @@ -285,14 +285,14 @@ public static class MaybeBadThing2Handler Let's say that some of our message types implement this interface: - + ```cs public interface IAccountCommand { Guid AccountId { get; } } ``` -snippet source | anchor +snippet source | anchor We can apply the `AccountMiddleware` from the section above to only these message types by telling Wolverine to only apply this middleware @@ -364,7 +364,7 @@ For more advanced usage, you can drop down to the JasperFx.CodeGeneration `Frame The first step is to create a JasperFx.CodeGeneration `Frame` class that generates that code around the inner message or HTTP handler: - + ```cs public class StopwatchFrame : SyncFrame { @@ -410,7 +410,7 @@ public class StopwatchFrame : SyncFrame } } ``` -snippet source | anchor +snippet source | anchor @@ -420,7 +420,7 @@ To attach our `StopwatchFrame` as middleware to any route or message handler, we `ModifyChainAttribute` class as shown below: - + ```cs public class StopwatchAttribute : ModifyChainAttribute { @@ -430,7 +430,7 @@ public class StopwatchAttribute : ModifyChainAttribute } } ``` -snippet source | anchor +snippet source | anchor This attribute can now be placed either on a specific HTTP route endpoint method or message handler method to **only** apply to @@ -439,7 +439,7 @@ that specific action, or it can be placed on a `Handler` or `Endpoint` class to Here's an example: - + ```cs public class ClockedEndpoint { @@ -450,7 +450,7 @@ public class ClockedEndpoint } } ``` -snippet source | anchor +snippet source | anchor Now, when the application is bootstrapped, this is the code that would be generated to handle the "GET /clocked" route: @@ -499,7 +499,7 @@ Again, please go easy with this feature and try not to shoot yourself in the foo You can register user-defined policies that apply to all chains or some subset of chains. For message handlers, implement this interface: - + ```cs /// /// Use to apply your own conventions or policies to message handlers @@ -515,13 +515,13 @@ public interface IHandlerPolicy : IWolverinePolicy void Apply(IReadOnlyList chains, GenerationRules rules, IServiceContainer container); } ``` -snippet source | anchor +snippet source | anchor Here's a simple sample that registers middleware on each handler chain: - + ```cs public class WrapWithSimple : IHandlerPolicy { @@ -531,18 +531,18 @@ public class WrapWithSimple : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor Then register your custom `IHandlerPolicy` with a Wolverine application like this: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.Policies.Add(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Using Configure(chain) Methods @@ -577,7 +577,7 @@ public static void Configure(HandlerChain chain) Here's an example of this being used from Wolverine's test suite: - + ```cs public class CustomizedHandler { @@ -598,7 +598,7 @@ public class CustomizedHandler } } ``` -snippet source | anchor +snippet source | anchor ## Sending Messages from Middleware diff --git a/docs/guide/handlers/persistence.md b/docs/guide/handlers/persistence.md index f398d19ca..680be5834 100644 --- a/docs/guide/handlers/persistence.md +++ b/docs/guide/handlers/persistence.md @@ -112,7 +112,7 @@ or request body, you can specify the identity value source through the `EntityAt to one of these values: - + ```cs public enum ValueSource { @@ -137,7 +137,7 @@ public enum ValueSource FromQueryString } ``` -snippet source | anchor +snippet source | anchor Some other facts to know about `[Entity]` usage: diff --git a/docs/guide/handlers/return-values.md b/docs/guide/handlers/return-values.md index 382678b3a..859a63760 100644 --- a/docs/guide/handlers/return-values.md +++ b/docs/guide/handlers/return-values.md @@ -54,7 +54,7 @@ For an example, let's say that you want to isolate the [side effect](https://en. methods by returning a custom return value called `WriteFile`: - + ```cs // This has to be public btw public record WriteFile(string Path, string Contents) @@ -65,8 +65,8 @@ public record WriteFile(string Path, string Contents) } } ``` -snippet source | anchor - +snippet source | anchor + ```cs // ISideEffect is a Wolverine marker interface public class WriteFile : ISideEffect @@ -92,7 +92,7 @@ public class WriteFile : ISideEffect } } ``` -snippet source | anchor +snippet source | anchor And now, let's teach Wolverine to call the `WriteAsync()` method on each `WriteFile` that is returned from a message handler @@ -100,7 +100,7 @@ at runtime instead of Wolverine using the default policy of treating it as a cas to write a custom `IChainPolicy` like so: - + ```cs internal class WriteFilePolicy : IChainPolicy { @@ -133,17 +133,17 @@ internal class WriteFilePolicy : IChainPolicy } } ``` -snippet source | anchor +snippet source | anchor and lastly, I'll register that policy in my Wolverine application at configuration time: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.Policies.Add(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/side-effects.md b/docs/guide/handlers/side-effects.md index 0dbfb1740..3de55a213 100644 --- a/docs/guide/handlers/side-effects.md +++ b/docs/guide/handlers/side-effects.md @@ -24,7 +24,7 @@ irritating to use in tests). First off, I'm going to create a new "side effect" type for writing a file like this: - + ```cs // This has to be public btw public record WriteFile(string Path, string Contents) @@ -35,8 +35,8 @@ public record WriteFile(string Path, string Contents) } } ``` -snippet source | anchor - +snippet source | anchor + ```cs // ISideEffect is a Wolverine marker interface public class WriteFile : ISideEffect @@ -62,13 +62,13 @@ public class WriteFile : ISideEffect } } ``` -snippet source | anchor +snippet source | anchor And the matching message type, message handler, and a settings class for configuration: - + ```cs // An options class public class PathSettings @@ -89,7 +89,7 @@ public class RecordTextHandler } } ``` -snippet source | anchor +snippet source | anchor At runtime, Wolverine is generating this code to handle the `RecordText` message: diff --git a/docs/guide/handlers/sticky.md b/docs/guide/handlers/sticky.md index 6c7786f57..7dba52b26 100644 --- a/docs/guide/handlers/sticky.md +++ b/docs/guide/handlers/sticky.md @@ -21,11 +21,11 @@ be handled completely separately by two different handlers performing two differ message as an input. - + ```cs public class StickyMessage; ``` -snippet source | anchor +snippet source | anchor And we're going to handle that `StickyMessage` message separately with two different handler types: diff --git a/docs/guide/handlers/timeout.md b/docs/guide/handlers/timeout.md index ee2087e75..640ae62ce 100644 --- a/docs/guide/handlers/timeout.md +++ b/docs/guide/handlers/timeout.md @@ -24,10 +24,10 @@ To override the message timeout on a message type by message type basis, you can attribute as shown below: - + ```cs [MessageTimeout(1)] public async Task Handle(PotentiallySlowMessage message, CancellationToken cancellationToken) ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/as-parameters.md b/docs/guide/http/as-parameters.md index ef9e071e9..910f61d4a 100644 --- a/docs/guide/http/as-parameters.md +++ b/docs/guide/http/as-parameters.md @@ -181,7 +181,7 @@ The [Fluent Validation middleware](./fluentvalidation) for Wolverine.HTTP is abl bound with `[AsParameters]`: - + ```cs public static class ValidatedAsParametersEndpoint { @@ -208,5 +208,5 @@ public class ValidatedQuery } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/endpoints.md b/docs/guide/http/endpoints.md index 17ff77846..308df87b8 100644 --- a/docs/guide/http/endpoints.md +++ b/docs/guide/http/endpoints.md @@ -171,7 +171,7 @@ to use any return values as a the endpoint response and to return an empty respo code. Here's an example from the tests: - + ```cs [AggregateHandler] [WolverinePost("/orders/ship"), EmptyResponse] @@ -184,7 +184,7 @@ public static OrderShipped Ship(ShipOrder command, Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor ## JSON Handling @@ -217,7 +217,7 @@ The `IResult` mechanics are applied to the return value of any type that can be Wolverine will execute an ASP.Net Core `IResult` object returned from an HTTP endpoint method. - + ```cs [WolverinePost("/choose/color")] public IResult Redirect(GoToColor request) @@ -235,7 +235,7 @@ public IResult Redirect(GoToColor request) } } ``` -snippet source | anchor +snippet source | anchor @@ -280,7 +280,7 @@ There's actually a way to customize how Wolverine handles parameters in HTTP end To do so, you'd need to write an implementation of the `IParameterStrategy` interface from Wolverine.Http: - + ```cs /// /// Apply custom handling to a Wolverine.Http endpoint/chain based on a parameter within the @@ -292,14 +292,14 @@ public interface IParameterStrategy bool TryMatch(HttpChain chain, IServiceContainer container, ParameterInfo parameter, out Variable? variable); } ``` -snippet source | anchor +snippet source | anchor As an example, let's say that you want any parameter of type `DateTimeOffset` that's named "now" to receive the current system time. To do that, we can write this class: - + ```cs public class NowParameterStrategy : IParameterStrategy { @@ -318,7 +318,7 @@ public class NowParameterStrategy : IParameterStrategy } } ``` -snippet source | anchor +snippet source | anchor and register that strategy within our `MapWolverineEndpoints()` set up like so: @@ -329,7 +329,7 @@ and register that strategy within our `MapWolverineEndpoints()` set up like so: // Customizing parameter handling opts.AddParameterHandlingStrategy(); ``` -snippet source | anchor +snippet source | anchor And lastly, here's the application within an HTTP endpoint for extra context: @@ -432,7 +432,7 @@ As of Wolverine 5.7, you can also technically use `HttpContext` arguments in the you are carefully accounting for that being null as shown in this sample: - + ```cs public record DoHybrid(string Message); @@ -451,7 +451,7 @@ public static class HybridHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/index.md b/docs/guide/http/index.md index 2a895f2a1..3b9b42690 100644 --- a/docs/guide/http/index.md +++ b/docs/guide/http/index.md @@ -64,7 +64,7 @@ Okay, but we still need to expose a web service endpoint for this functionality. as a "mediator" tool like so: - + ```cs public class TodoController : ControllerBase { @@ -81,7 +81,7 @@ public class TodoController : ControllerBase } } ``` -snippet source | anchor +snippet source | anchor Or we could do the same thing with Minimal API: diff --git a/docs/guide/http/integration.md b/docs/guide/http/integration.md index 363cb65ec..7976f2311 100644 --- a/docs/guide/http/integration.md +++ b/docs/guide/http/integration.md @@ -126,7 +126,7 @@ public async Task hello_world() Moving on to the actual `Todo` problem domain, let's assume we've got a class like this: - + ```cs public class Todo { @@ -135,7 +135,7 @@ public class Todo public bool IsComplete { get; set; } } ``` -snippet source | anchor +snippet source | anchor In a sample class called [TodoEndpoints](https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs) @@ -159,7 +159,7 @@ will shine in more complicated endpoints. Consider this endpoint just to return the data for a single `Todo` document: - + ```cs // Wolverine can infer the 200/404 status codes for you here // so there's no code noise just to satisfy OpenAPI tooling @@ -167,7 +167,7 @@ Consider this endpoint just to return the data for a single `Todo` document: public static Task GetTodo(int id, IQuerySession session, CancellationToken cancellation) => session.LoadAsync(id, cancellation); ``` -snippet source | anchor +snippet source | anchor At this point it's effectively de rigueur for any web service to support [OpenAPI](https://www.openapis.org/) documentation directly @@ -212,7 +212,7 @@ handling and response by whether or not the document actually exists. Just to sh and also how WolverineFx.Http supports middleware, consider this more complex endpoint: - + ```cs public static class UpdateTodoEndpoint { @@ -233,7 +233,7 @@ public static class UpdateTodoEndpoint } } ``` -snippet source | anchor +snippet source | anchor ## How it Works diff --git a/docs/guide/http/marten.md b/docs/guide/http/marten.md index c37ad2710..700972846 100644 --- a/docs/guide/http/marten.md +++ b/docs/guide/http/marten.md @@ -93,7 +93,7 @@ If you want soft-deleted documents to be treated as `NULL` for a endpoint, you c In combination with `Required = true` that means the endpoint will return 404 for missing and soft-deleted documents. - + ```cs [WolverineGet("/invoices/soft-delete/{id}")] public static Invoice GetSoftDeleted([Document(Required = true, MaybeSoftDeleted = false)] Invoice invoice) @@ -101,7 +101,7 @@ public static Invoice GetSoftDeleted([Document(Required = true, MaybeSoftDeleted return invoice; } ``` -snippet source | anchor +snippet source | anchor @@ -266,7 +266,7 @@ public class Order To append a single event to an event stream from an HTTP endpoint, you can use a return value like so: - + ```cs [AggregateHandler] [WolverinePost("/orders/ship"), EmptyResponse] @@ -279,7 +279,7 @@ public static OrderShipped Ship(ShipOrder command, Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor Or potentially append multiple events using the `Events` type as a return value like this sample: @@ -348,7 +348,38 @@ If you should happen to have a message handler or HTTP endpoint signature that u but you want the `UpdatedAggregate` to **only** apply to one of the streams, you can use the `UpdatedAggregate` to tip off Wolverine about that like in this sample: -snippet: sample_MakePurchaseHandler + + +```cs +public static class MakePurchaseHandler +{ + // See how we used the generic version + // of UpdatedAggregate to tell Wolverine we + // want *only* the XAccount as the response + // from this handler + public static UpdatedAggregate Handle( + MakePurchase command, + + [WriteAggregate] IEventStream account, + + [WriteAggregate] IEventStream inventory) + { + if (command.Number > inventory.Aggregate.Quantity || + (command.Number * inventory.Aggregate.UnitPrice) > account.Aggregate.Balance) + { + // Do Nothing! + return new UpdatedAggregate(); + } + + account.AppendOne(new ItemPurchased(command.InventoryId, command.Number, inventory.Aggregate.UnitPrice)); + inventory.AppendOne(new Drawdown(command.Number)); + + return new UpdatedAggregate(); + } +} +``` +snippet source | anchor + ::: info Wolverine can't (yet) handle a signature with multiple event streams of the same aggregate type and @@ -366,12 +397,12 @@ If you want to inject the current state of an event sourced aggregate as a param an HTTP endpoint method, use the `[ReadAggregate]` attribute like this: - + ```cs [WolverineGet("/orders/latest/{id}")] public static Order GetLatest(Guid id, [ReadAggregate] Order order) => order; ``` -snippet source | anchor +snippet source | anchor If the aggregate doesn't exist, the HTTP request will stop with a 404 status code. @@ -391,7 +422,7 @@ Register it in `WolverineHttpOptions` like this: ```cs opts.UseMartenCompiledQueryResultPolicy(); ``` -snippet source | anchor +snippet source | anchor If you now return a compiled query from an Endpoint the result will get directly streamed to the client as JSON. Short circuiting JSON deserialization. diff --git a/docs/guide/http/mediator.md b/docs/guide/http/mediator.md index 492a10b0f..ef7ecc1eb 100644 --- a/docs/guide/http/mediator.md +++ b/docs/guide/http/mediator.md @@ -36,16 +36,16 @@ The functionality is used from extension methods off of the ASP.Net Core [WebApp ```cs // Functional equivalent to MapPost(pattern, (command, IMessageBus) => bus.Invoke(command)) -app.MapPostToWolverine("/wolverine"); -app.MapPutToWolverine("/wolverine"); -app.MapDeleteToWolverine("/wolverine"); + app.MapPostToWolverine("/wolverine"); + app.MapPutToWolverine("/wolverine"); + app.MapDeleteToWolverine("/wolverine"); // Functional equivalent to MapPost(pattern, (command, IMessageBus) => bus.Invoke(command)) -app.MapPostToWolverine("/wolverine/request"); -app.MapDeleteToWolverine("/wolverine/request"); -app.MapPutToWolverine("/wolverine/request"); + app.MapPostToWolverine("/wolverine/request"); + app.MapDeleteToWolverine("/wolverine/request"); + app.MapPutToWolverine("/wolverine/request"); ``` -snippet source | anchor +snippet source | anchor With this mechanism, Wolverine is able to optimize the runtime function for Minimal API by eliminating IoC service locations diff --git a/docs/guide/http/messaging.md b/docs/guide/http/messaging.md index 318a84596..d0f499ffd 100644 --- a/docs/guide/http/messaging.md +++ b/docs/guide/http/messaging.md @@ -10,7 +10,7 @@ So there's absolutely nothing stopping you from just using `IMessageBus` as an i dependency to a Wolverine HTTP endpoint method to publish messages like this sample: - + ```cs // This would have an empty response and a 204 status code [WolverinePost("/spawn3")] @@ -20,7 +20,7 @@ public static async ValueTask SendViaMessageBus(IMessageBus bus) await bus.PublishAsync(new HttpMessage2("bar")); } ``` -snippet source | anchor +snippet source | anchor But of course there's some other alternatives to directly using `IMessageBus` by utilizing Wolverine's [cascading messages](/guide/handlers/cascading) @@ -39,7 +39,7 @@ to be immediately published to Wolverine without any need for additional Wolveri Note that this mechanism will return an empty body with a status code of 202 to denote future processing. - + ```cs var builder = WebApplication.CreateBuilder(); @@ -61,13 +61,13 @@ app.MapWolverineEndpoints(opts => // and the rest of your application configuration and bootstrapping ``` -snippet source | anchor +snippet source | anchor On the other hand, the `PublishAsync()` method will send a message if there is a known subscriber and ignore the message if there is no subscriber (as explained in [sending or publishing Messages](/guide/messaging/message-bus#sending-or-publishing-messages)): - + ```cs var builder = WebApplication.CreateBuilder(); @@ -89,7 +89,7 @@ app.MapWolverineEndpoints(opts => // and the rest of your application configuration and bootstrapping ``` -snippet source | anchor +snippet source | anchor Middleware policies from Wolverine.Http are applicable to these endpoints, so for example, it's feasible to use @@ -104,7 +104,7 @@ that this collection of objects is meant to be cascaded messages that are publis Here's an example: - + ```cs // This would have a string response and a 200 status code [WolverinePost("/spawn")] @@ -121,14 +121,14 @@ public static (string, OutgoingMessages) Post(SpawnInput input) return ("got it", messages); } ``` -snippet source | anchor +snippet source | anchor Otherwise, if you want to make it clearer from the signature of your HTTP handler method what messages are cascaded and there's no variance in the type of messages published, you can use additional tuple return values like this: - + ```cs // This would have an empty response and a 204 status code [EmptyResponse] @@ -138,5 +138,5 @@ public static (HttpMessage1, HttpMessage2) Post() return new(new HttpMessage1("foo"), new HttpMessage2("bar")); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/metadata.md b/docs/guide/http/metadata.md index 21ebd70bc..5e7eed00d 100644 --- a/docs/guide/http/metadata.md +++ b/docs/guide/http/metadata.md @@ -68,7 +68,7 @@ for finer grained control. Here's a sample from the Wolverine testing code that determine the OpenAPI operation id: - + ```cs // This class is NOT distributed in any kind of Nuget today, but feel very free // to copy this code into your own as it is at least tested through Wolverine's @@ -84,7 +84,7 @@ public class WolverineOperationFilter : IOperationFilter // IOperationFilter is } } ``` -snippet source | anchor +snippet source | anchor And that would be registered with Swashbuckle inside of your `Program.Main()` method like so: @@ -92,12 +92,9 @@ And that would be registered with Swashbuckle inside of your `Program.Main()` me ```cs -builder.Services.AddSwaggerGen(x => -{ - x.OperationFilter(); -}); +builder.Services.AddSwaggerGen(x => { x.OperationFilter(); }); ``` -snippet source | anchor +snippet source | anchor ## Operation Id @@ -135,7 +132,7 @@ an HTTP endpoint, you can have your response type implement the `IHttpAware` int consider the `CreationResponse` type in Wolverine: - + ```cs /// /// Base class for resource types that denote some kind of resource being created @@ -161,7 +158,7 @@ public record CreationResponse([StringSyntax("Route")]string Url) : IHttpAware public static CreationResponse For(T value, string url) => new CreationResponse(url, value); } ``` -snippet source | anchor +snippet source | anchor Any endpoint that returns `CreationResponse` or a sub class will automatically expose a status code of `201` for successful diff --git a/docs/guide/http/middleware.md b/docs/guide/http/middleware.md index 32a612d89..53d918f2d 100644 --- a/docs/guide/http/middleware.md +++ b/docs/guide/http/middleware.md @@ -49,7 +49,7 @@ Which is registered like this (or as described in [`Registering Middleware by Me opts.AddMiddlewareByMessageType(typeof(FakeAuthenticationMiddleware)); opts.AddMiddlewareByMessageType(typeof(CanShipOrderMiddleWare)); ``` -snippet source | anchor +snippet source | anchor The key point to notice there is that `IResult` is a "return value" of the middleware. In the case of an HTTP endpoint, @@ -59,7 +59,7 @@ object is anything else, Wolverine will execute that `IResult` and stop processi For a little more complex example, here's part of the Fluent Validation middleware for Wolverine.Http: - + ```cs [MethodImpl(MethodImplOptions.AggressiveInlining)] public static async Task ExecuteOne(IValidator validator, IProblemDetailSource source, T message) @@ -79,7 +79,7 @@ public static async Task ExecuteOne(IValidator validator, IProble return WolverineContinue.Result(); } ``` -snippet source | anchor +snippet source | anchor Likewise, you can also just return a `null` from middleware for `IResult` and Wolverine will interpret that as @@ -185,7 +185,7 @@ this middleware is applied to any endpoint that also uses Wolverine message publ from the `HttpContext` to subsequent Wolverine messages published during the request: - + ```cs public static class RequestIdMiddleware { @@ -201,13 +201,13 @@ public static class RequestIdMiddleware } } ``` -snippet source | anchor +snippet source | anchor And a matching `IHttpPolicy` to apply that middleware to any HTTP endpoint where there is a dependency on Wolverine's `IMessageContext` or `IMessageBus`: - + ```cs internal class RequestIdPolicy : IHttpPolicy { @@ -225,7 +225,7 @@ internal class RequestIdPolicy : IHttpPolicy } } ``` -snippet source | anchor +snippet source | anchor Lastly, this particular policy is included by default, but if it wasn't, this is the code to apply it explicitly: diff --git a/docs/guide/http/multi-tenancy.md b/docs/guide/http/multi-tenancy.md index 0cdb8e665..98d745973 100644 --- a/docs/guide/http/multi-tenancy.md +++ b/docs/guide/http/multi-tenancy.md @@ -266,7 +266,7 @@ of multi-tenancy, so you can completely opt out of all tenant id detection and a `[NotTenanted]` attribute as shown here in the tests: - + ```cs // Mark this endpoint as not using any kind of multi-tenancy [WolverineGet("/nottenanted"), NotTenanted] @@ -275,7 +275,7 @@ public static string NoTenantNoProblem() return "hey"; } ``` -snippet source | anchor +snippet source | anchor If the above usage completely disabled all tenant id detection or validation, in the case of an endpoint that *might* be @@ -305,7 +305,7 @@ parameters. Wolverine still has you covered by allowing you to create custom imp interface: - + ```cs /// /// Used to create new strategies to detect the tenant id from an HttpContext @@ -321,13 +321,13 @@ public interface ITenantDetection public ValueTask DetectTenant(HttpContext context); } ``` -snippet source | anchor +snippet source | anchor As any example, the route argument detection implementation looks like this: - + ```cs internal class ArgumentDetection : ITenantDetection, ISynchronousTenantDetection { @@ -356,7 +356,7 @@ internal class ArgumentDetection : ITenantDetection, ISynchronousTenantDetection } } ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -468,7 +468,7 @@ Using the `WolverineFx.Http.Marten` Nuget, there's a helper to replace Marten's with a multi-tenanted version like this: - + ```cs builder.Services.AddMartenTenancyDetection(tenantId => { @@ -476,8 +476,8 @@ builder.Services.AddMartenTenancyDetection(tenantId => tenantId.DefaultIs("default-tenant"); }); ``` -snippet source | anchor - +snippet source | anchor + ```cs builder.Services.AddMartenTenancyDetection(tenantId => { @@ -488,6 +488,6 @@ builder.Services.AddMartenTenancyDetection(tenantId => session.CorrelationId = c.TraceIdentifier; }); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/policies.md b/docs/guide/http/policies.md index eb0792aff..09e31c689 100644 --- a/docs/guide/http/policies.md +++ b/docs/guide/http/policies.md @@ -4,7 +4,7 @@ Custom policies can be created for HTTP endpoints through either creating your o shown below: - + ```cs /// /// Use to apply your own conventions or policies to HTTP endpoint handlers @@ -20,7 +20,7 @@ public interface IHttpPolicy void Apply(IReadOnlyList chains, GenerationRules rules, IServiceContainer container); } ``` -snippet source | anchor +snippet source | anchor And then adding a policy to the `WolverineHttpOptions` like this code from the Fluent Validation extension for HTTP: @@ -64,12 +64,12 @@ app.MapWolverineEndpoints(opts => // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); - + // Or instead, you could use Data Annotations that are built // into the Wolverine.HTTP library opts.UseDataAnnotationsValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor The `HttpChain` model is a configuration time structure that Wolverine.Http will use at runtime to create the full @@ -100,12 +100,12 @@ app.MapWolverineEndpoints(opts => // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); - + // Or instead, you could use Data Annotations that are built // into the Wolverine.HTTP library opts.UseDataAnnotationsValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor ## Resource Writer Policies @@ -113,7 +113,7 @@ app.MapWolverineEndpoints(opts => Wolverine has an additional type of policy that deals with how an endpoints primary result is handled. - + ```cs /// /// Use to apply custom handling to the primary result of an HTTP endpoint handler @@ -128,7 +128,7 @@ public interface IResourceWriterPolicy bool TryApply(HttpChain chain); } ``` -snippet source | anchor +snippet source | anchor Only one of these so called resource writer policies can apply to each endpoint and there are a couple of built in policies already. @@ -140,7 +140,7 @@ If you need special handling of a primary return type you can implement `IResour ```cs opts.AddResourceWriterPolicy(); ``` -snippet source | anchor +snippet source | anchor Resource writer policies registered this way will be applied in order before all built in policies. diff --git a/docs/guide/http/problemdetails.md b/docs/guide/http/problemdetails.md index fd21f9050..4fd4dc623 100644 --- a/docs/guide/http/problemdetails.md +++ b/docs/guide/http/problemdetails.md @@ -7,7 +7,7 @@ off, explicit validation for certain endpoints. Consider this contrived sample endpoint with explicit validation being done in a "Before" middleware method: - + ```cs public class ProblemDetailsUsageEndpoint { @@ -35,7 +35,7 @@ public class ProblemDetailsUsageEndpoint public record NumberMessage(int Number); ``` -snippet source | anchor +snippet source | anchor Wolverine.Http now (as of 1.2.0) has a convention that sees a return value of `ProblemDetails` and looks at that as a diff --git a/docs/guide/http/querystring.md b/docs/guide/http/querystring.md index 4a29f1f36..14a3d509e 100644 --- a/docs/guide/http/querystring.md +++ b/docs/guide/http/querystring.md @@ -81,7 +81,7 @@ it would be convenient to have Wolverine just let you declare a single .NET type will be filled with any of the matching query string parameters like this sample: - + ```cs // If you want every value to be optional, use public, settable // properties and a no-arg public constructor @@ -120,7 +120,7 @@ public static class QueryOrdersEndpoint } } ``` -snippet source | anchor +snippet source | anchor Because we've used the `[FromQuery]` attribute on a parameter argument that's not a simple type, Wolverine is trying to bind diff --git a/docs/guide/http/security.md b/docs/guide/http/security.md index c76b79a3d..efd7ae65f 100644 --- a/docs/guide/http/security.md +++ b/docs/guide/http/security.md @@ -16,7 +16,7 @@ app.MapWolverineEndpoints(opts => or more selectively, the code above is just syntactical sugar for: - + ```cs /// /// Equivalent of calling RequireAuthorization() on all wolverine endpoints @@ -26,5 +26,5 @@ public void RequireAuthorizeOnAll() ConfigureEndpoints(e => e.RequireAuthorization()); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/transport.md b/docs/guide/http/transport.md new file mode 100644 index 000000000..5f7d3e250 --- /dev/null +++ b/docs/guide/http/transport.md @@ -0,0 +1,37 @@ +# HTTP Messaging Transport + +On top of everything else that Wolverine does, the `WolverineFx.HTTP` Nuget also contains the ability to use HTTP as a +messaging transport for Wolverine messaging. Assuming you have that library attached to your AspNetCore project, add +this declaration to your `WebApplication` in your `Program.Main()` method: + + + +```cs +app.MapWolverineHttpTransportEndpoints(); +``` +snippet source | anchor + + +The declaration above is actually using Minimal API rather than native Wolverine.HTTP endpoints, but that's perfectly fine +in this case. That declaration also enables you to use Minimal API's Fluent Interface to customize the authorization +rules against the HTTP endpoints for Wolverine messaging. + +To establish publishing rules in your application to a remote endpoint in another system, use this syntax: + + + +```cs +using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.PublishAllMessages() + .ToHttpEndpoint("https://binary.com/api"); + }) + .StartAsync(); +``` +snippet source | anchor + + +::: tip +This functionality if very new, and you may want to reach out through Discord for any questions. +::: \ No newline at end of file diff --git a/docs/guide/http/validation.md b/docs/guide/http/validation.md index daf74da38..3604530cb 100644 --- a/docs/guide/http/validation.md +++ b/docs/guide/http/validation.md @@ -28,7 +28,7 @@ off, explicit validation for certain endpoints. Consider this contrived sample endpoint with explicit validation being done in a "Before" middleware method: - + ```cs public class ProblemDetailsUsageEndpoint { @@ -56,7 +56,7 @@ public class ProblemDetailsUsageEndpoint public record NumberMessage(int Number); ``` -snippet source | anchor +snippet source | anchor Wolverine.Http now (as of 1.2.0) has a convention that sees a return value of `ProblemDetails` and looks at that as a @@ -256,7 +256,7 @@ Any validation errors detected will cause the HTTP request to fail with a `Probl For an example, consider this input model that will be a request type in your application: - + ```cs public record CreateAccount( // don't forget the property prefix on records @@ -273,14 +273,14 @@ public record CreateAccount( } } ``` -snippet source | anchor +snippet source | anchor As long as the Data Annotations middleware is active, the `CreateAccount` model would be validated if used as the request body like this: - + ```cs [WolverinePost("/validate/account")] public static string Post( @@ -292,7 +292,7 @@ public static string Post( return "Got a new account"; } ``` -snippet source | anchor +snippet source | anchor or even like this: @@ -360,12 +360,12 @@ app.MapWolverineEndpoints(opts => // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); - + // Or instead, you could use Data Annotations that are built // into the Wolverine.HTTP library opts.UseDataAnnotationsValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor ## AsParameters Binding @@ -374,7 +374,7 @@ The Fluent Validation middleware can also be used against the `[AsParameters]` i of an HTTP endpoint: - + ```cs public static class ValidatedAsParametersEndpoint { @@ -401,7 +401,7 @@ public class ValidatedQuery } } ``` -snippet source | anchor +snippet source | anchor ## QueryString Binding @@ -409,7 +409,7 @@ public class ValidatedQuery Wolverine.HTTP can apply the Fluent Validation middleware to complex types that are bound by the `[FromQuery]` behavior: - + ```cs public record CreateCustomer ( @@ -444,5 +444,5 @@ public static class CreateCustomerEndpoint } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/logging.md b/docs/guide/logging.md index 92f489f9f..f689635e1 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -39,7 +39,7 @@ you don't want logging for that particular message type, but do for all other me level for only that specific message type like so: - + ```cs public class CustomizedHandler { @@ -60,7 +60,7 @@ public class CustomizedHandler } } ``` -snippet source | anchor +snippet source | anchor Methods on message handler types with the signature: @@ -78,7 +78,7 @@ Wolverine's node agent controller performs health checks periodically (every 10 You can control this tracing behavior through the `DurabilitySettings`: - + ```cs public record QuietMessage; @@ -114,7 +114,7 @@ public class QuietAndVerboseMessageHandler } } ``` -snippet source | anchor +snippet source | anchor ## Controlling Message Specific Logging and Tracing @@ -126,7 +126,7 @@ way to do that is to use the `[WolverineLogging]` attribute on either the handle below: - + ```cs public record QuietMessage; @@ -162,7 +162,7 @@ public class QuietAndVerboseMessageHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messages.md b/docs/guide/messages.md index e5a83e2a2..5e4b41fce 100644 --- a/docs/guide/messages.md +++ b/docs/guide/messages.md @@ -19,7 +19,7 @@ to Newtonsoft.JSON or to use higher performance [MemoryPack](/guide/messages.htm Let's say that you have a basic message structure like this: - + ```cs public class PersonBorn { @@ -33,7 +33,7 @@ public class PersonBorn public int Year { get; set; } } ``` -snippet source | anchor +snippet source | anchor By default, Wolverine will identify this type by just using the .NET full name like so: @@ -114,12 +114,12 @@ The marker types shown above may be helpful in transitioning an existing codebas You can optionally use an attribute to mark a type as a message: - + ```cs [WolverineMessage] public record CloseIssue(Guid Id); ``` -snippet source | anchor +snippet source | anchor Or lastly, make up your own criteria to find and mark message types within your system as shown below: @@ -143,7 +143,7 @@ Going back to the original `PersonBorn` message class in previous sections, let' create a new version of that message that is no longer structurally equivalent to the original message: - + ```cs [MessageIdentity("person-born", Version = 2)] public class PersonBornV2 @@ -153,7 +153,7 @@ public class PersonBornV2 public DateTime Birthday { get; set; } } ``` -snippet source | anchor +snippet source | anchor The `[MessageIdentity("person-born", Version = 2)]` attribute usage tells Wolverine that this class is "Version 2" for the `message-type` = "person-born." @@ -176,14 +176,14 @@ And to instead opt into using System.Text.Json with different defaults -- which increased risk of serialization failures -- use this syntax where `opts` is a `WolverineOptions` object: - + ```cs opts.UseSystemTextJsonForSerialization(stj => { stj.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; }); ``` -snippet source | anchor +snippet source | anchor When using Newtonsoft.Json, the default configuration is: @@ -203,7 +203,7 @@ return new JsonSerializerSettings To customize the Newtonsoft.Json serialization, use this option: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -214,7 +214,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ### MessagePack Serialization @@ -342,7 +342,7 @@ using var host = await Host.CreateDefaultBuilder() If you make breaking changes to an incoming message in a later version, you can simply handle both versions of that message separately: - + ```cs public class PersonCreatedHandler { @@ -357,14 +357,14 @@ public class PersonCreatedHandler } } ``` -snippet source | anchor +snippet source | anchor Or you could use a custom `IMessageDeserializer` to read incoming messages from V1 into the new V2 message type, or you can take advantage of message forwarding so you only need to handle one message type using the `IForwardsTo` interface as shown below: - + ```cs public class PersonBorn : IForwardsTo { @@ -385,13 +385,13 @@ public class PersonBorn : IForwardsTo } } ``` -snippet source | anchor +snippet source | anchor Which forwards to the current message type: - + ```cs [MessageIdentity("person-born", Version = 2)] public class PersonBornV2 @@ -401,7 +401,7 @@ public class PersonBornV2 public DateTime Birthday { get; set; } } ``` -snippet source | anchor +snippet source | anchor Using this strategy, other systems could still send your system the original `application/vnd.person-born.v1+json` formatted diff --git a/docs/guide/messaging/listeners.md b/docs/guide/messaging/listeners.md index d5eb9ac3c..a3f530f18 100644 --- a/docs/guide/messaging/listeners.md +++ b/docs/guide/messaging/listeners.md @@ -139,7 +139,7 @@ In the case where you need messages from a single endpoint to be processed in st you have the `ListenWithStrictOrdering()` option: - + ```cs var host = await Host.CreateDefaultBuilder().UseWolverine(opts => { @@ -153,7 +153,7 @@ var host = await Host.CreateDefaultBuilder().UseWolverine(opts => .ListenWithStrictOrdering(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor This option does a couple things: @@ -167,7 +167,24 @@ This option does a couple things: In some cases, you may want to disable all message processing for messages received from external transports like Rabbit MQ or AWS SQS. To do that, simply set: -snippet: sample_disable_all_listeners + + +```cs +.UseWolverine(opts => +{ + // This will disable all message listening to + // external message brokers + opts.DisableAllExternalListeners = true; + + opts.DisableConventionalDiscovery(); + + // This could never, ever work + opts.UseRabbitMq().AutoProvision(); + opts.ListenToRabbitQueue("incoming"); +}).StartAsync(); +``` +snippet source | anchor + The original use case for this flag was a command line tool that needed to publish messages to a system through Rabbit MQ then exit. Having that process also trying to publish messages received diff --git a/docs/guide/messaging/message-bus.md b/docs/guide/messaging/message-bus.md index 3ba56d8ca..3bf9f59cb 100644 --- a/docs/guide/messaging/message-bus.md +++ b/docs/guide/messaging/message-bus.md @@ -170,7 +170,7 @@ You can explicitly override this behavior on a handler by handler basis with the as shown below: - + ```cs public class CreateItemCommandHandler { @@ -191,7 +191,26 @@ public class CreateItemCommandHandler } } ``` -snippet source | anchor +snippet source | anchor + + +## Global Timeout Default for Request/Reply + +The default timeout for all remote invocations, request/reply, or send and wait messaging is 5 seconds. You can override that +on a case by case basis, or you can set a default global timeout value: + + + +```cs +using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // Set a global default timeout for remote + // invocation or request/reply operations + opts.DefaultRemoteInvocationTimeout = 10.Seconds(); + }).StartAsync(); +``` +snippet source | anchor ## Disabling Remote Request/Reply @@ -308,7 +327,7 @@ actually have a couple different syntactical options. First, if you're directly can schedule a message with a delay using this extension method: - + ```cs public async Task schedule_send(IMessageContext context, Guid issueId) { @@ -327,13 +346,13 @@ public async Task schedule_send(IMessageContext context, Guid issueId) }); } ``` -snippet source | anchor +snippet source | anchor Or using an absolute time, with this overload of the extension method: - + ```cs public async Task schedule_send_at_5_tomorrow_afternoon(IMessageContext context, Guid issueId) { @@ -350,7 +369,7 @@ public async Task schedule_send_at_5_tomorrow_afternoon(IMessageContext context, await context.ScheduleAsync(timeout, time); } ``` -snippet source | anchor +snippet source | anchor Now, Wolverine tries really hard to enable you to use [pure functions](https://en.wikipedia.org/wiki/Pure_function) for as many message handlers as possible, so @@ -383,13 +402,13 @@ Lastly, there's a special base class called `TimeoutMessage` that your message t directly to the message itself for easy usage as a cascaded message. Here's an example message type: - + ```cs // This message will always be scheduled to be delivered after // a one minute delay public record OrderTimeout(string Id) : TimeoutMessage(1.Minutes()); ``` -snippet source | anchor +snippet source | anchor Which is used within this sample saga implementation: @@ -416,7 +435,7 @@ public static (Order, OrderTimeout) Start(StartOrder order, ILogger logge TODO -- more text here. NEW PAGE??? - + ```cs public static async Task SendMessagesWithDeliveryOptions(IMessageBus bus) { @@ -434,7 +453,7 @@ public static async Task SendMessagesWithDeliveryOptions(IMessageBus bus) .WithHeader("tenant", "one")); } ``` -snippet source | anchor +snippet source | anchor ## Sending Raw Message Data diff --git a/docs/guide/messaging/partitioning.md b/docs/guide/messaging/partitioning.md index 4c6334de0..764d02c00 100644 --- a/docs/guide/messaging/partitioning.md +++ b/docs/guide/messaging/partitioning.md @@ -89,7 +89,7 @@ At runtime, when you publish an `IOrderCommand` within the system, Wolverine wil queues to send the message, and the easiest way to explain this is really just to show the internal code: - + ```cs /// /// Uses a combination of message grouping id rules and a deterministic hash @@ -114,7 +114,7 @@ public static int SlotForSending(this Envelope envelope, int numberOfSlots, Mess return Math.Abs(groupId.GetDeterministicHashCode() % numberOfSlots); } ``` -snippet source | anchor +snippet source | anchor The code above manages publishing between the "orders1", "orders2", "orders3", and "orders4" queues. Inside of each of the @@ -177,7 +177,7 @@ The built in rules *at this point* include: Internally, Wolverine is using a list of implementations of this interface: - + ```cs /// /// Strategy for determining the GroupId of a message @@ -187,7 +187,7 @@ public interface IGroupingRule bool TryFindIdentity(Envelope envelope, out string groupId); } ``` -snippet source | anchor +snippet source | anchor Definitely note that these rules are fall through, and the order you declare the rules diff --git a/docs/guide/messaging/subscriptions.md b/docs/guide/messaging/subscriptions.md index bb8b97377..3986b4484 100644 --- a/docs/guide/messaging/subscriptions.md +++ b/docs/guide/messaging/subscriptions.md @@ -44,7 +44,7 @@ of the Wolverine configuration. Programmatically, this code shows how to "look" into the configured Wolverine subscriptions for a message type: - + ```cs public static void PreviewRouting(IHost host) { @@ -72,7 +72,7 @@ public static void PreviewRouting(IHost host) } } ``` -snippet source | anchor +snippet source | anchor First, you can always use the [command line support](/guide/command-line) to preview Wolverine's known message types by using: @@ -95,7 +95,7 @@ preview functionality by "telling" Wolverine what your outgoing message types ar To route messages to specific endpoints, we can apply static message routing rules by using a routing rule as shown below: - + ```cs using var host = Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -144,7 +144,7 @@ using var host = Host.CreateDefaultBuilder() opts.PublishAllMessages().ToPort(3333); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Do note that doing the message type filtering by namespace will also include child namespaces. In @@ -215,7 +215,7 @@ for a message type that "knows" how to create the Wolverine `Envelope` for a sin outgoing message to a single subscribing endpoint: - + ```cs /// /// Contains all the rules for where and how an outgoing message @@ -229,7 +229,7 @@ public interface IMessageRoute MessageSubscriptionDescriptor Describe(); } ``` -snippet source | anchor +snippet source | anchor This type "knows" about any endpoint or model sending customizations like delivery expiration @@ -293,7 +293,7 @@ Let's say you want to use a completely different conventional routing topology t of the box. You can do that by creating your own implementation of this interface: - + ```cs /// /// Plugin for doing any kind of conventional message routing @@ -316,7 +316,7 @@ public interface IMessageRoutingConvention IEnumerable DiscoverSenders(Type messageType, IWolverineRuntime runtime); } ``` -snippet source | anchor +snippet source | anchor As a concrete example, the Wolverine team received [this request](https://github.com/JasperFx/wolverine/issues/1130) to conventionally route messages based on @@ -325,7 +325,7 @@ That's not something that Wolverine supports out of the box, but you could build convention like this: - + ```cs public class RouteKeyConvention : IMessageRoutingConvention { @@ -359,13 +359,13 @@ public class RouteKeyConvention : IMessageRoutingConvention } } ``` -snippet source | anchor +snippet source | anchor And register it to your Wolverine application like so: - + ```cs var builder = Host.CreateApplicationBuilder(); var rabbitConnectionString = builder @@ -386,7 +386,7 @@ builder.UseWolverine(opts => // actually start the app... ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/azureservicebus/index.md b/docs/guide/messaging/transports/azureservicebus/index.md index f55fe60c2..1fc5316ce 100644 --- a/docs/guide/messaging/transports/azureservicebus/index.md +++ b/docs/guide/messaging/transports/azureservicebus/index.md @@ -117,7 +117,7 @@ var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToAzureServiceBusQueue("send_and_receive"); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Connecting To Multiple Namespaces diff --git a/docs/guide/messaging/transports/azureservicebus/session-identifiers.md b/docs/guide/messaging/transports/azureservicebus/session-identifiers.md index b7d723e7d..cfb7f39f4 100644 --- a/docs/guide/messaging/transports/azureservicebus/session-identifiers.md +++ b/docs/guide/messaging/transports/azureservicebus/session-identifiers.md @@ -43,10 +43,22 @@ _host = await Host.CreateDefaultBuilder() // Require sessions on this subscription .RequireSessions(1) + .ProcessInline(); + + opts.PublishMessage().ToAzureServiceBusTopic("asb4").BufferedInMemory(); + opts.ListenToAzureServiceBusSubscription("asb4") + .FromTopic("asb4", cfg => + { + cfg.EnablePartitioning = true; + }) + + // Require sessions on this subscription + .RequireSessions(1) + .ProcessInline(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor To publish messages to Azure Service Bus with a session id, you will need to of course supply the session id: @@ -59,7 +71,7 @@ await bus.SendAsync(new AsbMessage3("Red"), new DeliveryOptions { GroupId = "2" await bus.SendAsync(new AsbMessage3("Green"), new DeliveryOptions { GroupId = "2" }); await bus.SendAsync(new AsbMessage3("Refactor"), new DeliveryOptions { GroupId = "2" }); ``` -snippet source | anchor +snippet source | anchor ::: info diff --git a/docs/guide/messaging/transports/gcp-pubsub/conventional-routing.md b/docs/guide/messaging/transports/gcp-pubsub/conventional-routing.md index 227903c83..389de55a7 100644 --- a/docs/guide/messaging/transports/gcp-pubsub/conventional-routing.md +++ b/docs/guide/messaging/transports/gcp-pubsub/conventional-routing.md @@ -44,5 +44,5 @@ var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/gcp-pubsub/deadlettering.md b/docs/guide/messaging/transports/gcp-pubsub/deadlettering.md index c5105df0d..63c1561e2 100644 --- a/docs/guide/messaging/transports/gcp-pubsub/deadlettering.md +++ b/docs/guide/messaging/transports/gcp-pubsub/deadlettering.md @@ -25,7 +25,7 @@ var host = await Host.CreateDefaultBuilder() ); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor When enabled, Wolverine will try to move dead letter messages in GCP Pub/Sub to a single, global topic named "wlvrn.dead-letter". @@ -56,5 +56,5 @@ var host = await Host.CreateDefaultBuilder() ); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/gcp-pubsub/interoperability.md b/docs/guide/messaging/transports/gcp-pubsub/interoperability.md index 5f432b7c9..289c1fc19 100644 --- a/docs/guide/messaging/transports/gcp-pubsub/interoperability.md +++ b/docs/guide/messaging/transports/gcp-pubsub/interoperability.md @@ -54,7 +54,7 @@ public class CustomPubsubMapper : EnvelopeMapper, } } ``` -snippet source | anchor +snippet source | anchor To apply that mapper to specific endpoints, use this syntax on any type of GCP Pub/Sub endpoint: @@ -71,5 +71,5 @@ using var host = await Host.CreateDefaultBuilder() .ConfigureSenders(s => s.UseInterop((e, _) => new CustomPubsubMapper(e))); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/gcp-pubsub/listening.md b/docs/guide/messaging/transports/gcp-pubsub/listening.md index d86c585cf..75f3b9a22 100644 --- a/docs/guide/messaging/transports/gcp-pubsub/listening.md +++ b/docs/guide/messaging/transports/gcp-pubsub/listening.md @@ -12,6 +12,12 @@ var host = await Host.CreateDefaultBuilder() opts.ListenToPubsubTopic("incoming1"); + // Listen to an existing subscription + opts.ListenToPubsubSubscription("subscription1", x => + { + // Other configuration... + }); + opts.ListenToPubsubTopic("incoming2") // You can optimize the throughput by running multiple listeners @@ -34,5 +40,5 @@ var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/gcp-pubsub/publishing.md b/docs/guide/messaging/transports/gcp-pubsub/publishing.md index 909f61fc5..dc502bee9 100644 --- a/docs/guide/messaging/transports/gcp-pubsub/publishing.md +++ b/docs/guide/messaging/transports/gcp-pubsub/publishing.md @@ -24,5 +24,5 @@ var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/kafka.md b/docs/guide/messaging/transports/kafka.md index 0d94f3f67..7bfc0450d 100644 --- a/docs/guide/messaging/transports/kafka.md +++ b/docs/guide/messaging/transports/kafka.md @@ -211,7 +211,7 @@ When receiving messages through Kafka and Wolverine, there are some useful eleme on the Wolverine `Envelope` you can use for instrumentation or diagnostics as shown in this sample middleware: - + ```cs public static class KafkaInstrumentation { @@ -224,7 +224,7 @@ public static class KafkaInstrumentation } } ``` -snippet source | anchor +snippet source | anchor ## Connecting to Multiple Brokers diff --git a/docs/guide/messaging/transports/local.md b/docs/guide/messaging/transports/local.md index 1c1579661..117af8bf6 100644 --- a/docs/guide/messaging/transports/local.md +++ b/docs/guide/messaging/transports/local.md @@ -32,7 +32,7 @@ Some things to know about the local queues: If you want to enqueue a message locally to a specific worker queue, you can use this syntax: - + ```cs public ValueTask EnqueueToQueue(IMessageContext bus) { @@ -49,7 +49,7 @@ public ValueTask EnqueueToQueue(IMessageContext bus) return bus.EndpointFor("highpriority").SendAsync(@event); } ``` -snippet source | anchor +snippet source | anchor ## Scheduling Local Execution @@ -101,7 +101,7 @@ public class ImportanceMessage; Otherwise, you can take advantage of Wolverine's message routing rules like this: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -112,7 +112,7 @@ using var host = await Host.CreateDefaultBuilder() .ToLocalQueue("important"); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor The routing rules and/or `[LocalQueue]` routing is also honored for cascading messages, meaning that any message that is handled inside a Wolverine system could publish cascading messages to the local worker queues. @@ -220,7 +220,7 @@ the Wolverine naming conventions. To get back to leaning more on the type system interface that can be implemented on any handler type to configure the local queue where that handler would run: - + ```cs /// /// Helps mark a handler to configure the local queue that its messages @@ -232,7 +232,7 @@ public interface IConfigureLocalQueue static abstract void Configure(LocalQueueConfiguration configuration); } ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -243,7 +243,7 @@ handler type itself cannot be static. Just a .NET quirk. To use this, just implement that interface on any message handler type: - + ```cs public class MultipleMessage1Handler : IConfigureLocalQueue { @@ -260,7 +260,7 @@ public class MultipleMessage1Handler : IConfigureLocalQueue } } ``` -snippet source | anchor +snippet source | anchor ## Durable Local Messages @@ -270,7 +270,7 @@ The local worker queues can optionally be designated as "durable," meaning that Here is an example of configuring a local queue to be durable: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -283,7 +283,7 @@ using var host = await Host.CreateDefaultBuilder() .UseDurableInbox(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -295,7 +295,7 @@ See [Durable Inbox and Outbox Messaging](/guide/durability/) for more informatio The queues are built on top of the TPL Dataflow library, so it's pretty easy to configure parallelization (how many concurrent messages could be handled by a queue). Here's an example of how to establish this: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -321,7 +321,7 @@ using var host = await Host.CreateDefaultBuilder() opts.LocalQueue("four").UseDurableInbox(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -335,7 +335,7 @@ locally enqueued messages or scheduled messages that may have initially failed. In the sample Wolverine configuration shown below: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -346,7 +346,7 @@ using var host = await Host.CreateDefaultBuilder() .ToLocalQueue("important"); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Calling `IMessageBus.SendAsync(new Message2())` would publish the message to the local "important" queue. diff --git a/docs/guide/messaging/transports/mqtt.md b/docs/guide/messaging/transports/mqtt.md index 223156588..5a011506a 100644 --- a/docs/guide/messaging/transports/mqtt.md +++ b/docs/guide/messaging/transports/mqtt.md @@ -127,12 +127,21 @@ would be derived from either Wolverine's [message type name](/guide/messages.htm or by using the `[Topic("topic name")]` attribute as shown below: - + ```cs [Topic("one")] public class TopicMessage1; ``` -snippet source | anchor +snippet source | anchor + +```cs +[Topic("color.blue")] +public class FirstMessage +{ + public Guid Id { get; set; } = Guid.NewGuid(); +} +``` +snippet source | anchor ## Publishing by Topic Rules @@ -254,7 +263,7 @@ For more complex interoperability, you can implement the `IMqttEnvelopeMapper` i incoming and outgoing MQTT messages and the Wolverine `Envelope` structure. Here's an example: - + ```cs public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper { @@ -280,7 +289,7 @@ public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor And apply that to an MQTT topic like so: @@ -360,7 +369,7 @@ interface to map from Wolverine's `Envelope` structure and MQTT's `MqttApplicati Here's a simple example: - + ```cs public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper { @@ -386,7 +395,7 @@ public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor You will need to apply that mapper to each endpoint like so: diff --git a/docs/guide/messaging/transports/rabbitmq/index.md b/docs/guide/messaging/transports/rabbitmq/index.md index a903ae808..77b5a5546 100644 --- a/docs/guide/messaging/transports/rabbitmq/index.md +++ b/docs/guide/messaging/transports/rabbitmq/index.md @@ -201,5 +201,30 @@ using var host = await Host.CreateDefaultBuilder() Of course, doing so means that you will not be able to do request/reply through Rabbit MQ with your Wolverine application. +## Configuring Channel Creation +You now have the ability to fine tune how the [Rabbit MQ channels](https://www.rabbitmq.com/docs/channels~~~~) are created by Wolverine through +this syntax: + + + +```cs +var builder = Host.CreateApplicationBuilder(); +builder.UseWolverine(opts => +{ + opts + .UseRabbitMq(builder.Configuration.GetConnectionString("rabbitmq")) + + // Fine tune how the underlying Rabbit MQ channels from + // this application will behave + .ConfigureChannelCreation(o => + { + o.PublisherConfirmationsEnabled = true; + o.PublisherConfirmationTrackingEnabled = true; + o.ConsumerDispatchConcurrency = 5; + }); +}); +``` +snippet source | anchor + diff --git a/docs/guide/messaging/transports/rabbitmq/object-management.md b/docs/guide/messaging/transports/rabbitmq/object-management.md index d463d80cd..9516a48da 100644 --- a/docs/guide/messaging/transports/rabbitmq/object-management.md +++ b/docs/guide/messaging/transports/rabbitmq/object-management.md @@ -178,7 +178,7 @@ are largely not impacted otherwise. Here are your options for configuring one or many queues as opting into being a "Quorum Queue" or a "Stream": - + ```cs var builder = Host.CreateApplicationBuilder(); builder.UseWolverine(opts => @@ -205,7 +205,7 @@ builder.UseWolverine(opts => }); ``` -snippet source | anchor +snippet source | anchor There are just a few things to know: @@ -220,7 +220,7 @@ you can quickly access and make additions to the Rabbit MQ integration with your like so: - + ```cs public class MyModuleExtension : IWolverineExtension { @@ -235,6 +235,6 @@ public class MyModuleExtension : IWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/topics.md b/docs/guide/messaging/transports/rabbitmq/topics.md index f26b0168b..8fa6f0e1b 100644 --- a/docs/guide/messaging/transports/rabbitmq/topics.md +++ b/docs/guide/messaging/transports/rabbitmq/topics.md @@ -48,13 +48,19 @@ on a message type like so: ```cs +[Topic("one")] +public class TopicMessage1; +``` +snippet source | anchor + +```cs [Topic("color.blue")] public class FirstMessage { public Guid Id { get; set; } = Guid.NewGuid(); } ``` -snippet source | anchor +snippet source | anchor Of course, you can always explicitly send a message to a specific topic with this syntax: diff --git a/docs/guide/messaging/transports/redis.md b/docs/guide/messaging/transports/redis.md index adab8da76..29e5b86ae 100644 --- a/docs/guide/messaging/transports/redis.md +++ b/docs/guide/messaging/transports/redis.md @@ -151,7 +151,7 @@ Next, the Redis transport supports interoperability through the `IRedisEnvelopeM can build your own version of this mapper interface like the following: - + ```cs // Simplistic envelope mapper that expects every message to be of // type "T" and serialized as JSON that works perfectly well w/ our @@ -213,6 +213,50 @@ public class OurRedisJsonMapper : EnvelopeMappersnippet source | anchor +snippet source | anchor +## Scheduled Messaging + +The Redis transport supports native Redis message scheduling for delayed or scheduled delivery. There's no configuration +necessary to utilize that. + +## Dead Letter Queue Messages + +For `Buffered` or `Inline` endpoints, you can use native Redis streams for "dead letter queue" messages using +the name "{StreamKey}:dead-letter": + + + +```cs +var builder = Host.CreateDefaultBuilder(); + +using var host = await builder.UseWolverine(opts => +{ + opts.UseRedisTransport("localhost:6379").AutoProvision() + .SystemQueuesEnabled(false) // Disable reply queues + .DeleteStreamEntryOnAck(true); // Clean up stream entries on ack + + // Sending inline so the messages are added to the stream right away + opts.PublishAllMessages().ToRedisStream("wolverine-messages") + .SendInline(); + + opts.ListenToRedisStream("wolverine-messages", "default") + .EnableNativeDeadLetterQueue() // Enable DLQ for failed messages + .UseDurableInbox(); // Use durable inbox so retry messages are persisted + + // schedule retry delays + // if durable, these will be scheduled natively in Redis + opts.OnException() + .ScheduleRetry( + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(20), + TimeSpan.FromSeconds(30)); + + opts.Services.AddResourceSetupOnStartup(); +}).StartAsync(); +``` +snippet source | anchor + + + diff --git a/docs/guide/messaging/transports/signalr.md b/docs/guide/messaging/transports/signalr.md index b83f80a10..50e32befc 100644 --- a/docs/guide/messaging/transports/signalr.md +++ b/docs/guide/messaging/transports/signalr.md @@ -113,13 +113,13 @@ For the message routing above, you'll notice that I utilized a marker interface like this: - + ```cs // Marker interface for the sample application just to facilitate // message routing public interface WolverineChatWebSocketMessage : WebSocketMessage; ``` -snippet source | anchor +snippet source | anchor The Wolverine `WebSocketMessage` marker interface does have a little bit of impact in that: diff --git a/docs/guide/messaging/transports/tcp.md b/docs/guide/messaging/transports/tcp.md index 425c16ec9..a457bc0cf 100644 --- a/docs/guide/messaging/transports/tcp.md +++ b/docs/guide/messaging/transports/tcp.md @@ -13,7 +13,7 @@ You can listen to messages from as many ports as you like, but be aware of port To listen for messages with the TCP transport, use the `ListenAtPort()` extension method shown below: - + ```cs public static IHost CreateHostBuilder() { @@ -41,7 +41,7 @@ public static IHost CreateHostBuilder() return builder.Build(); } ``` -snippet source | anchor +snippet source | anchor Likewise, to publish via TCP, use the `ToPort()` extension method to publish to another port on the same @@ -79,7 +79,7 @@ var answer = bus.EndpointFor("One") or use `ToServerAndPort()` to send messages to a port on another machine: - + ```cs using var host = Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -128,7 +128,7 @@ using var host = Host.CreateDefaultBuilder() opts.PublishAllMessages().ToPort(3333); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/unknown.md b/docs/guide/messaging/unknown.md index 806025714..a3c5b45b1 100644 --- a/docs/guide/messaging/unknown.md +++ b/docs/guide/messaging/unknown.md @@ -43,7 +43,7 @@ You can direct Wolverine to take custom actions on messages received with unknow a custom implementation of this interface: - + ```cs namespace Wolverine; @@ -62,13 +62,13 @@ public interface IMissingHandler ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root); } ``` -snippet source | anchor +snippet source | anchor Here's a made up sample that theoretically posts a message to a Slack room by sending a Wolverine message in response: - + ```cs public class MyCustomActionForMissingHandlers : IMissingHandler { @@ -80,7 +80,7 @@ public class MyCustomActionForMissingHandlers : IMissingHandler } } ``` -snippet source | anchor +snippet source | anchor And simply registering that with your application's IoC container against the `IMissingHandler` interface like this: diff --git a/docs/guide/migration.md b/docs/guide/migration.md index efc2a3d3d..48ed4bbdc 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -162,7 +162,7 @@ var builder = WebApplication.CreateBuilder(args); // will assert this is missing on startup:( builder.Services.AddWolverineHttp(); ``` -snippet source | anchor +snippet source | anchor Also for Wolverine.Http users, the `[Document]` attribute behavior in the Marten integration is now "required by default." @@ -182,7 +182,7 @@ You can selectively override this behavior and tell Wolverine to publish the res by using the new 3.0 `[AlwaysPublishResponse]` attribute like this: - + ```cs public class CreateItemCommandHandler { @@ -203,5 +203,5 @@ public class CreateItemCommandHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/runtime.md b/docs/guide/runtime.md index 2412df65a..04df42fc7 100644 --- a/docs/guide/runtime.md +++ b/docs/guide/runtime.md @@ -8,7 +8,7 @@ everything is just a message. The two key parts of a Wolverine application are messages: - + ```cs // A "command" message public record DebitAccount(long AccountId, decimal Amount); @@ -16,13 +16,13 @@ public record DebitAccount(long AccountId, decimal Amount); // An "event" message public record AccountOverdrawn(long AccountId); ``` -snippet source | anchor +snippet source | anchor And the message handling code for the messages, which in Wolverine's case just means a function or method that accepts the message type as its first argument like so: - + ```cs public static class DebitAccountHandler { @@ -32,7 +32,7 @@ public static class DebitAccountHandler } } ``` -snippet source | anchor +snippet source | anchor ## Invoking a Message Inline @@ -239,7 +239,7 @@ The stateful, running "agents" are exposed through an `IAgent` interface like so: - + ```cs /// /// Models a constantly running background process within a Wolverine @@ -258,8 +258,8 @@ public interface IAgent : IHostedService // Standard .NET interface for backgrou AgentStatus Status { get; } } ``` -snippet source | anchor - +snippet source | anchor + ```cs /// /// Models a constantly running background process within a Wolverine @@ -312,13 +312,13 @@ public class CompositeAgent : IAgent public AgentStatus Status { get; private set; } = AgentStatus.Stopped; } ``` -snippet source | anchor +snippet source | anchor With related groups of agents built and assigned by IoC-registered implementations of this interface: - + ```cs /// /// Pluggable model for managing the assignment and execution of stateful, "sticky" @@ -360,7 +360,7 @@ public interface IAgentFamily ValueTask EvaluateAssignmentsAsync(AssignmentGrid assignments); } ``` -snippet source | anchor +snippet source | anchor Built in examples of the agent and agent family are: diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 3f6df0773..e8b6e3783 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -412,7 +412,7 @@ For an example, let's look at this message handler for applying a debit to a ban will use [cascading messages](/guide/handlers/cascading) to raise a variable number of additional messages: - + ```cs [Transactional] public static IEnumerable Handle( @@ -444,7 +444,7 @@ public static IEnumerable Handle( yield return new AccountUpdated(account.Id, account.Balance); } ``` -snippet source | anchor +snippet source | anchor The testing extensions can be seen in action by the following test: @@ -516,7 +516,7 @@ Here's a different version of the message handler from the previous section, but directly: - + ```cs [Transactional] public static async Task Handle( @@ -551,7 +551,7 @@ public static async Task Handle( new DeliveryOptions { DeliverWithin = 5.Seconds() }); } ``` -snippet source | anchor +snippet source | anchor To test this handler, we can use `TestMessageContext` as a stand in to just record @@ -799,7 +799,7 @@ bootstrap your Wolverine application in an integration testing harness, you can // This is bootstrapping the actual application using // its implied Program.Main() set up // For non-Alba users, this is using IWebHostBuilder -Host = await AlbaHost.For(x => +Host = await AlbaHost.For(x => { x.ConfigureServices(services => { @@ -814,7 +814,7 @@ Host = await AlbaHost.For(x => }); }); ``` -snippet source | anchor +snippet source | anchor ## Stubbing Message Handlers diff --git a/docs/introduction/from-mediatr.md b/docs/introduction/from-mediatr.md index a58c85d70..a5f0d3b48 100644 --- a/docs/introduction/from-mediatr.md +++ b/docs/introduction/from-mediatr.md @@ -159,7 +159,7 @@ avoid the really nasty kind of Exception stack traces you get from many other mi Let's say that you have a Wolverine.HTTP endpoint like so: - + ```cs public record CreateCustomer ( @@ -194,7 +194,7 @@ public static class CreateCustomerEndpoint } } ``` -snippet source | anchor +snippet source | anchor In the application bootstrapping, I've added this option: diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index ffef15fe5..4ab717369 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -33,17 +33,17 @@ public record CreateIssue(Guid OriginatorId, string Title, string Description); - + ```cs public record AssignIssue(Guid IssueId, Guid AssigneeId); ``` -snippet source | anchor +snippet source | anchor Let's jump right into the `Program.cs` file of our new web service: - + ```cs using JasperFx; using Quickstart; @@ -85,7 +85,7 @@ app.MapGet("/", () => Results.Redirect("/swagger")); // your Wolverine application return await app.RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -107,7 +107,7 @@ inline. In a simplistic form, here is the entire handler file for the `CreateIss command: - + ```cs namespace Quickstart; @@ -141,7 +141,7 @@ public class CreateIssueHandler } } ``` -snippet source | anchor +snippet source | anchor Hopefully that code is simple enough, but let's talk what you do not see in this code or @@ -168,7 +168,7 @@ the initial web service call. The `IssueCreated` event message will be handled by this code: - + ```cs public static class IssueCreatedHandler { @@ -192,7 +192,7 @@ public static class IssueCreatedHandler } } ``` -snippet source | anchor +snippet source | anchor Now, you'll notice that Wolverine is happy to allow you to use static methods as diff --git a/docs/tutorials/concurrency.md b/docs/tutorials/concurrency.md index 16fce68d9..a3486fb9e 100644 --- a/docs/tutorials/concurrency.md +++ b/docs/tutorials/concurrency.md @@ -52,7 +52,7 @@ In that case, there's absolutely no value in retrying the message, so we should move that message off immediately like one of these: - + ```cs public static class MarkItemReadyHandler { @@ -85,7 +85,7 @@ public static class MarkItemReadyHandler } } ``` -snippet source | anchor +snippet source | anchor ## Exclusive Locks or Serializable Transactions diff --git a/docs/tutorials/cqrs-with-marten.md b/docs/tutorials/cqrs-with-marten.md index 0430862c2..f6311784d 100644 --- a/docs/tutorials/cqrs-with-marten.md +++ b/docs/tutorials/cqrs-with-marten.md @@ -98,7 +98,7 @@ and will probably evolve through subsequent user stories. We're starting from an so we're going to skip ahead to some of our initial event types: - + ```cs public class Incident { @@ -132,7 +132,7 @@ public class Incident public bool ShouldDelete(Archived @event) => true; } ``` -snippet source | anchor +snippet source | anchor ::: info @@ -185,7 +185,7 @@ our code, so here's the first cut at the HTTP endpoint that will log a new incid for the incident in one file: - + ```cs public record LogIncident( Guid CustomerId, @@ -210,7 +210,7 @@ public static class LogIncidentEndpoint } } ``` -snippet source | anchor +snippet source | anchor And maybe there's a few details to unpack. It might help to [see the code](/guide/codegen) that Wolverine generates for this HTTP @@ -341,20 +341,20 @@ overrides so that you are mostly using the application **as it is actually confi As a little tip, I've added this bit of marker code to the very bottom of our `Program` file: - + ```cs // Adding this just makes it easier to bootstrap your // application in a test harness project. Only a convenience public partial class Program{} ``` -snippet source | anchor +snippet source | anchor Having that above, I'll switch to the test harness project and create a shared fixture to bootstrap the `IHost` for the application throughout the integration tests: - + ```cs public class AppFixture : IAsyncLifetime { @@ -392,14 +392,14 @@ public class AppFixture : IAsyncLifetime } } ``` -snippet source | anchor +snippet source | anchor And I like to add a base class for integration tests with some convenience methods that have been useful here and there: - + ```cs [CollectionDefinition("integration")] public class IntegrationCollection : ICollectionFixture; @@ -465,7 +465,7 @@ public abstract class IntegrationContext : IAsyncLifetime } } ``` -snippet source | anchor +snippet source | anchor With all of that in place (and if you're using Docker for your infrastructure, a quick `docker compose up -d` command), @@ -522,7 +522,7 @@ allows you to express most command handlers that target Marten event sourcing as On to the code: - + ```cs public record CategoriseIncident( IncidentCategory Category, @@ -563,7 +563,7 @@ public static class CategoriseIncidentEndpoint } } ``` -snippet source | anchor +snippet source | anchor In this case, I'm sourcing the `Incident` value using the `incidentId` route argument as diff --git a/docs/tutorials/idempotency.md b/docs/tutorials/idempotency.md index a042ffab4..027d547f3 100644 --- a/docs/tutorials/idempotency.md +++ b/docs/tutorials/idempotency.md @@ -113,7 +113,7 @@ public static void Handle(DoSomething msg) } ``` -snippet source | anchor +snippet source | anchor Or you can use an overload of the auto apply transactions policy: @@ -155,7 +155,30 @@ But of course, you may have message handlers that don't need to touch your under handler might do nothing but call an external web service. You may want to make this message handler be idempotent to protect against duplicated calls to that web service. You're in luck, because Wolverine exposes this policy to do exactly that: -snippet: sample_using_AutoApplyIdempotencyOnNonTransactionalHandlers + + +```cs +using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.Durability.Mode = DurabilityMode.Solo; + + opts.Services.AddDbContextWithWolverineIntegration(x => + x.UseSqlServer(Servers.SqlServerConnectionString)); + + opts.Services.AddResourceSetupOnStartup(StartupAction.ResetState); + + opts.Policies.AutoApplyTransactions(IdempotencyStyle.Eager); + + opts.PersistMessagesWithSqlServer(Servers.SqlServerConnectionString, "idempotency"); + opts.UseEntityFrameworkCoreTransactions(); + + // THIS RIGHT HERE + opts.Policies.AutoApplyIdempotencyOnNonTransactionalHandlers(); + }).StartAsync(); +``` +snippet source | anchor + Specifically, see the call to `WolverineOptions.Policies.AutoApplyIdempotencyOnNonTransactionalHandlers()` above. What that is doing is: @@ -184,7 +207,7 @@ system, so Wolverine has a background process to delete messages marked as `Hand with the setting shown below: - + ```cs using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => @@ -195,7 +218,7 @@ using var host = await Host.CreateDefaultBuilder() opts.Durability.KeepAfterMessageHandling = 10.Minutes(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor The default is to keep messages for at least 5 minutes. diff --git a/docs/tutorials/interop.md b/docs/tutorials/interop.md index 59994f81e..b1bd729b3 100644 --- a/docs/tutorials/interop.md +++ b/docs/tutorials/interop.md @@ -109,7 +109,7 @@ In this first sample, I'm going to write a simplistic mapper for Kafka that assu endpoint is JSON and a specific type: - + ```cs // Simplistic envelope mapper that expects every message to be of // type "T" and serialized as JSON that works perfectly well w/ our @@ -145,7 +145,7 @@ public class OurKafkaJsonMapper : IKafkaEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor Which is essentially how the built in "Raw JSON" mapper works in external transport mappers. In the envelope mapper above @@ -200,7 +200,7 @@ a custom mapper from scratch. The NServiceBus interoperability for everything bu approach: - + ```cs public void UseNServiceBusInterop() { @@ -238,13 +238,13 @@ public void UseNServiceBusInterop() }); } ``` -snippet source | anchor +snippet source | anchor Finally, here's another example that works quite differently where the mapper sets a serializer directly on the `Envelope`: - + ```cs // This guy is the envelope mapper for interoperating // with MassTransit @@ -282,7 +282,7 @@ internal class MassTransitMapper : ISqsEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor In the case above, the `MassTransitSerializer` is a two step process that first deserializes a JSON document that contains diff --git a/docs/tutorials/leader-election.md b/docs/tutorials/leader-election.md index 8b69a9e99..0d2a8f054 100644 --- a/docs/tutorials/leader-election.md +++ b/docs/tutorials/leader-election.md @@ -190,7 +190,7 @@ For testing, you also have this helper: // This is bootstrapping the actual application using // its implied Program.Main() set up // For non-Alba users, this is using IWebHostBuilder -Host = await AlbaHost.For(x => +Host = await AlbaHost.For(x => { x.ConfigureServices(services => { @@ -205,7 +205,7 @@ Host = await AlbaHost.For(x => }); }); ``` -snippet source | anchor +snippet source | anchor Likewise, any other `DurabilityMode` setting than `Balanced` (the default) will @@ -217,7 +217,7 @@ To write your own family of "sticky" agents and use Wolverine to distribute them you'll first need to make implementations of this interface: - + ```cs /// /// Models a constantly running background process within a Wolverine @@ -236,8 +236,8 @@ public interface IAgent : IHostedService // Standard .NET interface for backgrou AgentStatus Status { get; } } ``` -snippet source | anchor - +snippet source | anchor + ```cs /// /// Models a constantly running background process within a Wolverine @@ -290,7 +290,7 @@ public class CompositeAgent : IAgent public AgentStatus Status { get; private set; } = AgentStatus.Stopped; } ``` -snippet source | anchor +snippet source | anchor Note that you could use [BackgroundService](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-9.0&tabs=visual-studio) as a base class. @@ -301,7 +301,7 @@ unique identifier to track where and whether the known agents are executing. The next service is the actual distributor. To plug into Wolverine, you need to build an implementation of this service: - + ```cs /// /// Pluggable model for managing the assignment and execution of stateful, "sticky" @@ -343,7 +343,7 @@ public interface IAgentFamily ValueTask EvaluateAssignmentsAsync(AssignmentGrid assignments); } ``` -snippet source | anchor +snippet source | anchor In this case, you can plug custom `IAgentFamily` strategies into Wolverine by just registering a concrete service in diff --git a/docs/tutorials/mediator.md b/docs/tutorials/mediator.md index 422ed81bf..75df0194a 100644 --- a/docs/tutorials/mediator.md +++ b/docs/tutorials/mediator.md @@ -68,7 +68,7 @@ From there, we'll slightly modify the `Program` file generated by the `webapi` t into Wolverine's [extended command line support](/guide/command-line): - + ```cs var builder = WebApplication.CreateBuilder(args); @@ -99,7 +99,7 @@ builder.Services.AddDbContext( // use this DbContext type optionsLifetime: ServiceLifetime.Singleton); ``` -snippet source | anchor +snippet source | anchor Now, let's add a Wolverine message handler that will: @@ -111,7 +111,7 @@ Now, let's add a Wolverine message handler that will: Using idiomatic Wolverine, that handler looks like this: - + ```cs public class ItemHandler { @@ -146,7 +146,7 @@ public class ItemHandler } } ``` -snippet source | anchor +snippet source | anchor **Note**, as long as this handler class is public and in the main application assembly, Wolverine is going @@ -156,11 +156,11 @@ to find it and wire it up inside its execution pipeline. There's no explicit cod Now, moving up to the API layer, we can add a new HTTP endpoint to delegate to Wolverine as a mediator with: - + ```cs app.MapPost("/items/create", (CreateItemCommand cmd, IMessageBus bus) => bus.InvokeAsync(cmd)); ``` -snippet source | anchor +snippet source | anchor There isn't much to this code -- and that's the entire point! When Wolverine registers itself into @@ -185,7 +185,7 @@ As a contrast, here's what the same functionality looks like if you write all th explicitly in a controller action: - + ```cs // This controller does all the transactional work and business // logic all by itself @@ -227,7 +227,7 @@ public class DoItAllMyselfItemController : ControllerBase } } ``` -snippet source | anchor +snippet source | anchor So one, there's just more going on in the `/items/create` HTTP endpoint defined above because you're needing to do a little bit of @@ -247,11 +247,11 @@ output of the message handler to the HTTP response body (and assume we'll use JS example code simpler): - + ```cs app.MapPost("/items/create2", (CreateItemCommand cmd, IMessageBus bus) => bus.InvokeAsync(cmd)); ``` -snippet source | anchor +snippet source | anchor Using the `IMessageBus.Invoke(message)` overload, the returned `ItemCreated` response diff --git a/docs/tutorials/middleware.md b/docs/tutorials/middleware.md index 7f5c459e0..2a8291ee3 100644 --- a/docs/tutorials/middleware.md +++ b/docs/tutorials/middleware.md @@ -32,31 +32,31 @@ Using Wolverine's [conventional middleware approach](/guide/handlers/middleware. command message types that reference an `Account` like so: - + ```cs public interface IAccountCommand { Guid AccountId { get; } } ``` -snippet source | anchor +snippet source | anchor So a command message might look like this: - + ```cs public record CreditAccount(Guid AccountId, decimal Amount) : IAccountCommand; ``` -snippet source | anchor +snippet source | anchor Skipping ahead a little bit, if we had a handler for the `CreditAccount` command type above that was counting on some kind of middleware to just "push" the matching `Account` data in, the handler might just be this: - + ```cs public static class CreditAccountHandler { @@ -78,7 +78,7 @@ public static class CreditAccountHandler } } ``` -snippet source | anchor +snippet source | anchor You'll notice at this point that the message handler is synchronous because it's no longer doing any calls to the database. Besides removing some repetitive code, this appproach @@ -88,7 +88,7 @@ Next, let's build the actual middleware that will attempt to load an `Account` m or be aborted. Here's sample code to do exactly that: - + ```cs // This is *a* way to build middleware in Wolverine by basically just // writing functions/methods. There's a naming convention that @@ -121,7 +121,7 @@ public static class AccountLookupMiddleware } } ``` -snippet source | anchor +snippet source | anchor Now, some notes about the code above: diff --git a/docs/tutorials/ping-pong.md b/docs/tutorials/ping-pong.md index bef413367..302ce3435 100644 --- a/docs/tutorials/ping-pong.md +++ b/docs/tutorials/ping-pong.md @@ -7,7 +7,7 @@ To show off some of the messaging, let's just build [a very simple "Ping/Pong" e First off, I'm going to build out a very small shared library just to hold the messages we're going to exchange: - + ```cs public class Ping { @@ -19,13 +19,13 @@ public class Pong public int Number { get; set; } } ``` -snippet source | anchor +snippet source | anchor And next, I'll start a small *Pinger* service with the `dotnet new worker` template. There's just three pieces of code, starting with the boostrapping code: - + ```cs using Messages; using JasperFx; @@ -50,13 +50,13 @@ return await Host.CreateDefaultBuilder(args) }) .RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor and the `Worker` class that's just going to publish a new `Ping` message once a second: - + ```cs using Messages; using Wolverine; @@ -90,13 +90,13 @@ public class Worker : BackgroundService } } ``` -snippet source | anchor +snippet source | anchor and lastly a message handler for any `Pong` messages coming back from the `Ponger` we'll build next: - + ```cs using Messages; @@ -110,14 +110,14 @@ public class PongHandler } } ``` -snippet source | anchor +snippet source | anchor Okay then, next let's move on to building the `Ponger` application. This time I'll use `dotnet new console` to start the new project, then add references to our *Messages* library and Wolverine itself. For the bootstrapping, add this code: - + ```cs using Microsoft.Extensions.Hosting; using JasperFx; @@ -134,14 +134,14 @@ return await Host.CreateDefaultBuilder(args) }) .RunJasperFxCommands(args); ``` -snippet source | anchor +snippet source | anchor And a message handler for the `Ping` messages that will turn right around and shoot a `Pong` response right back to the original sender: - + ```cs using Messages; using Microsoft.Extensions.Logging; @@ -158,8 +158,8 @@ public class PingHandler } } ``` -snippet source | anchor - +snippet source | anchor + ```cs public static class PingHandler { @@ -188,7 +188,7 @@ public static class PingHandler } } ``` -snippet source | anchor +snippet source | anchor If I start up first the *Ponger* service, then the *Pinger* service, I'll see console output like this from *Pinger*: diff --git a/docs/tutorials/railway-programming.md b/docs/tutorials/railway-programming.md index c77cc7b9a..ac4c92f03 100644 --- a/docs/tutorials/railway-programming.md +++ b/docs/tutorials/railway-programming.md @@ -75,7 +75,7 @@ You have more specialized ways of doing that in HTTP endpoints by using the `Pro processing like this example that uses a `Validate()` method to potentially stop processing with a descriptive 400 and error message: - + ```cs public record CategoriseIncident( IncidentCategory Category, @@ -116,7 +116,7 @@ public static class CategoriseIncidentEndpoint } } ``` -snippet source | anchor +snippet source | anchor The value `WolverineContinue.NoProblems` tells Wolverine that everything is good, full speed ahead. Anything else will write the `ProblemDetails` diff --git a/src/Http/Wolverine.Http.Tests/Transport/HttpTransportConfigurationTests.cs b/src/Http/Wolverine.Http.Tests/Transport/HttpTransportConfigurationTests.cs index 3661cae7a..fd512b029 100644 --- a/src/Http/Wolverine.Http.Tests/Transport/HttpTransportConfigurationTests.cs +++ b/src/Http/Wolverine.Http.Tests/Transport/HttpTransportConfigurationTests.cs @@ -97,6 +97,8 @@ public async Task to_http_endpoint_with_cloud_events_sets_serializer_options() [Fact] public async Task to_http_endpoint_without_cloud_events_keeps_default_options() { + #region sample_publishing_rules_for_http + using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { @@ -105,6 +107,8 @@ public async Task to_http_endpoint_without_cloud_events_keeps_default_options() }) .StartAsync(); + #endregion + var runtime = host.GetRuntime(); var transport = runtime.Options.Transports.GetOrCreate(); diff --git a/src/Http/WolverineWebApi/Program.cs b/src/Http/WolverineWebApi/Program.cs index 7ecc4b2c8..588124579 100644 --- a/src/Http/WolverineWebApi/Program.cs +++ b/src/Http/WolverineWebApi/Program.cs @@ -284,9 +284,13 @@ public static async Task Main(string[] args) #endregion }); - + + #region sample_MapWolverineHttpTransportEndpoints + app.MapWolverineHttpTransportEndpoints(); + #endregion + #region sample_optimized_mediator_usage // Functional equivalent to MapPost(pattern, (command, IMessageBus) => bus.Invoke(command)) diff --git a/src/Samples/DocumentationSamples/GlobalTimeoutRequestReply.cs b/src/Samples/DocumentationSamples/GlobalTimeoutRequestReply.cs new file mode 100644 index 000000000..369c78e07 --- /dev/null +++ b/src/Samples/DocumentationSamples/GlobalTimeoutRequestReply.cs @@ -0,0 +1,23 @@ +using JasperFx.Core; +using Microsoft.Extensions.Hosting; +using Wolverine; + +namespace DocumentationSamples; + +public class GlobalTimeoutRequestReply +{ + public static async Task global_timeout() + { + #region sample_global_timeout_for_remote_invocation + + using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // Set a global default timeout for remote + // invocation or request/reply operations + opts.DefaultRemoteInvocationTimeout = 10.Seconds(); + }).StartAsync(); + + #endregion + } +} \ No newline at end of file diff --git a/src/Samples/DocumentationSamples/InboxOutboxSettings.cs b/src/Samples/DocumentationSamples/InboxOutboxSettings.cs new file mode 100644 index 000000000..6c046ff1b --- /dev/null +++ b/src/Samples/DocumentationSamples/InboxOutboxSettings.cs @@ -0,0 +1,31 @@ +using JasperFx.Core; +using Microsoft.Extensions.Hosting; +using Wolverine; + +namespace DocumentationSamples; + +public class InboxOutboxSettings +{ + public async Task configure() + { + #region sample_using_inbox_outbox_stale_time + + using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // configure the actual message persistence... + + // This directs Wolverine to "bump" any messages marked + // as being owned by a specific node but older than + // these thresholds as being open to any node pulling + // them in + + // TL;DR: make Wolverine go grab stale messages and make + // sure they are processed or sent to the messaging brokers + opts.Durability.InboxStaleTime = 5.Minutes(); + opts.Durability.OutboxStaleTime = 5.Minutes(); + }).StartAsync(); + + #endregion + } +} \ No newline at end of file diff --git a/src/Transports/GCP/Wolverine.Pubsub.Tests/DocumentationSamples.cs b/src/Transports/GCP/Wolverine.Pubsub.Tests/DocumentationSamples.cs index 279b2d226..710d574e3 100644 --- a/src/Transports/GCP/Wolverine.Pubsub.Tests/DocumentationSamples.cs +++ b/src/Transports/GCP/Wolverine.Pubsub.Tests/DocumentationSamples.cs @@ -73,6 +73,12 @@ public async Task configuring_listeners() opts.ListenToPubsubTopic("incoming1"); + // Listen to an existing subscription + opts.ListenToPubsubSubscription("subscription1", x => + { + // Other configuration... + }); + opts.ListenToPubsubTopic("incoming2") // You can optimize the throughput by running multiple listeners diff --git a/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/channel_configuration.cs b/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/channel_configuration.cs index 05b9af0c0..072b64634 100644 --- a/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/channel_configuration.cs +++ b/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/channel_configuration.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Shouldly; using Wolverine.RabbitMQ.Internal; using Xunit; @@ -6,6 +8,29 @@ namespace Wolverine.RabbitMQ.Tests; public class channel_configuration { + public static async Task configure_sample() + { + #region sample_configuring_rabbit_mq_channel_creation + + var builder = Host.CreateApplicationBuilder(); + builder.UseWolverine(opts => + { + opts + .UseRabbitMq(builder.Configuration.GetConnectionString("rabbitmq")) + + // Fine tune how the underlying Rabbit MQ channels from + // this application will behave + .ConfigureChannelCreation(o => + { + o.PublisherConfirmationsEnabled = true; + o.PublisherConfirmationTrackingEnabled = true; + o.ConsumerDispatchConcurrency = 5; + }); + }); + + #endregion + } + [Fact] public void can_customize_channel_creation() { diff --git a/src/Transports/Redis/Wolverine.Redis.Tests/Samples/RedisTransportWithScheduling.cs b/src/Transports/Redis/Wolverine.Redis.Tests/Samples/RedisTransportWithScheduling.cs index 55161b0b9..939b65b69 100644 --- a/src/Transports/Redis/Wolverine.Redis.Tests/Samples/RedisTransportWithScheduling.cs +++ b/src/Transports/Redis/Wolverine.Redis.Tests/Samples/RedisTransportWithScheduling.cs @@ -4,6 +4,8 @@ using Wolverine.ErrorHandling; using Wolverine.Redis; +#region sample_using_dead_letter_queue_for_redis + var builder = Host.CreateDefaultBuilder(); using var host = await builder.UseWolverine(opts => @@ -30,6 +32,8 @@ opts.Services.AddResourceSetupOnStartup(); }).StartAsync(); + +#endregion var bus = host.MessageBus(); var delay = new Random().Next(10, 50); await bus.ScheduleAsync(