From bef94a59f01ffea6b55544ddccef85b9f6685668 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Mon, 20 May 2024 08:25:18 -0500 Subject: [PATCH] new mechanism to disable automatic Wolverine module discovery --- docs/guide/codegen.md | 8 +-- docs/guide/configuration.md | 2 +- docs/guide/diagnostics.md | 6 +- docs/guide/durability/dead-letter-storage.md | 6 +- docs/guide/durability/efcore.md | 6 +- docs/guide/durability/idempotency.md | 2 +- docs/guide/durability/index.md | 20 +++--- .../leadership-and-troubleshooting.md | 9 ++- .../durability/marten/event-forwarding.md | 12 ++-- .../guide/durability/marten/event-sourcing.md | 10 +-- docs/guide/durability/marten/inbox.md | 9 ++- docs/guide/durability/marten/index.md | 9 ++- docs/guide/durability/marten/multi-tenancy.md | 30 ++++---- docs/guide/durability/marten/operations.md | 4 +- docs/guide/durability/marten/subscriptions.md | 60 ++++++++-------- docs/guide/durability/postgresql.md | 14 ++-- docs/guide/durability/sagas.md | 22 +++--- docs/guide/durability/sqlserver.md | 14 ++-- docs/guide/extensions.md | 51 ++++++++++---- docs/guide/handlers/cascading.md | 30 ++++---- docs/guide/handlers/discovery.md | 10 +-- docs/guide/handlers/error-handling.md | 24 +++---- docs/guide/handlers/index.md | 18 ++--- docs/guide/handlers/middleware.md | 34 ++++----- docs/guide/handlers/multi-tenancy.md | 4 +- docs/guide/handlers/return-values.md | 6 +- docs/guide/handlers/side-effects.md | 6 +- docs/guide/http/endpoints.md | 16 ++--- docs/guide/http/fluentvalidation.md | 6 +- docs/guide/http/headers.md | 9 ++- docs/guide/http/index.md | 24 +++---- docs/guide/http/integration.md | 26 +++---- docs/guide/http/json.md | 2 +- docs/guide/http/marten.md | 33 ++++++--- docs/guide/http/mediator.md | 2 +- docs/guide/http/metadata.md | 2 +- docs/guide/http/middleware.md | 28 ++++---- docs/guide/http/multi-tenancy.md | 63 ++++++++--------- docs/guide/http/policies.md | 14 ++-- docs/guide/http/problemdetails.md | 4 +- docs/guide/http/querystring.md | 4 +- docs/guide/http/routing.md | 18 +++-- docs/guide/http/sagas.md | 18 ++--- docs/guide/logging.md | 24 +++---- docs/guide/messages.md | 10 +-- docs/guide/messaging/broadcast-to-topic.md | 8 +-- docs/guide/messaging/endpoint-operations.md | 4 +- docs/guide/messaging/expiration.md | 14 ++-- docs/guide/messaging/listeners.md | 8 +-- docs/guide/messaging/message-bus.md | 12 ++-- docs/guide/messaging/subscriptions.md | 2 +- .../azureservicebus/conventional-routing.md | 2 +- .../transports/azureservicebus/index.md | 4 +- .../azureservicebus/interoperability.md | 6 +- .../transports/azureservicebus/listening.md | 4 +- .../azureservicebus/object-management.md | 12 ++-- .../transports/azureservicebus/publishing.md | 4 +- .../transports/azureservicebus/scheduled.md | 4 +- .../azureservicebus/session-identifiers.md | 12 ++-- .../transports/azureservicebus/topics.md | 10 +-- docs/guide/messaging/transports/kafka.md | 15 ++-- docs/guide/messaging/transports/local.md | 16 ++--- docs/guide/messaging/transports/mqtt.md | 33 ++++----- .../rabbitmq/conventional-routing.md | 4 +- .../transports/rabbitmq/deadletterqueues.md | 9 +-- .../messaging/transports/rabbitmq/index.md | 12 ++-- .../transports/rabbitmq/interoperability.md | 11 ++- .../transports/rabbitmq/listening.md | 4 +- .../transports/rabbitmq/object-management.md | 11 ++- .../transports/rabbitmq/publishing.md | 6 +- .../messaging/transports/rabbitmq/topics.md | 24 +++---- .../transports/sqs/deadletterqueues.md | 2 +- docs/guide/messaging/transports/sqs/index.md | 8 +-- .../transports/sqs/interoperability.md | 8 +-- .../messaging/transports/sqs/listening.md | 2 +- .../messaging/transports/sqs/publishing.md | 4 +- docs/guide/messaging/transports/tcp.md | 2 +- docs/guide/runtime.md | 6 +- docs/guide/testing.md | 70 +++++++++---------- docs/tutorials/mediator.md | 11 ++- docs/tutorials/middleware.md | 30 ++++---- docs/tutorials/serverless.md | 11 ++- ...rappingTests.cs => bootstrapping_specs.cs} | 29 +++++++- src/Wolverine/HostBuilderExtensions.cs | 16 ++++- src/Wolverine/WolverineOptions.Extensions.cs | 1 + src/Wolverine/WolverineOptions.cs | 5 -- 86 files changed, 622 insertions(+), 563 deletions(-) rename src/Testing/CoreTests/Configuration/{BootstrappingTests.cs => bootstrapping_specs.cs} (83%) diff --git a/docs/guide/codegen.md b/docs/guide/codegen.md index 56b516293..84bc139d2 100644 --- a/docs/guide/codegen.md +++ b/docs/guide/codegen.md @@ -43,8 +43,8 @@ Lastly, you have a couple options about how Wolverine handles the dynamic code g using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { - // The default behavior. Dynamically generate the - // types on the first usage + // The default behavior. Dynamically generate the + // types on the first usage opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Dynamic; // Never generate types at runtime, but instead try to locate @@ -53,7 +53,7 @@ using var host = await Host.CreateDefaultBuilder() // Hybrid approach that first tries to locate the types // from the application assembly, but falls back to - // generating the code and dynamic type. Also writes the + // generating the code and dynamic type. Also writes the // generated source code file to disk opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto; }).StartAsync(); @@ -127,7 +127,7 @@ using var host = await Host.CreateDefaultBuilder() if (context.HostingEnvironment.IsProduction()) { opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Static; - + // You probably only ever want to do this in Production opts.Services.AssertAllExpectedPreBuiltTypesExistOnStartUp(); } diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 09f57ab58..c2787e191 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -119,7 +119,7 @@ return await Host.CreateDefaultBuilder(args) .AddSource("Wolverine"); }); }) - + // Executing with Oakton as the command line parser to unlock // quite a few utilities and diagnostics in our Wolverine application .RunOaktonCommands(args); diff --git a/docs/guide/diagnostics.md b/docs/guide/diagnostics.md index 706d88985..451caf82c 100644 --- a/docs/guide/diagnostics.md +++ b/docs/guide/diagnostics.md @@ -123,12 +123,12 @@ using var host = await Host.CreateDefaultBuilder() { // Surely plenty of other configuration for Wolverine... - // This *temporary* line of code will write out a full report about why or + // This *temporary* line of code will write out a full report about why or // why not Wolverine is finding this handler and its candidate handler messages Console.WriteLine(opts.DescribeHandlerMatch(typeof(MyMissingMessageHandler))); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Asserting Wolverine Configuration @@ -180,6 +180,6 @@ public static void using_preview_subscriptions(IMessageBus bus) } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/durability/dead-letter-storage.md b/docs/guide/durability/dead-letter-storage.md index 59ed772be..a8baee7e7 100644 --- a/docs/guide/durability/dead-letter-storage.md +++ b/docs/guide/durability/dead-letter-storage.md @@ -41,16 +41,16 @@ To integrate the Dead Letters REST API into your WolverineFX application, you si ```cs app.MapDeadLettersEndpoints() - + // It's a Minimal API endpoint group, // so you can add whatever authorization // 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.md b/docs/guide/durability/efcore.md index bfa039382..df7492a48 100644 --- a/docs/guide/durability/efcore.md +++ b/docs/guide/durability/efcore.md @@ -25,7 +25,7 @@ builder.Host.UseWolverine(opts => // Set up Entity Framework Core as the support // for Wolverine's transactional middleware opts.UseEntityFrameworkCoreTransactions(); - + // Enrolling all local queues into the // durable inbox/outbox processing opts.Policies.UseDurableLocalQueues(); @@ -201,7 +201,7 @@ public class SampleMappedDbContext : DbContext } } ``` -snippet source | anchor +snippet source | anchor @@ -274,7 +274,7 @@ public async Task Post3( // Gotta attach the DbContext to the outbox // BEFORE sending any messages outbox.Enroll(dbContext); - + // Publish a message to take action on the new item // in a background thread await outbox.PublishAsync(new ItemCreated diff --git a/docs/guide/durability/idempotency.md b/docs/guide/durability/idempotency.md index dd0712573..264216003 100644 --- a/docs/guide/durability/idempotency.md +++ b/docs/guide/durability/idempotency.md @@ -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 4c1f726bd..7a3e5c0cb 100644 --- a/docs/guide/durability/index.md +++ b/docs/guide/durability/index.md @@ -18,15 +18,15 @@ Consider this sample message handler from Wolverine's [AppWithMiddleware sample ```cs -[Transactional] +[Transactional] public static async Task Handle( - DebitAccount command, - Account account, - IDocumentSession session, + DebitAccount command, + Account account, + IDocumentSession session, IMessageContext messaging) { account.Balance -= command.Amount; - + // This just marks the account as changed, but // doesn't actually commit changes to the database // yet. That actually matters as I hopefully explain @@ -40,12 +40,12 @@ public static async Task Handle( else if (account.Balance < 0) { await messaging.SendAsync(new AccountOverdrawn(account.Id), new DeliveryOptions{DeliverWithin = 1.Hours()}); - + // Give the customer 10 days to deal with the overdrawn account await messaging.ScheduleAsync(new EnforceAccountOverdrawnDeadline(account.Id), 10.Days()); } - - // "messaging" is a Wolverine IMessageContext or IMessageBus service + + // "messaging" is a Wolverine IMessageContext or IMessageBus service // Do the deliver within rule on individual messages await messaging.SendAsync(new AccountUpdated(account.Id, account.Balance), new DeliveryOptions { DeliverWithin = 5.Seconds() }); @@ -153,7 +153,7 @@ using var host = await Host.CreateDefaultBuilder() .UseDurableInbox(); // Make every single listener endpoint use - // durable message storage + // durable message storage opts.Policies.UseDurableInboxOnAllListeners(); }).StartAsync(); ``` @@ -270,7 +270,7 @@ var app = builder.Build(); // the message storage return await app.RunOaktonCommands(args); ``` -snippet source | anchor +snippet source | anchor ## Database Schema Objects diff --git a/docs/guide/durability/leadership-and-troubleshooting.md b/docs/guide/durability/leadership-and-troubleshooting.md index e8c348d46..de3000a19 100644 --- a/docs/guide/durability/leadership-and-troubleshooting.md +++ b/docs/guide/durability/leadership-and-troubleshooting.md @@ -47,23 +47,22 @@ using var host = await Host.CreateDefaultBuilder() { opts.Services.AddMarten("some connection string") - // This adds quite a bit of middleware for + // This adds quite a bit of middleware for // Marten .IntegrateWithWolverine(); - + // You want this maybe! opts.Policies.AutoApplyTransactions(); if (context.HostingEnvironment.IsDevelopment()) { - // But wait! Optimize Wolverine for usage as + // But wait! Optimize Wolverine for usage as // if there would never be more than one node running opts.Durability.Mode = DurabilityMode.Solo; } - }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Running your Wolverine application like this means that Wolverine is able to more quickly start the transactional inbox diff --git a/docs/guide/durability/marten/event-forwarding.md b/docs/guide/durability/marten/event-forwarding.md index 2afa63719..d900f3392 100644 --- a/docs/guide/durability/marten/event-forwarding.md +++ b/docs/guide/durability/marten/event-forwarding.md @@ -96,8 +96,8 @@ builder.Host.UseWolverine(opts => // to the database happen opts.Policies.OnException() .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()); - - // Automatic usage of transactional middleware as + + // Automatic usage of transactional middleware as // Wolverine recognizes that an HTTP endpoint or message handler // persists data opts.Policies.AutoApplyTransactions(); @@ -120,7 +120,7 @@ public static Task SaveInMartenAndWaitForOutgoingMessagesAsync( var session = factory.OpenSession(context); action(session); await session.SaveChangesAsync(); - + // Shouldn't be necessary, but real life says do it anyway await context.As().FlushOutgoingMessagesAsync(); }, timeoutInMilliseconds); @@ -144,7 +144,7 @@ public async Task execution_of_forwarded_events_can_be_awaited_from_tests() services.AddMarten(Servers.PostgresConnectionString) .IntegrateWithWolverine().EventForwardingToWolverine(opts => { - opts.SubscribeToEvent().TransformedTo(e => + opts.SubscribeToEvent().TransformedTo(e => new SecondMessage(e.StreamId, e.Sequence)); }); }).StartAsync(); @@ -154,7 +154,7 @@ public async Task execution_of_forwarded_events_can_be_awaited_from_tests() { session.Events.Append(aggregateId, new SecondEvent()); }, 100_000); - + using var store = host.Services.GetRequiredService(); await using var session = store.LightweightSession(); var events = await session.Events.FetchStreamAsync(aggregateId); @@ -178,5 +178,5 @@ public static Task HandleAsync(SecondMessage message, IDocumentSession session) return session.SaveChangesAsync(); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/durability/marten/event-sourcing.md b/docs/guide/durability/marten/event-sourcing.md index 94b26f028..772251b34 100644 --- a/docs/guide/durability/marten/event-sourcing.md +++ b/docs/guide/durability/marten/event-sourcing.md @@ -99,7 +99,7 @@ public async Task Post( { // This is important! outbox.Enroll(session); - + // Fetch the current value of the Order aggregate var stream = await session .Events @@ -179,7 +179,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 @@ -293,14 +293,14 @@ Here's an alternative to the `MarkItemReady` handler that uses `Events`: [AggregateHandler] public static async Task<(Events, OutgoingMessages)> HandleAsync(MarkItemReady command, Order order, ISomeService service) { - // All contrived, let's say we need to call some + // All contrived, let's say we need to call some // kind of service to get data so this handler has to be // async var data = await service.FindDataAsync(); var messages = new OutgoingMessages(); var events = new Events(); - + if (order.Items.TryGetValue(command.ItemName, out var item)) { // Not doing this in a purist way here, but just @@ -328,7 +328,7 @@ public static async Task<(Events, OutgoingMessages)> HandleAsync(MarkItemReady c return (events, messages); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/durability/marten/inbox.md b/docs/guide/durability/marten/inbox.md index 440aa132e..601de1670 100644 --- a/docs/guide/durability/marten/inbox.md +++ b/docs/guide/durability/marten/inbox.md @@ -22,19 +22,18 @@ builder.Services.AddMarten(opts => builder.Host.UseWolverine(opts => { opts.Policies.OnAnyException().RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()); - + opts.Services.AddScoped(); - + opts.Policies.DisableConventionalLocalRouting(); opts.UseRabbitMq().AutoProvision(); - + opts.Policies.UseDurableInboxOnAllListeners(); opts.Policies.UseDurableOutboxOnAllSendingEndpoints(); opts.ListenToRabbitQueue("chaos2"); opts.PublishAllMessages().ToRabbitQueue("chaos2"); - - + opts.Policies.AutoApplyTransactions(); }); ``` diff --git a/docs/guide/durability/marten/index.md b/docs/guide/durability/marten/index.md index ab996cb86..f8dc68df7 100644 --- a/docs/guide/durability/marten/index.md +++ b/docs/guide/durability/marten/index.md @@ -31,19 +31,18 @@ builder.Services.AddMarten(opts => builder.Host.UseWolverine(opts => { opts.Policies.OnAnyException().RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()); - + opts.Services.AddScoped(); - + opts.Policies.DisableConventionalLocalRouting(); opts.UseRabbitMq().AutoProvision(); - + opts.Policies.UseDurableInboxOnAllListeners(); opts.Policies.UseDurableOutboxOnAllSendingEndpoints(); opts.ListenToRabbitQueue("chaos2"); opts.PublishAllMessages().ToRabbitQueue("chaos2"); - - + opts.Policies.AutoApplyTransactions(); }); ``` diff --git a/docs/guide/durability/marten/multi-tenancy.md b/docs/guide/durability/marten/multi-tenancy.md index 0d6d0ef61..081e6ff47 100644 --- a/docs/guide/durability/marten/multi-tenancy.md +++ b/docs/guide/durability/marten/multi-tenancy.md @@ -38,12 +38,12 @@ builder.Services.AddMarten(m => tenancy.AddSingleTenantDatabase("Host=localhost;Port=5433;Database=tenant2;Username=postgres;password=postgres", "tenant2"); tenancy.AddSingleTenantDatabase("Host=localhost;Port=5433;Database=tenant3;Username=postgres;password=postgres", "tenant3"); }); - + m.DatabaseSchemaName = "mttodo"; }) .IntegrateWithWolverine(masterDatabaseConnectionString:connectionString); ``` -snippet source | anchor +snippet source | anchor And you'll probably want this as well to make sure the message storage is in all the databases upfront: @@ -53,7 +53,7 @@ And you'll probably want this as well to make sure the message storage is in all ```cs builder.Services.AddResourceSetupOnStartup(); ``` -snippet source | anchor +snippet source | anchor Lastly, this is the Wolverine set up: @@ -67,13 +67,13 @@ builder.Host.UseWolverine(opts => // This middleware will apply to the HTTP // endpoints as well opts.Policies.AutoApplyTransactions(); - + // Setting up the outbox on all locally handled // background tasks opts.Policies.UseDurableLocalQueues(); }); ``` -snippet source | anchor +snippet source | anchor From there, you should be completely ready to use Marten + Wolverine with usages like this: @@ -84,8 +84,8 @@ From there, you should be completely ready to use Marten + Wolverine with usages // While this is still valid.... [WolverineDelete("/todoitems/{tenant}/longhand")] public static async Task Delete( - string tenant, - DeleteTodo command, + string tenant, + DeleteTodo command, IMessageBus bus) { // Invoke inline for the specified tenant @@ -105,7 +105,7 @@ public static void Delete( session.Delete(command.Id); } ``` -snippet source | anchor +snippet source | anchor @@ -157,9 +157,9 @@ _host = await Host.CreateDefaultBuilder() opts.Services.AddMarten(Servers.PostgresConnectionString) .IntegrateWithWolverine() .UseLightweightSessions(); - + opts.Policies.AutoApplyTransactions(); - + }).StartAsync(); ``` snippet source | anchor @@ -174,13 +174,13 @@ and after that, the calls to [InvokeForTenantAsync()]() "just work" as you can s public async Task execute_with_tenancy() { var id = Guid.NewGuid(); - + await _host.ExecuteAndWaitAsync(c => c.InvokeForTenantAsync("one", new CreateTenantDocument(id, "Andor"))); - + await _host.ExecuteAndWaitAsync(c => c.InvokeForTenantAsync("two", new CreateTenantDocument(id, "Tear"))); - + await _host.ExecuteAndWaitAsync(c => c.InvokeForTenantAsync("three", new CreateTenantDocument(id, "Illian"))); @@ -192,14 +192,14 @@ public async Task execute_with_tenancy() var document = await session.LoadAsync(id); document.Location.ShouldBe("Andor"); } - + // Check the second tenant using (var session = store.LightweightSession("two")) { var document = await session.LoadAsync(id); document.Location.ShouldBe("Tear"); } - + // Check the third tenant using (var session = store.LightweightSession("three")) { diff --git a/docs/guide/durability/marten/operations.md b/docs/guide/durability/marten/operations.md index d17ac0538..93bb116e0 100644 --- a/docs/guide/durability/marten/operations.md +++ b/docs/guide/durability/marten/operations.md @@ -53,12 +53,12 @@ public static class TodoListEndpoint var listId = CombGuidIdGeneration.NewGuid(); var result = new TodoListCreated(listId, request.Title); var startStream = MartenOps.StartStream(listId, result); - + return (new TodoCreationResponse(listId), startStream); } } ``` -snippet source | anchor +snippet source | anchor The major advantage of using a Marten side effect is to help keep your Wolverine handlers or HTTP endpoints diff --git a/docs/guide/durability/marten/subscriptions.md b/docs/guide/durability/marten/subscriptions.md index 7b336a321..cd78d9379 100644 --- a/docs/guide/durability/marten/subscriptions.md +++ b/docs/guide/durability/marten/subscriptions.md @@ -39,28 +39,28 @@ using var host = await Host.CreateDefaultBuilder() { opts.Services .AddMarten() - - // Just pulling the connection information from + + // Just pulling the connection information from // the IoC container at runtime. .UseNpgsqlDataSource() - + // You don't absolutely have to have the Wolverine // integration active here for subscriptions, but it's // more than likely that you will want this anyway .IntegrateWithWolverine() - + // The Marten async daemon most be active .AddAsyncDaemon(DaemonMode.HotCold) - + // This would attempt to publish every non-archived event // from Marten to Wolverine subscribers .PublishEventsToWolverine("Everything") - + // You wouldn't do this *and* the above option, but just to show // the filtering .PublishEventsToWolverine("Orders", relay => { - // Filtering + // Filtering relay.FilterIncomingEventsOnStreamType(typeof(Order)); // Optionally, tell Marten to only subscribe to new @@ -69,7 +69,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -113,15 +113,14 @@ public static class InternalOrderCreatedHandler public static Task LoadAsync(IEvent e, IQuerySession session, CancellationToken cancellationToken) => session.LoadAsync(e.Data.CustomerId, cancellationToken); - - + public static OrderCreatedIntegrationEvent Handle(IEvent e, Customer customer) { return new OrderCreatedIntegrationEvent(e.Data.OrderNumber, customer.Name, e.Timestamp); } } ``` -snippet source | anchor +snippet source | anchor ## Process Events as Messages in Strict Order @@ -144,7 +143,7 @@ using var host = await Host.CreateDefaultBuilder() o.Projections.Errors.SkipApplyErrors = true; }) - // Just pulling the connection information from + // Just pulling the connection information from // the IoC container at runtime. .UseNpgsqlDataSource() @@ -152,10 +151,10 @@ using var host = await Host.CreateDefaultBuilder() // integration active here for subscriptions, but it's // more than likely that you will want this anyway .IntegrateWithWolverine() - + // The Marten async daemon most be active .AddAsyncDaemon(DaemonMode.HotCold) - + // Notice the allow list filtering of event types and the possibility of overriding // the starting point for this subscription at runtime .ProcessEventsWithWolverineHandlersInStrictOrder("Orders", o => @@ -168,7 +167,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor In this recipe, Marten & Wolverine are working together to call `IMessageBus.InvokeAsync()` on each event in order. You can @@ -218,8 +217,8 @@ public class CompanyActivations public void Add(Guid companyId, string name) { Removals.Remove(companyId); - - // Fill is an extension method in JasperFx.Core that adds the + + // Fill is an extension method in JasperFx.Core that adds the // record to a list if the value does not already exist Additions.Fill(new NewCompany(companyId, name)); } @@ -257,7 +256,7 @@ public class CompanyTransferSubscription : BatchSubscription break; } } - + // At the end of all of this, publish a single message // In case you're wondering, this will opt into Wolverine's // transactional outbox with the same transaction as any changes @@ -267,7 +266,7 @@ public class CompanyTransferSubscription : BatchSubscription } } ``` -snippet source | anchor +snippet source | anchor And the related code to register this subscription: @@ -278,31 +277,30 @@ And the related code to register this subscription: using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { - opts.UseRabbitMq(); - + opts.UseRabbitMq(); + // There needs to be *some* kind of subscriber for CompanyActivations // for this to work at all opts.PublishMessage() .ToRabbitExchange("activations"); - + opts.Services .AddMarten() - // Just pulling the connection information from + // Just pulling the connection information from // the IoC container at runtime. .UseNpgsqlDataSource() - + .IntegrateWithWolverine() - + // The Marten async daemon most be active .AddAsyncDaemon(DaemonMode.HotCold) - // Register the new subscription .SubscribeToEvents(new CompanyTransferSubscription()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Using IoC Services in Subscriptions @@ -316,8 +314,8 @@ and register the projection with this slightly different usage using the `Subscr using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { - opts.UseRabbitMq(); - + opts.UseRabbitMq(); + // There needs to be *some* kind of subscriber for CompanyActivations // for this to work at all opts.PublishMessage() @@ -326,7 +324,7 @@ using var host = await Host.CreateDefaultBuilder() opts.Services .AddMarten() - // Just pulling the connection information from + // Just pulling the connection information from // the IoC container at runtime. .UseNpgsqlDataSource() @@ -342,7 +340,7 @@ using var host = await Host.CreateDefaultBuilder() }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor See the [Marten documentation on subscriptions](/guide/durability/marten/subscriptions.html#using-ioc-services-in-subscriptions) for more information about the lifecycle and mechanics. diff --git a/docs/guide/durability/postgresql.md b/docs/guide/durability/postgresql.md index 9524fc9a1..7f0701463 100644 --- a/docs/guide/durability/postgresql.md +++ b/docs/guide/durability/postgresql.md @@ -44,7 +44,7 @@ var app = builder.Build(); // the message storage return await app.RunOaktonCommands(args); ``` -snippet source | anchor +snippet source | anchor ## PostgreSQL Messaging Transport @@ -65,11 +65,11 @@ using var host = await Host.CreateDefaultBuilder() { var connectionString = context.Configuration.GetConnectionString("postgres"); opts.UsePostgresqlPersistenceAndTransport(connectionString, "myapp") - + // Tell Wolverine to build out all necessary queue or scheduled message // tables on demand as needed .AutoProvision() - + // Optional that may be helpful in testing, but probably bad // in production! .AutoPurgeOnStartup(); @@ -79,14 +79,14 @@ using var host = await Host.CreateDefaultBuilder() // Use this to set up queue listeners opts.ListenToPostgresqlQueue("inbound") - + .CircuitBreaker(cb => { // fine tune the circuit breaker // policies here }) - - // Optionally specify how many messages to + + // Optionally specify how many messages to // fetch into the listener at any one time .MaximumMessagesToReceive(50); }).StartAsync(); @@ -102,7 +102,7 @@ that they are utilizing the transactional inbox and outbox. The PostgreSQL queue ```cs opts.ListenToPostgresqlQueue("sender").BufferedInMemory(); ``` -snippet source | anchor +snippet source | anchor Using this option just means that the PostgreSQL queues can be used for both sending or receiving with no integration diff --git a/docs/guide/durability/sagas.md b/docs/guide/durability/sagas.md index 0b8104e4e..148f23002 100644 --- a/docs/guide/durability/sagas.md +++ b/docs/guide/durability/sagas.md @@ -43,7 +43,7 @@ public class Order : Saga public static (Order, OrderTimeout) Start(StartOrder order, ILogger logger) { logger.LogInformation("Got a new order with id {Id}", order.OrderId); - + // creating a timeout message for the saga return (new Order{Id = order.OrderId}, new OrderTimeout(order.OrderId)); } @@ -253,7 +253,7 @@ public class ToyOnTray [SagaIdentity] public int OrderId { get; set; } } ``` -snippet source | anchor +snippet source | anchor Next, Wolverine looks for a member named "{saga type name}Id." In the case of our `Order` @@ -303,7 +303,7 @@ You can also simply return one or more `Saga` type objects from a handler method public class Reservation : Saga { public string? Id { get; set; } - + // Apply the CompleteReservation to the saga public void Handle(BookReservation book, ILogger logger) { @@ -324,7 +324,7 @@ public class Reservation : Saga } } ``` -snippet source | anchor +snippet source | anchor and the handler that would start the new saga: @@ -336,23 +336,23 @@ public class StartReservationHandler { public static ( // Outgoing message - ReservationBooked, - + ReservationBooked, + // Starts a new Saga - Reservation, - + Reservation, + // Additional message cascading for the new saga ReservationTimeout) Handle(StartReservation start) { return ( - new ReservationBooked(start.ReservationId, DateTimeOffset.UtcNow), - new Reservation { Id = start.ReservationId }, + new ReservationBooked(start.ReservationId, DateTimeOffset.UtcNow), + new Reservation { Id = start.ReservationId }, new ReservationTimeout(start.ReservationId) ); } } ``` -snippet source | anchor +snippet source | anchor ## Method Conventions diff --git a/docs/guide/durability/sqlserver.md b/docs/guide/durability/sqlserver.md index 24a118e1c..bb6862159 100644 --- a/docs/guide/durability/sqlserver.md +++ b/docs/guide/durability/sqlserver.md @@ -24,7 +24,7 @@ builder.Host.UseWolverine(opts => // Set up Entity Framework Core as the support // for Wolverine's transactional middleware opts.UseEntityFrameworkCoreTransactions(); - + // Enrolling all local queues into the // durable inbox/outbox processing opts.Policies.UseDurableLocalQueues(); @@ -52,11 +52,11 @@ using var host = await Host.CreateDefaultBuilder() { var connectionString = context.Configuration.GetConnectionString("sqlserver"); opts.UseSqlServerPersistenceAndTransport(connectionString, "myapp") - + // Tell Wolverine to build out all necessary queue or scheduled message // tables on demand as needed .AutoProvision() - + // Optional that may be helpful in testing, but probably bad // in production! .AutoPurgeOnStartup(); @@ -66,14 +66,14 @@ using var host = await Host.CreateDefaultBuilder() // Use this to set up queue listeners opts.ListenToSqlServerQueue("inbound") - + .CircuitBreaker(cb => { // fine tune the circuit breaker // policies here }) - - // Optionally specify how many messages to + + // Optionally specify how many messages to // fetch into the listener at any one time .MaximumMessagesToReceive(50); }).StartAsync(); @@ -89,7 +89,7 @@ that they are utilizing the transactional inbox and outbox. The Sql Server queue ```cs opts.ListenToSqlServerQueue("sender").BufferedInMemory(); ``` -snippet source | anchor +snippet source | anchor Using this option just means that the Sql Server queues can be used for both sending or receiving with no integration diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index 8e963422e..cf8a2f6d9 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -51,20 +51,20 @@ using var host = await Host.CreateDefaultBuilder() { // Including a single extension opts.Include(); - + // Or add a Wolverine extension that needs // to use IoC services opts.Services.AddWolverineExtension(); }) - + .ConfigureServices(services => { // This is the same logical usage, just showing that it // can be done directly against IServiceCollection services.AddWolverineExtension(); }) - + .StartAsync(); ``` snippet source | anchor @@ -83,11 +83,11 @@ during testing: // to do the actual bootstrapping await using var host = await AlbaHost.For(x => { - // I'm overriding + // I'm overriding x.ConfigureServices(services => services.DisableAllExternalWolverineTransports()); }); ``` -snippet source | anchor +snippet source | anchor Behind the scenes, Wolverine has a small extension like this: @@ -103,7 +103,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: @@ -117,7 +117,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 @@ -141,7 +141,7 @@ public class ConfigurationUsingExtension : IWolverineExtension public void Configure(WolverineOptions options) { - // Configure the wolverine application using + // Configure the wolverine application using // the information from IConfiguration } } @@ -197,7 +197,7 @@ public class SampleAsyncExtension : IAsyncWolverineExtension } } ``` -snippet source | anchor +snippet source | anchor Which can be added to your application with this extension method on `IServiceCollection`: @@ -216,7 +216,7 @@ using var host = await Host.CreateDefaultBuilder() }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ### Asynchronous Extensions and Wolverine.HTTP @@ -236,11 +236,15 @@ 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 +::: warning +This functionality will likely be eliminated in Wolverine 3.0. +::: + ::: tip Use this sparingly, but it might be advantageous for adding extra instrumentation or extra middleware ::: @@ -255,7 +259,30 @@ with this attribute to automatically add that extension to Wolverine: ```cs -[assembly: WolverineModule(typeof(Module1Extension))] +[assembly: WolverineModule] ``` snippet source | anchor + +## Disabling Assembly Scanning + +Some Wolverine users have seen rare issues with the assembly scanning cratering an application with out of memory +exceptions in the case of an application directory being the same as the root of a Docker container. *If* you experience +that issue, or just want a faster start up time, you can disable the automatic extension discovery using this syntax: + + + +```cs +using var host = await Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.DisableConventionalDiscovery(); + }) + + // Note that this isn't part of UseWolverine() + .DisableWolverineExtensionScanning() + + .StartAsync(); +``` +snippet source | anchor + diff --git a/docs/guide/handlers/cascading.md b/docs/guide/handlers/cascading.md index 785364528..c71582c87 100644 --- a/docs/guide/handlers/cascading.md +++ b/docs/guide/handlers/cascading.md @@ -30,7 +30,7 @@ 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_ @@ -47,7 +47,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 @@ -91,7 +91,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. @@ -114,7 +114,7 @@ The usage of this is shown below: public static OutgoingMessages Handle(Incoming incoming) { // You can use collection initializers for OutgoingMessages in C# - // as a shorthand. + // as a shorthand. var messages = new OutgoingMessages { new Message1(), @@ -154,14 +154,14 @@ public static IEnumerable Consume(Incoming incoming) { // Delay the message delivery by 10 minutes yield return new Message1().DelayedFor(10.Minutes()); - + // Schedule the message delivery for a certain time yield return new Message2().ScheduledAt(new DateTimeOffset(DateTime.Today.AddDays(2))); - + // Customize the message delivery however you please... yield return new Message3() .WithDeliveryOptions(new DeliveryOptions().WithHeader("foo", "bar")); - + // Send back to the original sender yield return Respond.ToSender(new Message4()); } @@ -203,7 +203,7 @@ public class Requester } } ``` -snippet source | anchor +snippet source | anchor and inside Receiver we have this code: @@ -219,7 +219,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: @@ -257,7 +257,7 @@ public class ConditionalResponseHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -282,7 +282,7 @@ public class ScheduledResponseHandler } } ``` -snippet source | anchor +snippet source | anchor ## Multiple Cascading Messages @@ -306,7 +306,7 @@ public class MultipleResponseHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -333,7 +333,7 @@ public class MultipleResponseHandler } } ``` -snippet source | anchor +snippet source | anchor can be rewritten with C# 7 tuples to: @@ -351,7 +351,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 @@ -374,6 +374,6 @@ public object Handle(PingMessage message) return Respond.ToSender(pong); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/discovery.md b/docs/guide/handlers/discovery.md index 6781bfdae..e50f9a355 100644 --- a/docs/guide/handlers/discovery.md +++ b/docs/guide/handlers/discovery.md @@ -29,12 +29,12 @@ using var host = await Host.CreateDefaultBuilder() { // Surely plenty of other configuration for Wolverine... - // This *temporary* line of code will write out a full report about why or + // This *temporary* line of code will write out a full report about why or // why not Wolverine is finding this handler and its candidate handler messages Console.WriteLine(opts.DescribeHandlerMatch(typeof(MyMissingMessageHandler))); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Even if the report itself isn't exactly clear to you, using this textual report in a Wolverine issue or @@ -223,7 +223,7 @@ using var host = await Host.CreateDefaultBuilder() opts.DisableConventionalDiscovery(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Explicitly Ignoring Methods @@ -280,7 +280,7 @@ public class BlockbusterHandler } } ``` -snippet source | anchor +snippet source | anchor @@ -320,5 +320,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 c2de2cd51..8c4641cdb 100644 --- a/docs/guide/handlers/error-handling.md +++ b/docs/guide/handlers/error-handling.md @@ -64,7 +64,7 @@ using var host = await Host.CreateDefaultBuilder() opts.OnException().MoveToErrorQueue(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -83,7 +83,7 @@ using var host = await Host.CreateDefaultBuilder() .Discard(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor You have to explicitly discard a message or it will eventually be sent to a dead letter queue when the message has exhausted its configured retries or requeues. @@ -111,7 +111,7 @@ using var host = await Host.CreateDefaultBuilder() .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Or through attributes on a single message: @@ -125,7 +125,7 @@ public class MessageWithBackoff // whatever members } ``` -snippet source | anchor +snippet source | anchor @@ -154,7 +154,7 @@ using var host = await Host.CreateDefaultBuilder() .Requeue().AndPauseProcessing(10.Minutes()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -191,7 +191,7 @@ public class AttributeUsingHandler } } ``` -snippet source | anchor +snippet source | anchor You can also use the fluent interface approach on a specific message type if you put a method with the signature `public static void Configure(HandlerChain chain)` @@ -222,7 +222,7 @@ public class MyErrorCausingHandler } } ``` -snippet source | anchor +snippet source | anchor To specify global error handling rules, use the fluent interface directly on `WolverineOptions.Handlers` as shown below: @@ -243,7 +243,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:) @@ -268,7 +268,7 @@ public class ErrorHandlingPolicy : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor ### Exception Filtering @@ -318,7 +318,7 @@ theReceiver = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Optionally, you can implement a new type to handle this same custom logic by @@ -345,7 +345,7 @@ public class ShippingOrderFailurePolicy : UserDefinedContinuation } } ``` -snippet source | anchor +snippet source | anchor and register that secondary action like this: @@ -363,7 +363,7 @@ theReceiver = await Host.CreateDefaultBuilder() .Discard().And(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/index.md b/docs/guide/handlers/index.md index c17ad06f5..c849a3fbf 100644 --- a/docs/guide/handlers/index.md +++ b/docs/guide/handlers/index.md @@ -21,7 +21,7 @@ public class MyMessageHandler } } ``` -snippet source | anchor +snippet source | anchor If you've used other messaging, command execution, or so called "mediator" tool in .NET, you'll surely notice the absence of any kind of @@ -39,7 +39,7 @@ 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 @@ -174,7 +174,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 @@ -202,7 +202,7 @@ public class ServiceUsingHandler } } ``` -snippet source | anchor +snippet source | anchor ::: tip @@ -229,7 +229,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 @@ -257,7 +257,7 @@ public static class MethodInjectionHandler } } ``` -snippet source | anchor +snippet source | anchor So, what can be injected as an argument to your message handler? @@ -307,14 +307,14 @@ public static class ShipOrderHandler // business logic easy to unit test public static IEnumerable Handle(ShipOrder command, Order order, Customer customer) { - // use the command data, plus the related Order & Customer data to + // use the command data, plus the related Order & Customer data to // "decide" what action to take next yield return new MailOvernight(order.Id); } } ``` -snippet source | anchor +snippet source | anchor @@ -335,7 +335,7 @@ public class EnvelopeUsingHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/middleware.md b/docs/guide/handlers/middleware.md index 647658fd4..b4cc3c0d9 100644 --- a/docs/guide/handlers/middleware.md +++ b/docs/guide/handlers/middleware.md @@ -74,7 +74,7 @@ and that can be added to our application at bootstrapping time like this: using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { - // Apply our new middleware to message handlers, but optionally + // Apply our new middleware to message handlers, but optionally // filter it to only messages from a certain namespace opts.Policies .AddMiddleware(chain => @@ -178,12 +178,12 @@ public static class AccountLookupMiddleware // The message *has* to be first in the parameter list // Before or BeforeAsync tells Wolverine this method should be called before the actual action public static async Task<(HandlerContinuation, Account?)> LoadAsync( - IAccountCommand command, - ILogger logger, - + IAccountCommand command, + ILogger logger, + // This app is using Marten for persistence - IDocumentSession session, - + IDocumentSession session, + CancellationToken cancellation) { var account = await session.LoadAsync(command.AccountId, cancellation); @@ -191,7 +191,7 @@ public static class AccountLookupMiddleware { logger.LogInformation("Unable to find an account for {AccountId}, aborting the requested operation", command.AccountId); } - + return (account == null ? HandlerContinuation.Stop : HandlerContinuation.Continue, account); } } @@ -225,11 +225,11 @@ to any message that implements the `IAccountCommand` interface like this: ```cs builder.Host.UseWolverine(opts => { - // This middleware should be applied to all handlers where the + // This middleware should be applied to all handlers where the // command type implements the IAccountCommand interface that is the // "detected" message type of the middleware opts.Policies.ForMessagesOfType().AddMiddleware(typeof(AccountLookupMiddleware)); - + opts.UseFluentValidation(); // Explicit routing for the AccountUpdated @@ -240,12 +240,12 @@ builder.Host.UseWolverine(opts => // Throw the message away if it's not successfully // delivered within 10 seconds .DeliverWithin(10.Seconds()) - + // Not durable .BufferedInMemory(); }); ``` -snippet source | anchor +snippet source | anchor Wolverine determines the message type for a middleware class method by assuming that the first @@ -332,7 +332,7 @@ public class StopwatchFrame : SyncFrame } } ``` -snippet source | anchor +snippet source | anchor @@ -352,7 +352,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 @@ -372,7 +372,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: @@ -453,7 +453,7 @@ public class WrapWithSimple : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor Then register your custom `IHandlerPolicy` with a Wolverine application like this: @@ -464,7 +464,7 @@ Then register your custom `IHandlerPolicy` with a Wolverine application like thi using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.Policies.Add(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Using Configure(chain) Methods @@ -520,7 +520,7 @@ public class CustomizedHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/multi-tenancy.md b/docs/guide/handlers/multi-tenancy.md index 38dd5b3c5..2d5578920 100644 --- a/docs/guide/handlers/multi-tenancy.md +++ b/docs/guide/handlers/multi-tenancy.md @@ -16,7 +16,7 @@ public static async Task invoking_by_tenant(IMessageBus bus) await bus.InvokeForTenantAsync("tenant2", new CreateTodo("Update the Documentation")); } ``` -snippet source | anchor +snippet source | anchor When using this syntax, any [cascaded messages](/guide/handlers/cascading) will also be tagged with the same tenant id. @@ -35,5 +35,5 @@ public static async Task publish_by_tenant(IMessageBus bus) new DeliveryOptions { TenantId = "tenant3" }); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/handlers/return-values.md b/docs/guide/handlers/return-values.md index 01add1998..f0231cd64 100644 --- a/docs/guide/handlers/return-values.md +++ b/docs/guide/handlers/return-values.md @@ -80,14 +80,14 @@ public class WriteFile : ISideEffect Contents = contents; } - // Wolverine will call this method. + // Wolverine will call this method. public Task ExecuteAsync(PathSettings settings) { if (!Directory.Exists(settings.Directory)) { Directory.CreateDirectory(settings.Directory); } - + return File.WriteAllTextAsync(Path, Contents); } } @@ -110,7 +110,7 @@ internal class WriteFilePolicy : IChainPolicy public void Apply(IReadOnlyList chains, GenerationRules rules, IContainer container) { var method = ReflectionHelper.GetMethod(x => x.WriteAsync()); - + // Check out every message and/or http handler: foreach (var chain in chains) { diff --git a/docs/guide/handlers/side-effects.md b/docs/guide/handlers/side-effects.md index 0da4524ac..e3d620167 100644 --- a/docs/guide/handlers/side-effects.md +++ b/docs/guide/handlers/side-effects.md @@ -45,14 +45,14 @@ public class WriteFile : ISideEffect Contents = contents; } - // Wolverine will call this method. + // Wolverine will call this method. public Task ExecuteAsync(PathSettings settings) { if (!Directory.Exists(settings.Directory)) { Directory.CreateDirectory(settings.Directory); } - + return File.WriteAllTextAsync(Path, Contents); } } @@ -68,7 +68,7 @@ And the matching message type, message handler, and a settings class for configu // An options class public class PathSettings { - public string Directory { get; set; } + public string Directory { get; set; } = Environment.CurrentDirectory.AppendPath("files"); } diff --git a/docs/guide/http/endpoints.md b/docs/guide/http/endpoints.md index 6be280c8f..de4758cf4 100644 --- a/docs/guide/http/endpoints.md +++ b/docs/guide/http/endpoints.md @@ -15,7 +15,7 @@ public static ArithmeticResults PostJson(Question question) }; } ``` -snippet source | anchor +snippet source | anchor In the method signature above, `Question` is the "request" type (the payload sent from the client to the server) and `Answer` is the "resource" type (what is being returned to the client). @@ -36,7 +36,7 @@ public static Task PostJsonAsync(Question question) return Task.FromResult(results); } ``` -snippet source | anchor +snippet source | anchor The resource type is still `Answer`. Likewise, if an endpoint returns `ValueTask`, the resource type @@ -110,7 +110,7 @@ rules above. To make that concrete, consider this sample that we wrote in the in ```cs // Introducing this special type just for the http response // gives us back the 201 status code -public record TodoCreationResponse(int Id) +public record TodoCreationResponse(int Id) : CreationResponse("/todoitems/" + Id); // The "Endpoint" suffix is meaningful, but you could use @@ -122,7 +122,7 @@ public static class TodoCreationEndpoint public static (TodoCreationResponse, TodoCreated) Post(CreateTodo command, IDocumentSession session) { var todo = new Todo { Name = command.Name }; - + // Just telling Marten that there's a new entity to persist, // but I'm assuming that the transactional middleware in Wolverine is // handling the asynchronous persistence outside of this handler @@ -132,13 +132,13 @@ public static class TodoCreationEndpoint // assumed to be the Http response, and any subsequent values are // handled independently return ( - new TodoCreationResponse(todo.Id), + new TodoCreationResponse(todo.Id), new TodoCreated(todo.Id) ); } } ``` -snippet source | anchor +snippet source | anchor In the case above, `TodoCreationResponse` is the first item in the tuple, so Wolverine treats that as @@ -160,7 +160,7 @@ code. Here's an example from the tests: [WolverinePost("/orders/ship"), EmptyResponse] // The OrderShipped return value is treated as an event being posted // to a Marten even stream -// instead of as the HTTP response body because of the presence of +// instead of as the HTTP response body because of the presence of // the [EmptyResponse] attribute public static OrderShipped Ship(ShipOrder command, Order order) { @@ -321,7 +321,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: diff --git a/docs/guide/http/fluentvalidation.md b/docs/guide/http/fluentvalidation.md index 7e6403a8e..a44465885 100644 --- a/docs/guide/http/fluentvalidation.md +++ b/docs/guide/http/fluentvalidation.md @@ -37,12 +37,12 @@ app.MapWolverineEndpoints(opts => // This adds metadata for OpenAPI httpChain.WithMetadata(new CustomMetadata()); }); - + // more configuration for HTTP... - + // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/headers.md b/docs/guide/http/headers.md index 900d6af8e..2dc3c47bc 100644 --- a/docs/guide/http/headers.md +++ b/docs/guide/http/headers.md @@ -17,16 +17,15 @@ public static void Before([FromHeader(Name = "x-day")] string? day) Day = day; // This is for testing } - [WolverineGet("/headers/simple")] public string Get( // Find the request header with the supplied name and pass // it as the "name" parameter to this method at runtime - [FromHeader(Name = "x-wolverine")] + [FromHeader(Name = "x-wolverine")] string name) { return name; -} +} [WolverineGet("/headers/int")] public string Get( @@ -39,7 +38,7 @@ public string Get( ) { return (number * 2).ToString(); -} +} [WolverineGet("/headers/accepts")] // In this case, push the string value for the "accepts" header @@ -49,5 +48,5 @@ public string GetETag([FromHeader] string accepts) return accepts; } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/index.md b/docs/guide/http/index.md index 29ecaed44..f0c069cb3 100644 --- a/docs/guide/http/index.md +++ b/docs/guide/http/index.md @@ -28,17 +28,17 @@ public class CreateTodoHandler public static (Todo, TodoCreated) Handle(CreateTodo command, IDocumentSession session) { var todo = new Todo { Name = command.Name }; - + // Just telling Marten that there's a new entity to persist, // but I'm assuming that the transactional middleware in Wolverine is // handling the asynchronous persistence outside of this handler session.Store(todo); return (todo, new TodoCreated(todo.Id)); - } + } } ``` -snippet source | anchor +snippet source | anchor Okay, but we still need to expose a web service endpoint for this functionality. We *could* utilize Wolverine within an MVC controller @@ -52,17 +52,17 @@ public class TodoController : ControllerBase [HttpPost("/todoitems")] [ProducesResponseType(201, Type = typeof(Todo))] public async Task Post( - [FromBody] CreateTodo command, + [FromBody] CreateTodo command, [FromServices] IMessageBus bus) { // Delegate to Wolverine and capture the response // returned from the handler var todo = await bus.InvokeAsync(command); return Created($"/todoitems/{todo.Id}", todo); - } + } } ``` -snippet source | anchor +snippet source | anchor Or we could do the same thing with Minimal API: @@ -77,7 +77,7 @@ app.MapPost("/todoitems", async (CreateTodo command, IMessageBus bus) => return Results.Created($"/todoitems/{todo.Id}", todo); }).Produces(201); ``` -snippet source | anchor +snippet source | anchor While the code above is certainly functional, and many teams are succeeding today using a similar strategy with older tools like @@ -102,7 +102,7 @@ efficient delegation to the underlying Wolverine message handler: // code of 200 instead of 201. If you care about that anyway. app.MapPostToWolverine("/todoitems"); ``` -snippet source | anchor +snippet source | anchor The code up above is very close to a functional equivalent to our early Minimal API or MVC Controller usage, but there's a @@ -126,7 +126,7 @@ endpoint and use Wolverine to build an HTTP endpoint: ```cs // Introducing this special type just for the http response // gives us back the 201 status code -public record TodoCreationResponse(int Id) +public record TodoCreationResponse(int Id) : CreationResponse("/todoitems/" + Id); // The "Endpoint" suffix is meaningful, but you could use @@ -138,7 +138,7 @@ public static class TodoCreationEndpoint public static (TodoCreationResponse, TodoCreated) Post(CreateTodo command, IDocumentSession session) { var todo = new Todo { Name = command.Name }; - + // Just telling Marten that there's a new entity to persist, // but I'm assuming that the transactional middleware in Wolverine is // handling the asynchronous persistence outside of this handler @@ -148,13 +148,13 @@ public static class TodoCreationEndpoint // assumed to be the Http response, and any subsequent values are // handled independently return ( - new TodoCreationResponse(todo.Id), + new TodoCreationResponse(todo.Id), new TodoCreated(todo.Id) ); } } ``` -snippet source | anchor +snippet source | anchor The code above will actually generate the exact same OpenAPI documentation as the MVC Controller or Minimal API samples diff --git a/docs/guide/http/integration.md b/docs/guide/http/integration.md index 358ac4a20..dc994ae00 100644 --- a/docs/guide/http/integration.md +++ b/docs/guide/http/integration.md @@ -54,7 +54,7 @@ builder.Host.UseWolverine(opts => // This middleware will apply to the HTTP // endpoints as well opts.Policies.AutoApplyTransactions(); - + // Setting up the outbox on all locally handled // background tasks opts.Policies.UseDurableLocalQueues(); @@ -127,7 +127,7 @@ public async Task hello_world() result.ReadAsText().ShouldBe("Hello."); } ``` -snippet source | anchor +snippet source | anchor Moving on to the actual `Todo` problem domain, let's assume we've got a class like this: @@ -142,7 +142,7 @@ public class Todo public bool IsComplete { get; set; } } ``` -snippet source | anchor +snippet source | anchor ```cs public class Todo @@ -152,7 +152,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) @@ -162,10 +162,10 @@ let's add an HTTP service endpoint for listing all the known `Todo` documents: ```cs [WolverineGet("/todoitems")] -public static Task> Get(IQuerySession session) +public static Task> Get(IQuerySession session) => session.Query().ToListAsync(); ``` -snippet source | anchor +snippet source | anchor As you'd guess, this method will serialize all the known `Todo` documents from the database into the HTTP response @@ -181,10 +181,10 @@ Consider this endpoint just to return the data for a single `Todo` document: // Wolverine can infer the 200/404 status codes for you here // so there's no code noise just to satisfy OpenAPI tooling [WolverineGet("/todoitems/{id}")] -public static Task GetTodo(int id, IQuerySession session, CancellationToken cancellation) +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 @@ -207,11 +207,11 @@ public static async Task Create(CreateTodo command, IDocumentSession se // Going to raise an event within out system to be processed later await bus.PublishAsync(new TodoCreated(todo.Id)); - + return Results.Created($"/todoitems/{todo.Id}", todo); } ``` -snippet source | anchor +snippet source | anchor The endpoint code above is automatically enrolled in the Marten transactional middleware by simple virtue of having a @@ -236,8 +236,8 @@ public static class UpdateTodoEndpoint public static async Task<(Todo? todo, IResult result)> LoadAsync(UpdateTodo command, IDocumentSession session) { var todo = await session.LoadAsync(command.Id); - return todo != null - ? (todo, new WolverineContinue()) + return todo != null + ? (todo, new WolverineContinue()) : (todo, Results.NotFound()); } @@ -250,7 +250,7 @@ public static class UpdateTodoEndpoint } } ``` -snippet source | anchor +snippet source | anchor ## How it Works diff --git a/docs/guide/http/json.md b/docs/guide/http/json.md index 51fadb412..6f16a6be7 100644 --- a/docs/guide/http/json.md +++ b/docs/guide/http/json.md @@ -94,7 +94,7 @@ to the `MapWolverineEndpoints()` configuration: ```cs -var builder = WebApplication.CreateBuilder(Array.Empty()); +var builder = WebApplication.CreateBuilder([]); builder.Services.AddScoped(); builder.Services.AddMarten(Servers.PostgresConnectionString) diff --git a/docs/guide/http/marten.md b/docs/guide/http/marten.md index 22af9ce68..30de2e600 100644 --- a/docs/guide/http/marten.md +++ b/docs/guide/http/marten.md @@ -25,11 +25,11 @@ look like this: ```cs { [WolverineGet("/invoices/longhand/id")] - [ProducesResponseType(404)] + [ProducesResponseType(404)] [ProducesResponseType(200, Type = typeof(Invoice))] public static async Task GetInvoice( - Guid id, - IQuerySession session, + Guid id, + IQuerySession session, CancellationToken cancellationToken) { var invoice = await session.LoadAsync(id, cancellationToken); @@ -76,7 +76,18 @@ public static IMartenOp Approve([Document("number")] Invoice invoice) You can also combine the behavior of `[Document]` and `[Required]` through a single attribute like this: -snippet: sample_using_Document_required + + +```cs +[WolverinePost("/api/tenants/{tenant}/counters/{id}/inc2")] +public static IMartenOp Increment2([Document(Required = true)] Counter counter) +{ + counter = counter with { Count = counter.Count + 1 }; + return MartenOps.Store(counter); +} +``` +snippet source | anchor + In the code above, if the `Counter` document does not exist, the route will stop and return a status code 404 for Not Found. @@ -98,13 +109,13 @@ use the new `[Aggregate]` attribute from Wolverine.Http.Marten on endpoint metho [WolverinePost("/orders/{orderId}/ship2"), EmptyResponse] // The OrderShipped return value is treated as an event being posted // to a Marten even stream -// instead of as the HTTP response body because of the presence of +// instead of as the HTTP response body because of the presence of // the [EmptyResponse] attribute public static OrderShipped Ship(ShipOrder2 command, [Aggregate] Order order) { - if (order.HasShipped) + if (order.HasShipped) throw new InvalidOperationException("This has already shipped!"); - + return new OrderShipped(); } ``` @@ -120,7 +131,7 @@ have an endpoint signature like this: [WolverinePost("/orders/{orderId}/ship3"), EmptyResponse] // The OrderShipped return value is treated as an event being posted // to a Marten even stream -// instead of as the HTTP response body because of the presence of +// instead of as the HTTP response body because of the presence of // the [EmptyResponse] attribute public static OrderShipped Ship3([Aggregate] Order order) { @@ -222,7 +233,7 @@ To append a single event to an event stream from an HTTP endpoint, you can use a [WolverinePost("/orders/ship"), EmptyResponse] // The OrderShipped return value is treated as an event being posted // to a Marten even stream -// instead of as the HTTP response body because of the presence of +// instead of as the HTTP response body because of the presence of // the [EmptyResponse] attribute public static OrderShipped Ship(ShipOrder command, Order order) { @@ -242,7 +253,7 @@ Or potentially append multiple events using the `Events` type as a return value public static (OrderStatus, Events) Post(MarkItemReady command, Order order) { var events = new Events(); - + if (order.Items.TryGetValue(command.ItemName, out var item)) { item.Ready = true; @@ -278,7 +289,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 9515f3f28..5b75be7a8 100644 --- a/docs/guide/http/mediator.md +++ b/docs/guide/http/mediator.md @@ -43,7 +43,7 @@ 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/metadata.md b/docs/guide/http/metadata.md index a63fba8f7..413d0b626 100644 --- a/docs/guide/http/metadata.md +++ b/docs/guide/http/metadata.md @@ -18,7 +18,7 @@ all the attributes from ASP.Net Core that you use for MVC controller methods hap ```cs public class SignupEndpoint { - // The first couple attributes are ASP.Net Core + // The first couple attributes are ASP.Net Core // attributes that add OpenAPI metadata to this endpoint [Tags("Users")] [ProducesResponseType(204)] diff --git a/docs/guide/http/middleware.md b/docs/guide/http/middleware.md index 9baf1c0d1..f12d922a8 100644 --- a/docs/guide/http/middleware.md +++ b/docs/guide/http/middleware.md @@ -28,17 +28,17 @@ public class FakeAuthenticationMiddleware { public static IResult Before(IAmAuthenticated message) { - return message.Authenticated + return message.Authenticated // This tells Wolverine to just keep going - ? WolverineContinue.Result() - + ? WolverineContinue.Result() + // If the IResult is not WolverineContinue, Wolverine // will execute the IResult and stop processing otherwise : Results.Unauthorized(); } } ``` -snippet source | anchor +snippet source | anchor Which is registered like this (or as described in [`Registering Middleware by Message Type`](/guide/handlers/middleware.html##registering-middleware-by-message-type)): @@ -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, @@ -66,7 +66,7 @@ public static async Task ExecuteOne(IValidator validator, IProble { // First, validate the incoming request of type T var result = await validator.ValidateAsync(message); - + // If there are any errors, create a ProblemDetails result and return // that to write out the validation errors and otherwise stop processing if (result.Errors.Any()) @@ -109,7 +109,7 @@ public class StopwatchMiddleware } } ``` -snippet source | anchor +snippet source | anchor And you want to apply it to a single HTTP endpoint without having to dirty your hands with an attribute. You can use that naming @@ -131,7 +131,7 @@ from the `HttpContext` to subsequent Wolverine messages published during the req public static class RequestIdMiddleware { public const string CorrelationIdHeaderKey = "X-Correlation-ID"; - + // Remember that most Wolverine middleware can be done with "just" a method public static void Apply(HttpContext httpContext, IMessageContext messaging) { @@ -216,18 +216,18 @@ public record UpdateRequest(string Name, bool IsComplete); public static class UpdateEndpoint { // Find required Todo entity for the route handler below - public static Task LoadAsync(int id, IDocumentSession session) + public static Task LoadAsync(int id, IDocumentSession session) => session.LoadAsync(id); - + [WolverinePut("/todos/{id:int}")] public static StoreDoc Put( // Route argument int id, - + // The request body UpdateRequest request, - - // Entity loaded by the method above, + + // Entity loaded by the method above, // but note the [Required] attribute [Required] Todo? todo) { @@ -238,7 +238,7 @@ public static class UpdateEndpoint } } ``` -snippet source | anchor +snippet source | anchor You'll notice that the `LoadAsync()` method is looking up the `Todo` entity for the route parameter, where Wolverine would diff --git a/docs/guide/http/multi-tenancy.md b/docs/guide/http/multi-tenancy.md index 09ebbd6e4..77f074a03 100644 --- a/docs/guide/http/multi-tenancy.md +++ b/docs/guide/http/multi-tenancy.md @@ -49,21 +49,21 @@ var app = builder.Build(); app.MapWolverineEndpoints(opts => { // The tenancy detection is fall through, so the first strategy - // that finds anything wins! - - // Use the value of a named request header + // that finds anything wins! + + // Use the value of a named request header opts.TenantId.IsRequestHeaderValue("tenant"); - + // Detect the tenant id from an expected claim in the // current request's ClaimsPrincipal opts.TenantId.IsClaimTypeNamed("tenant"); - + // Use a query string value for the key 'tenant' opts.TenantId.IsQueryStringValue("tenant"); - + // Use a named route argument for the tenant id opts.TenantId.IsRouteArgumentNamed("tenant"); - + // Use the *first* sub domain name of the request Url // Note that this is very naive opts.TenantId.IsSubDomainName(); @@ -107,12 +107,12 @@ builder.Services.AddMarten(m => tenancy.AddSingleTenantDatabase("Host=localhost;Port=5433;Database=tenant2;Username=postgres;password=postgres", "tenant2"); tenancy.AddSingleTenantDatabase("Host=localhost;Port=5433;Database=tenant3;Username=postgres;password=postgres", "tenant3"); }); - + m.DatabaseSchemaName = "mttodo"; }) .IntegrateWithWolverine(masterDatabaseConnectionString:connectionString); ``` -snippet source | anchor +snippet source | anchor Then configures Wolverine itself like: @@ -126,13 +126,13 @@ builder.Host.UseWolverine(opts => // This middleware will apply to the HTTP // endpoints as well opts.Policies.AutoApplyTransactions(); - + // Setting up the outbox on all locally handled // background tasks opts.Policies.UseDurableLocalQueues(); }); ``` -snippet source | anchor +snippet source | anchor Lastly, the Wolverine.HTTP setup to add the tenant id detection: @@ -145,14 +145,14 @@ app.MapWolverineEndpoints(opts => { // Letting Wolverine HTTP automatically detect the tenant id! opts.TenantId.IsRouteArgumentNamed("tenant"); - + // Assert that the tenant id was successfully detected, - // or pull the rip cord on the request and return a + // or pull the rip cord on the request and return a // 400 w/ ProblemDetails opts.TenantId.AssertExists(); }); ``` -snippet source | anchor +snippet source | anchor In the code sample above, I'm choosing to make the "tenant" a mandatory route argument @@ -177,7 +177,7 @@ public static Task> Get(string tenant, IQuerySession session return session.Query().ToListAsync(); } ``` -snippet source | anchor +snippet source | anchor At runtime, Wolverine is now generating this code around that endpoint method: @@ -241,7 +241,7 @@ app.MapWolverineEndpoints(opts => opts.TenantId.AssertExists(); }); ``` -snippet source | anchor +snippet source | anchor At runtime, this is going to return a status code of 400 with a [ProblemDetails](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails?view=aspnetcore-7.0) specification @@ -261,7 +261,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 @@ -279,7 +279,7 @@ public static string MaybeTenanted(IMessageBus bus) return bus.TenantId ?? "none"; } ``` -snippet source | anchor +snippet source | anchor @@ -326,8 +326,8 @@ internal class ArgumentDetection : ITenantDetection public ValueTask DetectTenant(HttpContext httpContext) { - return httpContext.Request.RouteValues.TryGetValue(_argumentName, out var value) - ? new ValueTask(value?.ToString()) + return httpContext.Request.RouteValues.TryGetValue(_argumentName, out var value) + ? new ValueTask(value?.ToString()) : ValueTask.FromResult(null); } @@ -355,8 +355,7 @@ app.MapWolverineEndpoints(opts => // If your strategy does not need any IoC service // dependencies, just add it directly opts.TenantId.DetectWith(new MyCustomTenantDetection()); - - + // In this case, your detection type will be built by // the underlying IoC container for your application // No other registration is necessary here for the strategy @@ -364,7 +363,7 @@ app.MapWolverineEndpoints(opts => opts.TenantId.DetectWith(); }); ``` -snippet source | anchor +snippet source | anchor Just note that if you are having the IoC container for your Wolverine application resolve your @@ -386,8 +385,8 @@ at the same time, you will have to use Wolverine as a mediator but also pass the // While this is still valid.... [WolverineDelete("/todoitems/{tenant}/longhand")] public static async Task Delete( - string tenant, - DeleteTodo command, + string tenant, + DeleteTodo command, IMessageBus bus) { // Invoke inline for the specified tenant @@ -407,7 +406,7 @@ public static void Delete( session.Delete(command.Id); } ``` -snippet source | anchor +snippet source | anchor and with an expected result: @@ -419,22 +418,22 @@ and with an expected result: public static CreationResponse Create( // Only need this to express the location of the newly created // Todo object - string tenant, - CreateTodo command, + string tenant, + CreateTodo command, IDocumentSession session) { var todo = new Todo { Name = command.Name }; - + // Marten itself sets the Todo.Id identity // in this call - session.Store(todo); + session.Store(todo); // New syntax in Wolverine.HTTP 1.7 - // Helps Wolverine + // Helps Wolverine return CreationResponse.For(new TodoCreated(todo.Id), $"/todoitems/{tenant}/{todo.Id}"); } ``` -snippet source | anchor +snippet source | anchor See [Multi-Tenancy with Wolverine](/guide/handlers/multi-tenancy) for a little more information. diff --git a/docs/guide/http/policies.md b/docs/guide/http/policies.md index 653c6a571..2e5714b8b 100644 --- a/docs/guide/http/policies.md +++ b/docs/guide/http/policies.md @@ -58,14 +58,14 @@ app.MapWolverineEndpoints(opts => // This adds metadata for OpenAPI httpChain.WithMetadata(new CustomMetadata()); }); - + // more configuration for HTTP... - + // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); ``` -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 @@ -90,14 +90,14 @@ app.MapWolverineEndpoints(opts => // This adds metadata for OpenAPI httpChain.WithMetadata(new CustomMetadata()); }); - + // more configuration for HTTP... - + // Opting into the Fluent Validation middleware from // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor ## Resource Writer Policies @@ -132,7 +132,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 83b1886d8..2bec4ec7e 100644 --- a/docs/guide/http/problemdetails.md +++ b/docs/guide/http/problemdetails.md @@ -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 @@ -104,7 +104,7 @@ public async Task stop_with_problems_if_middleware_trips_off() }); } ``` -snippet source | anchor +snippet source | anchor Lastly, if Wolverine sees the existence of a `ProblemDetails` return value in any middleware, Wolverine will fill in OpenAPI diff --git a/docs/guide/http/querystring.md b/docs/guide/http/querystring.md index 4a5d19837..68d2d75e5 100644 --- a/docs/guide/http/querystring.md +++ b/docs/guide/http/querystring.md @@ -24,7 +24,7 @@ public static string UsingQueryString(string name) // name is from the query str return name.IsEmpty() ? "Name is missing" : $"Name is {name}"; } ``` -snippet source | anchor +snippet source | anchor And the corresponding tests: @@ -69,5 +69,5 @@ public async Task use_decimal_querystring_hit() body.ReadAsText().ShouldBe("Amount is 42.1"); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/routing.md b/docs/guide/http/routing.md index 08421de05..a5d62af45 100644 --- a/docs/guide/http/routing.md +++ b/docs/guide/http/routing.md @@ -21,7 +21,7 @@ public static string SimpleStringRouteArgument(string name) return $"Name is {name}"; } ``` -snippet source | anchor +snippet source | anchor In the sample above, the `name` argument will be the value of the route argument @@ -36,7 +36,7 @@ public static string IntRouteArgument(int age) return $"Age is {age}"; } ``` -snippet source | anchor +snippet source | anchor The following code snippet from `WolverineFx.Http` itself shows the valid route @@ -65,7 +65,7 @@ public static readonly Dictionary TypeOutputs = new() { typeof(DateTimeOffset), typeof(DateTimeOffset).FullName! } }; ``` -snippet source | anchor +snippet source | anchor ::: warning @@ -78,7 +78,17 @@ integer will result in a 404 response. You can add a name to the ASP.Net route with this property that is on all of the route definition attributes: -snippet: sample_using_route_name + + +```cs +[WolverinePost("/named/route", RouteName = "NamedRoute")] +public string Post() +{ + return "Hello"; +} +``` +snippet source | anchor + diff --git a/docs/guide/http/sagas.md b/docs/guide/http/sagas.md index 4273d30f3..1c4c9ae69 100644 --- a/docs/guide/http/sagas.md +++ b/docs/guide/http/sagas.md @@ -10,7 +10,7 @@ Let's say that we have a stateful saga type for making online reservations like public class Reservation : Saga { public string? Id { get; set; } - + // Apply the CompleteReservation to the saga public void Handle(BookReservation book, ILogger logger) { @@ -31,7 +31,7 @@ public class Reservation : Saga } } ``` -snippet source | anchor +snippet source | anchor To start the `Reservation` saga, you could use an HTTP endpoint method like this one: @@ -42,12 +42,12 @@ To start the `Reservation` saga, you could use an HTTP endpoint method like this [WolverinePost("/reservation")] public static ( // The first return value would be written out as the HTTP response body - ReservationBooked, - + ReservationBooked, + // Because this subclasses from Saga, Wolverine will persist this entity // with saga persistence - Reservation, - + Reservation, + // Other return values that trigger no special handling will be treated // as cascading messages ReservationTimeout) Post(StartReservation start) @@ -55,7 +55,7 @@ public static ( return (new ReservationBooked(start.ReservationId, DateTimeOffset.UtcNow), new Reservation { Id = start.ReservationId }, new ReservationTimeout(start.ReservationId)); } ``` -snippet source | anchor +snippet source | anchor Remember in Wolverine.HTTP that the *first* return value of an endpoint is assumed to be the response body by Wolverine, so if you are @@ -71,12 +71,12 @@ the `Saga` type *and* force Wolverine to use the return value as a new `Saga` as // This directs Wolverine to disregard the Reservation return value // as the response body, and allow Wolverine to use the Reservation // return as a new saga -[EmptyResponse] +[EmptyResponse] public static Reservation Post2(StartReservation start) { return new Reservation { Id = start.ReservationId }; } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/logging.md b/docs/guide/logging.md index cedebe9f9..e8e0382af 100644 --- a/docs/guide/logging.md +++ b/docs/guide/logging.md @@ -64,7 +64,7 @@ public class CustomizedHandler } } ``` -snippet source | anchor +snippet source | anchor Methods on message handler types with the signature: @@ -86,13 +86,13 @@ below: ```cs -public class QuietMessage{} +public class QuietMessage; public class QuietMessageHandler { [WolverineLogging( - telemetryEnabled:false, - successLogLevel: LogLevel.None, + telemetryEnabled:false, + successLogLevel: LogLevel.None, executionLogLevel:LogLevel.Trace)] public void Handle(QuietMessage message) { @@ -161,7 +161,7 @@ public class AuditedMessage [Audit("AccountIdentifier")] public int AccountId; } ``` -snippet source | anchor +snippet source | anchor Or if you are okay using a common message interface for common identification like "this message targets an account/organization/tenant/client" @@ -179,7 +179,7 @@ public interface IAccountMessage // A possible command that uses our marker interface above public record DebitAccount(int AccountId, decimal Amount) : IAccountMessage; ``` -snippet source | anchor +snippet source | anchor You can specify audited members through this syntax: @@ -190,7 +190,7 @@ You can specify audited members through this syntax: // opts is WolverineOptions inside of a UseWolverine() call opts.Policies.ForMessagesOfType().Audit(x => x.AccountId); ``` -snippet source | anchor +snippet source | anchor This will extend your log entries to like this: @@ -241,8 +241,8 @@ using var host = await Host.CreateDefaultBuilder() opts .PublishAllMessages() .ToPort(2222) - - // Disable Open Telemetry data collection on + + // Disable Open Telemetry data collection on // all messages sent, received, or executed // from this endpoint .TelemetryEnabled(false); @@ -377,7 +377,7 @@ public static class OrganizationTaggingMiddleware } } ``` -snippet source | anchor +snippet source | anchor Finally, we'll add the new middleware to all message handlers where the message implements the `IOrganizationRelated` interface like so: @@ -403,11 +403,11 @@ using var host = await Host.CreateDefaultBuilder() ```cs public static async Task publish_operation(IMessageBus bus, string tenantId, string name) { - // All outgoing messages or executed messages from this + // All outgoing messages or executed messages from this // IMessageBus object will be tagged with the tenant id bus.TenantId = tenantId; await bus.PublishAsync(new SomeMessage(name)); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messages.md b/docs/guide/messages.md index 6b7494eb1..2961893c2 100644 --- a/docs/guide/messages.md +++ b/docs/guide/messages.md @@ -125,7 +125,7 @@ Or lastly, make up your own criteria to find and mark message types within your ```cs opts.Discovery.CustomizeHandlerDiscovery(types => types.Includes.Implements()); ``` -snippet source | anchor +snippet source | anchor Note that only types that are in assemblies either marked with `[assembly: WolverineModule]` or the main application assembly @@ -179,7 +179,7 @@ opts.UseSystemTextJsonForSerialization(stj => stj.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; }); ``` -snippet source | anchor +snippet source | anchor When using Newtonsoft.Json, the default configuration is: @@ -210,7 +210,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ### MessagePack Serialization @@ -366,7 +366,7 @@ message type itself through Wolverine's `ISerializable` interface as shown below public class SerializedMessage : ISerializable { public string Name { get; set; } = "Bob Schneider"; - + public byte[] Write() { return Encoding.Default.GetBytes(Name); @@ -381,7 +381,7 @@ public class SerializedMessage : ISerializable } } ``` -snippet source | anchor +snippet source | anchor Wolverine will see the interface implementation of the message type, and automatically opt into using this "intrinsic" diff --git a/docs/guide/messaging/broadcast-to-topic.md b/docs/guide/messaging/broadcast-to-topic.md index e7375e48a..aa727701f 100644 --- a/docs/guide/messaging/broadcast-to-topic.md +++ b/docs/guide/messaging/broadcast-to-topic.md @@ -20,7 +20,7 @@ theSender = Host.CreateDefaultBuilder() opts.PublishMessagesToRabbitMqExchange("wolverine.topics", m => m.TopicName); }).Start(); ``` -snippet source | anchor +snippet source | anchor You can explicitly publish a message to a topic through this syntax: @@ -33,7 +33,7 @@ var publisher = theSender.Services await publisher.BroadcastToTopicAsync("color.purple", new Message1()); ``` -snippet source | anchor +snippet source | anchor ```cs var publisher = theSender.Services @@ -41,7 +41,7 @@ var publisher = theSender.Services await publisher.BroadcastToTopicAsync("color.purple", new Message1()); ``` -snippet source | anchor +snippet source | anchor ## Topic Sending as Cascading Message @@ -61,5 +61,5 @@ public class ManuallyRoutedTopicResponseHandler } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/endpoint-operations.md b/docs/guide/messaging/endpoint-operations.md index 20a9df6e0..681696aec 100644 --- a/docs/guide/messaging/endpoint-operations.md +++ b/docs/guide/messaging/endpoint-operations.md @@ -33,7 +33,7 @@ await bus.EndpointFor("One").InvokeAsync(new SomeMessage()); var answer = bus.EndpointFor("One") .InvokeAsync(new Question()); ``` -snippet source | anchor +snippet source | anchor There's another option to reference a messaging endpoint by `Uri` as shown below: @@ -45,5 +45,5 @@ There's another option to reference a messaging endpoint by `Uri` as shown below await bus.EndpointFor(new Uri("rabbitmq://queue/rabbit-one")) .InvokeAsync(new SomeMessage()); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/expiration.md b/docs/guide/messaging/expiration.md index 8474d2367..325b5e8e5 100644 --- a/docs/guide/messaging/expiration.md +++ b/docs/guide/messaging/expiration.md @@ -20,7 +20,7 @@ public DateTimeOffset? DeliverBy set => _deliverBy = value?.ToUniversalTime(); } ``` -snippet source | anchor +snippet source | anchor At runtime, Wolverine will: @@ -41,13 +41,13 @@ public async Task message_expiration(IMessageBus bus) { // Disregard the message if it isn't sent and/or processed within 3 seconds from now await bus.SendAsync(new StatusUpdate("Okay"), new DeliveryOptions { DeliverWithin = 3.Seconds() }); - + // Disregard the message if it isn't sent and/or processed by 3 PM today // but watch all the potentially harmful time zone issues in your real code that I'm ignoring here! await bus.SendAsync(new StatusUpdate("Okay"), new DeliveryOptions { DeliverBy = DateTime.Today.AddHours(15)}); } ``` -snippet source | anchor +snippet source | anchor ## By Subscriber @@ -73,15 +73,15 @@ using var host = await Host.CreateDefaultBuilder() // Explicitly configure a delivery expiration of 5 seconds // for a specific Azure Service Bus queue opts.PublishMessage().ToAzureServiceBusQueue("transient") - - // If the messages are transient, it's likely that they should not be + + // If the messages are transient, it's likely that they should not be // durably stored, so make things lighter in your system .BufferedInMemory() .DeliverWithin(5.Seconds()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## By Message Type @@ -92,7 +92,7 @@ on the message type as in this sample: ```cs -// The attribute directs Wolverine to send this message with +// The attribute directs Wolverine to send this message with // a "deliver within 5 seconds, or discard" directive [DeliverWithin(5)] public record AccountUpdated(Guid AccountId, decimal Balance); diff --git a/docs/guide/messaging/listeners.md b/docs/guide/messaging/listeners.md index 58599d1d9..cb74c5ecc 100644 --- a/docs/guide/messaging/listeners.md +++ b/docs/guide/messaging/listeners.md @@ -27,17 +27,17 @@ using var host = await Host.CreateDefaultBuilder() { // The Rabbit MQ transport supports all three types of listeners opts.UseRabbitMq(); - + // The durable mode requires some sort of envelope storage opts.PersistMessagesWithPostgresql("some connection string"); opts.ListenToRabbitQueue("inline") // Process inline, default is with one listener .ProcessInline() - + // But, you can use multiple, parallel listeners .ListenerCount(5); - + opts.ListenToRabbitQueue("buffered") // Buffer the messages in memory for increased throughput .BufferedInMemory(new BufferingLimits(1000, 500)); @@ -147,7 +147,7 @@ var host = await Host.CreateDefaultBuilder().UseWolverine(opts => opts.PersistMessagesWithPostgresql(Servers.PostgresConnectionString, "listeners"); opts.ListenToRabbitQueue("ordered") - + // This option is available on all types of Wolverine // endpoints that can be configured to be a listener .ListenWithStrictOrdering(); diff --git a/docs/guide/messaging/message-bus.md b/docs/guide/messaging/message-bus.md index a8558232c..a524e1c03 100644 --- a/docs/guide/messaging/message-bus.md +++ b/docs/guide/messaging/message-bus.md @@ -37,7 +37,7 @@ public static async Task use_message_bus(IMessageBus bus) // don't wait around await bus.SendAsync(new DebitAccount(1111, 250)); - // Or instead, publish it to any interested subscribers, + // Or instead, publish it to any interested subscribers, // but don't worry about it if there are actually any subscribers // This is probably best for raising event messages await bus.PublishAsync(new DebitAccount(1111, 300)); @@ -152,7 +152,7 @@ public ValueTask SendMessage(IMessageContext bus) return bus.SendAsync(@event); } ``` -snippet source | anchor +snippet source | anchor That by itself will send the `InvoiceCreated` message to whatever subscribers are interested in @@ -180,7 +180,7 @@ public ValueTask PublishMessage(IMessageContext bus) return bus.PublishAsync(@event); } ``` -snippet source | anchor +snippet source | anchor ## Scheduling Message Delivery or Execution @@ -274,14 +274,14 @@ public static IEnumerable Consume(Incoming incoming) { // Delay the message delivery by 10 minutes yield return new Message1().DelayedFor(10.Minutes()); - + // Schedule the message delivery for a certain time yield return new Message2().ScheduledAt(new DateTimeOffset(DateTime.Today.AddDays(2))); - + // Customize the message delivery however you please... yield return new Message3() .WithDeliveryOptions(new DeliveryOptions().WithHeader("foo", "bar")); - + // Send back to the original sender yield return Respond.ToSender(new Message4()); } diff --git a/docs/guide/messaging/subscriptions.md b/docs/guide/messaging/subscriptions.md index a8e6f0062..34930553b 100644 --- a/docs/guide/messaging/subscriptions.md +++ b/docs/guide/messaging/subscriptions.md @@ -15,7 +15,7 @@ public class SendingExample } } ``` -snippet source | anchor +snippet source | anchor When sending, publishing, scheduling, or invoking a message type for the first time, Wolverine runs through diff --git a/docs/guide/messaging/transports/azureservicebus/conventional-routing.md b/docs/guide/messaging/transports/azureservicebus/conventional-routing.md index e29b7ccd1..0f7cc5218 100644 --- a/docs/guide/messaging/transports/azureservicebus/conventional-routing.md +++ b/docs/guide/messaging/transports/azureservicebus/conventional-routing.md @@ -51,7 +51,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Route to Topics and Subscriptions diff --git a/docs/guide/messaging/transports/azureservicebus/index.md b/docs/guide/messaging/transports/azureservicebus/index.md index f8d84f783..166dee883 100644 --- a/docs/guide/messaging/transports/azureservicebus/index.md +++ b/docs/guide/messaging/transports/azureservicebus/index.md @@ -40,7 +40,7 @@ using var host = await Host.CreateDefaultBuilder() azure => { azure.RetryOptions.Mode = ServiceBusRetryMode.Exponential; }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor The advanced configuration for the broker is the [ServiceBusClientOptions](https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusclientoptions?view=azure-dotnet) class from the Azure.Messaging.ServiceBus @@ -80,7 +80,7 @@ var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToAzureServiceBusQueue("send_and_receive"); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/azureservicebus/interoperability.md b/docs/guide/messaging/transports/azureservicebus/interoperability.md index fe5678735..fa2fc493f 100644 --- a/docs/guide/messaging/transports/azureservicebus/interoperability.md +++ b/docs/guide/messaging/transports/azureservicebus/interoperability.md @@ -23,7 +23,7 @@ public class CustomAzureServiceBusMapper : IAzureServiceBusEnvelopeMapper public void MapIncomingToEnvelope(Envelope envelope, ServiceBusReceivedMessage incoming) { envelope.Data = incoming.Body.ToArray(); - + // You will have to help Wolverine out by either telling Wolverine // what the message type is, or by reading the actual message object, // or by telling Wolverine separately what the default message type @@ -37,7 +37,7 @@ public class CustomAzureServiceBusMapper : IAzureServiceBusEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor To apply that mapper to specific endpoints, use this syntax on any type of Azure Service Bus endpoint: @@ -56,5 +56,5 @@ using var host = await Host.CreateDefaultBuilder() .ConfigureSenders(s => s.InteropWith(new CustomAzureServiceBusMapper())); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/azureservicebus/listening.md b/docs/guide/messaging/transports/azureservicebus/listening.md index 3fc8e85db..63479805b 100644 --- a/docs/guide/messaging/transports/azureservicebus/listening.md +++ b/docs/guide/messaging/transports/azureservicebus/listening.md @@ -39,7 +39,7 @@ using var host = await Host.CreateDefaultBuilder() .BufferedInMemory(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Conventional Listener Configuration @@ -67,7 +67,7 @@ using var host = await Host.CreateDefaultBuilder() .ConfigureListeners(listener => { listener.UseDurableInbox(new BufferingLimits(500, 100)); }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Note that any of these settings would be overridden by specific configuration to diff --git a/docs/guide/messaging/transports/azureservicebus/object-management.md b/docs/guide/messaging/transports/azureservicebus/object-management.md index 990fea3dc..d72ddbdd5 100644 --- a/docs/guide/messaging/transports/azureservicebus/object-management.md +++ b/docs/guide/messaging/transports/azureservicebus/object-management.md @@ -17,7 +17,7 @@ using var host = await Host.CreateDefaultBuilder() opts.Services.AddResourceSetupOnStartup(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor You can also direct Wolverine to build out Azure Service Bus object on demand as needed with: @@ -29,13 +29,13 @@ using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.UseAzureServiceBus("some connection string") - + // Wolverine will build missing queues, topics, and subscriptions // as necessary at runtime .AutoProvision(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor You can also opt to auto-purge all queues (there's also an option to do this queue by queue) on application @@ -51,7 +51,7 @@ using var host = await Host.CreateDefaultBuilder() .AutoPurgeOnStartup(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor And lastly, because Azure Service Bus is a centralized broker model and you may have to share a single @@ -91,13 +91,13 @@ using var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages() .ToAzureServiceBusQueue("outgoing") .ConfigureQueue(options => { options.LockDuration = 3.Seconds(); }) - + // You may need to change the maximum number of messages // in message batches depending on the size of your messages // if you hit maximum data constraints .MessageBatchSize(50); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/azureservicebus/publishing.md b/docs/guide/messaging/transports/azureservicebus/publishing.md index 821881022..a1cbbe6b3 100644 --- a/docs/guide/messaging/transports/azureservicebus/publishing.md +++ b/docs/guide/messaging/transports/azureservicebus/publishing.md @@ -25,7 +25,7 @@ using var host = await Host.CreateDefaultBuilder() .BufferedInMemory(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -54,7 +54,7 @@ using var host = await Host.CreateDefaultBuilder() .ConfigureSenders(sender => sender.UseDurableOutbox()); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Note that any of these settings would be overridden by specific configuration to diff --git a/docs/guide/messaging/transports/azureservicebus/scheduled.md b/docs/guide/messaging/transports/azureservicebus/scheduled.md index 6503a45df..b379ada6c 100644 --- a/docs/guide/messaging/transports/azureservicebus/scheduled.md +++ b/docs/guide/messaging/transports/azureservicebus/scheduled.md @@ -27,7 +27,7 @@ public async Task SendScheduledMessage(IMessageContext bus, Guid invoiceId) await bus.ScheduleAsync(message, DateTimeOffset.Now.AddDays(30)); } ``` -snippet source | anchor +snippet source | anchor And also use Azure Service Bus scheduled delivery for scheduled retries (assuming that the listening endpoint was an **inline** Azure Service Bus listener): @@ -56,5 +56,5 @@ using var host = Host.CreateDefaultBuilder() }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/azureservicebus/session-identifiers.md b/docs/guide/messaging/transports/azureservicebus/session-identifiers.md index 4f1733082..530330a64 100644 --- a/docs/guide/messaging/transports/azureservicebus/session-identifiers.md +++ b/docs/guide/messaging/transports/azureservicebus/session-identifiers.md @@ -25,24 +25,24 @@ _host = await Host.CreateDefaultBuilder() opts.PublishMessage().ToAzureServiceBusQueue("send_and_receive"); opts.ListenToAzureServiceBusQueue("fifo1") - + // Require session identifiers with this queue .RequireSessions() - + // This controls the Wolverine handling to force it to process // messages sequentially .Sequential(); - + opts.PublishMessage() .ToAzureServiceBusQueue("fifo1"); opts.PublishMessage().ToAzureServiceBusTopic("asb3"); opts.ListenToAzureServiceBusSubscription("asb3") .FromTopic("asb3") - + // Require sessions on this subscription .RequireSessions(1) - + .ProcessInline(); }).StartAsync(); ``` @@ -59,7 +59,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/azureservicebus/topics.md b/docs/guide/messaging/transports/azureservicebus/topics.md index 07e94f016..450992002 100644 --- a/docs/guide/messaging/transports/azureservicebus/topics.md +++ b/docs/guide/messaging/transports/azureservicebus/topics.md @@ -11,15 +11,15 @@ using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.UseAzureServiceBus("some connection string") - + // If this is part of your configuration, Wolverine will try to create // any missing topics or subscriptions in the configuration at application // start up time .AutoProvision(); - + // Publish to a topic opts.PublishMessage().ToAzureServiceBusTopic("topic1") - + // Option to configure how the topic would be configured if // built by Wolverine .ConfigureTopic(topic => @@ -39,7 +39,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor To fully utilize subscription listening, be careful with using [Requeue error handling](/guide/handlers/error-handling) actions. In order to truly make @@ -62,7 +62,7 @@ opts.ListenToAzureServiceBusSubscription( }) .FromTopic("topic1"); ``` -snippet source | anchor +snippet source | anchor The default filter if not customized is a simple `1=1` (always true) filter. diff --git a/docs/guide/messaging/transports/kafka.md b/docs/guide/messaging/transports/kafka.md index 254a0c969..cc97f56fc 100644 --- a/docs/guide/messaging/transports/kafka.md +++ b/docs/guide/messaging/transports/kafka.md @@ -23,19 +23,19 @@ using var host = await Host.CreateDefaultBuilder() .UseWolverine(opts => { opts.UseKafka("localhost:29092") - + // See https://github.com/confluentinc/confluent-kafka-dotnet for the exact options here .ConfigureClient(client => { // configure both producers and consumers - + }) - + .ConfigureConsumers(consumer => { // configure only consumers }) - + .ConfigureProducers(producer => { // configure only producers @@ -45,21 +45,20 @@ using var host = await Host.CreateDefaultBuilder() // based on the message type (or message attributes) // This will get fancier in the near future opts.PublishAllMessages().ToKafkaTopics(); - + // Or explicitly make subscription rules opts.PublishMessage() .ToKafkaTopic("colors"); - + // Listen to topics opts.ListenToKafkaTopic("red") .ProcessInline(); opts.ListenToKafkaTopic("green") .BufferedInMemory(); - // This will direct Wolverine to try to ensure that all - // referenced Kafka topics exist at application start up + // referenced Kafka topics exist at application start up // time opts.Services.AddResourceSetupOnStartup(); }).StartAsync(); diff --git a/docs/guide/messaging/transports/local.md b/docs/guide/messaging/transports/local.md index 78275c40c..3375da73a 100644 --- a/docs/guide/messaging/transports/local.md +++ b/docs/guide/messaging/transports/local.md @@ -48,7 +48,7 @@ public ValueTask EnqueueToQueue(IMessageContext bus) return bus.EndpointFor("highpriority").SendAsync(@event); } ``` -snippet source | anchor +snippet source | anchor ## Scheduling Local Execution @@ -77,7 +77,7 @@ public async Task ScheduleLocally(IMessageContext bus, Guid invoiceId) await bus.ScheduleAsync(message, DateTimeOffset.Now.AddDays(30)); } ``` -snippet source | anchor +snippet source | anchor @@ -92,11 +92,9 @@ on a message type: ```cs [LocalQueue("important")] -public class ImportanceMessage -{ -} +public class ImportanceMessage; ``` -snippet source | anchor +snippet source | anchor Otherwise, you can take advantage of Wolverine's message routing rules like this: @@ -163,11 +161,11 @@ public static async Task disable_queue_routing() // routing that would take precedence over other conventional // routing opts.Policies.DisableConventionalLocalRouting(); - + // Other routing conventions. Rabbit MQ? SQS? }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Configuring Local Queues @@ -274,7 +272,7 @@ using var host = await Host.CreateDefaultBuilder() opts.LocalQueue("four").UseDurableInbox(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/mqtt.md b/docs/guide/messaging/transports/mqtt.md index fec52d1b8..01f7dd991 100644 --- a/docs/guide/messaging/transports/mqtt.md +++ b/docs/guide/messaging/transports/mqtt.md @@ -44,8 +44,7 @@ using var host = await Host.CreateDefaultBuilder() // not identifying metadata, tell Wolverine // to assume the incoming message is this type .DefaultIncomingMessage() - - + // The default is AtLeastOnce .QualityOfService(MqttQualityOfServiceLevel.AtMostOnce); @@ -131,11 +130,9 @@ or by using the `[Topic("topic name")]` attribute as shown below: ```cs [Topic("one")] -public class TopicMessage1 -{ -} +public class TopicMessage1; ``` -snippet source | anchor +snippet source | anchor ```cs [Topic("color.blue")] @@ -144,7 +141,7 @@ public class FirstMessage public Guid Id { get; set; } = Guid.NewGuid(); } ``` -snippet source | anchor +snippet source | anchor ## Publishing by Topic Rules @@ -161,7 +158,7 @@ public interface ITenantMessage string TenantId { get; } } ``` -snippet source | anchor +snippet source | anchor To publish any message implementing that interface to an MQTT topic, you could specify the topic name logic like this: @@ -185,10 +182,10 @@ using var host = await Host.CreateDefaultBuilder() }); }); - // Publish any message that implements ITenantMessage to + // Publish any message that implements ITenantMessage to // MQTT with a topic derived from the message opts.PublishMessagesToMqttTopic(m => $"{m.GetType().Name.ToLower()}/{m.TenantId}") - + // Specify or configure sending through Wolverine for all // MQTT topic broadcasting .QualityOfService(MqttQualityOfServiceLevel.ExactlyOnce) @@ -196,7 +193,7 @@ using var host = await Host.CreateDefaultBuilder() }) .StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Listening by Topic Filter @@ -275,7 +272,7 @@ public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper { // This is the only absolutely mandatory item outgoing.PayloadSegment = envelope.Data; - + // Maybe enrich this more? outgoing.ContentType = envelope.ContentType; } @@ -285,9 +282,9 @@ public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper // These are the absolute minimums necessary for Wolverine to function envelope.MessageType = typeof(PaymentMade).ToMessageTypeName(); envelope.Data = incoming.PayloadSegment.Array; - + // Optional items - envelope.DeliverWithin = 5.Seconds(); // throw away the message if it + envelope.DeliverWithin = 5.Seconds(); // throw away the message if it // is not successfully processed // within 5 seconds } @@ -298,7 +295,7 @@ public class MyMqttEnvelopeMapper : IMqttEnvelopeMapper } } ``` -snippet source | anchor +snippet source | anchor And apply that to an MQTT topic like so: @@ -326,11 +323,11 @@ using var host = await Host.CreateDefaultBuilder() // the message type opts.PublishAllMessages() .ToMqttTopics() - + // Tell Wolverine to map envelopes to MQTT messages // with our custom strategy .UseInterop(new MyMqttEnvelopeMapper()) - + .QualityOfService(MqttQualityOfServiceLevel.AtMostOnce); }) .StartAsync(); @@ -363,7 +360,7 @@ public static ClearMqttTopic Handle(TriggerZero message) return new ClearMqttTopic("red"); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/conventional-routing.md b/docs/guide/messaging/transports/rabbitmq/conventional-routing.md index fe773a048..32a59b611 100644 --- a/docs/guide/messaging/transports/rabbitmq/conventional-routing.md +++ b/docs/guide/messaging/transports/rabbitmq/conventional-routing.md @@ -20,7 +20,7 @@ using var host = await Host.CreateDefaultBuilder() .UseConventionalRouting(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor With the defaults from above, for each message that the application can handle @@ -73,7 +73,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/deadletterqueues.md b/docs/guide/messaging/transports/rabbitmq/deadletterqueues.md index 262244434..b27962842 100644 --- a/docs/guide/messaging/transports/rabbitmq/deadletterqueues.md +++ b/docs/guide/messaging/transports/rabbitmq/deadletterqueues.md @@ -28,7 +28,6 @@ using var host = await Host.CreateDefaultBuilder() { l.DeadLetterQueueing(new DeadLetterQueue($"{l.QueueName}-errors")); }); - // Use a different dead letter queue for this specific queue opts.ListenToRabbitQueue("incoming") @@ -36,7 +35,7 @@ using var host = await Host.CreateDefaultBuilder() }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ::: warning @@ -63,7 +62,6 @@ using var host = await Host.CreateDefaultBuilder() { l.DeadLetterQueueing(new DeadLetterQueue($"{l.QueueName}-errors", DeadLetterQueueMode.InteropFriendly)); }); - // Use a different dead letter queue for this specific queue opts.ListenToRabbitQueue("incoming") @@ -71,7 +69,7 @@ using var host = await Host.CreateDefaultBuilder() }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor And lastly, if you don't particularly want to have any Rabbit MQ dead letter queues and you quite like the [database backed @@ -93,14 +91,13 @@ using var host = await Host.CreateDefaultBuilder() // Really does the same thing as the first usage l.DisableDeadLetterQueueing(); }); - // Disable the dead letter queue for this specific queue opts.ListenToRabbitQueue("incoming").DisableDeadLetterQueueing(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/index.md b/docs/guide/messaging/transports/rabbitmq/index.md index 0bb6be425..cc860ace7 100644 --- a/docs/guide/messaging/transports/rabbitmq/index.md +++ b/docs/guide/messaging/transports/rabbitmq/index.md @@ -87,7 +87,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor To only send Rabbit MQ messages, but never receive them: @@ -116,7 +116,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -142,7 +142,7 @@ using var host = await Host.CreateDefaultBuilder() .EnableWolverineControlQueues(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -162,14 +162,12 @@ using var host = await Host.CreateDefaultBuilder() // *A* way to configure Rabbit MQ using their Uri schema // documented here: https://www.rabbitmq.com/uri-spec.html opts.UseRabbitMq(new Uri("amqp://localhost")) - + // Stop Wolverine from trying to create a reply queue // for this node if your process does not have permission to // do so against your Rabbit MQ broker .DisableSystemRequestReplyQueueDeclaration(); - - // Set up a listener for a queue, but also // fine-tune the queue characteristics if Wolverine // will be governing the queue setup @@ -180,7 +178,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Of course, doing so means that you will not be able to do request/reply through Rabbit MQ with your Wolverine application. diff --git a/docs/guide/messaging/transports/rabbitmq/interoperability.md b/docs/guide/messaging/transports/rabbitmq/interoperability.md index a5a6a0ebb..1c6a7f57d 100644 --- a/docs/guide/messaging/transports/rabbitmq/interoperability.md +++ b/docs/guide/messaging/transports/rabbitmq/interoperability.md @@ -36,7 +36,7 @@ using var host = await Host.CreateDefaultBuilder() .DefaultIncomingMessage(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor With this setting, there is **no other required headers** for Wolverine to process incoming messages. However, Wolverine will be @@ -62,7 +62,7 @@ public class SpecialMapper : IRabbitMqEnvelopeMapper outgoing.CorrelationId = envelope.CorrelationId; outgoing.MessageId = envelope.Id.ToString(); outgoing.ContentType = "application/json"; - + if (envelope.DeliverBy.HasValue) { var ttl = Convert.ToInt32(envelope.DeliverBy.Value.Subtract(DateTimeOffset.Now).TotalMilliseconds); @@ -121,14 +121,14 @@ using var host = await Host.CreateDefaultBuilder() opts.ListenToRabbitQueue("emails") // Apply your custom interoperability strategy here .UseInterop(new SpecialMapper()) - + // You may still want to define the default incoming // message as the message type name may not be sent // by the upstream system .DefaultIncomingMessage(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor @@ -166,10 +166,9 @@ Wolverine = await Host.CreateDefaultBuilder().UseWolverine(opts => opts.ListenToRabbitQueue("wolverine") .UseNServiceBusInterop() - .UseForReplies(); - + // This facilitates messaging from NServiceBus (or MassTransit) sending as interface // types, whereas Wolverine only wants to deal with concrete types opts.Policies.RegisterInteropMessageAssembly(typeof(IInterfaceMessage).Assembly); diff --git a/docs/guide/messaging/transports/rabbitmq/listening.md b/docs/guide/messaging/transports/rabbitmq/listening.md index c70e4e7dd..5dc1c2d20 100644 --- a/docs/guide/messaging/transports/rabbitmq/listening.md +++ b/docs/guide/messaging/transports/rabbitmq/listening.md @@ -38,7 +38,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor To optimize and tune the message processing, you may want to read more about the [Rabbit MQ prefetch count and prefetch @@ -80,5 +80,5 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/object-management.md b/docs/guide/messaging/transports/rabbitmq/object-management.md index e4787bb37..93e408e1e 100644 --- a/docs/guide/messaging/transports/rabbitmq/object-management.md +++ b/docs/guide/messaging/transports/rabbitmq/object-management.md @@ -30,7 +30,7 @@ using var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToRabbitExchange("exchange1"); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor At development time -- or occasionally in production systems -- you may want to have the messaging @@ -47,7 +47,7 @@ using var host = await Host.CreateDefaultBuilder() .AutoPurgeOnStartup(); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Or you can be more selective and only have certain queues of volatile messages purged @@ -64,7 +64,7 @@ using var host = await Host.CreateDefaultBuilder() .DeclareQueue("queue2", q => q.PurgeOnStartup = true); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor Wolverine's Rabbit MQ integration also supports the [Oakton stateful resource](https://jasperfx.github.io/oakton/guide/host/resources.html) model, @@ -160,7 +160,7 @@ runtime.ModifyRabbitMqObjects(o => // Unbind a queue from an exchange runtime.UnBindRabbitMqQueue(queueName, exchangeName, bindingKey); ``` -snippet source | anchor +snippet source | anchor ## Inside of Wolverine Extensions @@ -183,10 +183,9 @@ public class MyModuleExtension : IWolverineExtension .DeclareExchange("my-module") .DeclareQueue("my-queue"); - } } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/rabbitmq/publishing.md b/docs/guide/messaging/transports/rabbitmq/publishing.md index 5b9116248..db3de2721 100644 --- a/docs/guide/messaging/transports/rabbitmq/publishing.md +++ b/docs/guide/messaging/transports/rabbitmq/publishing.md @@ -23,7 +23,7 @@ using var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToRabbitQueue("special", queue => { queue.IsExclusive = true; }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Publish to an Exchange @@ -55,7 +55,7 @@ using var host = await Host.CreateDefaultBuilder() }); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Publish to a Routing Key @@ -84,6 +84,6 @@ using var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToRabbitExchange("exchange1"); }).StartAsync(); ``` -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 1c76144d0..f463bddc0 100644 --- a/docs/guide/messaging/transports/rabbitmq/topics.md +++ b/docs/guide/messaging/transports/rabbitmq/topics.md @@ -23,7 +23,7 @@ using var host = await Host.CreateDefaultBuilder() opts.ListenToRabbitQueue(""); }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor While we're specifying the exchange name ("topics-exchange"), we did nothing to specify the topic @@ -35,7 +35,7 @@ name. With this set up, when you publish a message in this application like so: var publisher = host.Services.GetRequiredService(); await publisher.SendAsync(new Message1()); ``` -snippet source | anchor +snippet source | anchor You will be sending that message to the "topics-exchange" with a topic name derived from @@ -49,11 +49,9 @@ on a message type like so: ```cs [Topic("one")] -public class TopicMessage1 -{ -} +public class TopicMessage1; ``` -snippet source | anchor +snippet source | anchor ```cs [Topic("color.blue")] @@ -62,7 +60,7 @@ 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: @@ -72,7 +70,7 @@ Of course, you can always explicitly send a message to a specific topic with thi ```cs await publisher.BroadcastToTopicAsync("color.*", new Message1()); ``` -snippet source | anchor +snippet source | anchor Note two things about the code above: @@ -102,7 +100,7 @@ theSender = Host.CreateDefaultBuilder() opts.PublishMessagesToRabbitMqExchange("wolverine.topics", m => m.TopicName); }).Start(); ``` -snippet source | anchor +snippet source | anchor ## Publishing by Topic Rule @@ -119,7 +117,7 @@ public interface ITenantMessage string TenantId { get; } } ``` -snippet source | anchor +snippet source | anchor Let's say that any message that implements that interface, we want published to the @@ -133,15 +131,15 @@ using var host = await Host.CreateDefaultBuilder() { opts.UseRabbitMq(); - // Publish any message that implements ITenantMessage to + // Publish any message that implements ITenantMessage to // a Rabbit MQ "Topic" exchange named "tenant.messages" opts.PublishMessagesToRabbitMqExchange("tenant.messages",m => $"{m.GetType().Name.ToLower()}/{m.TenantId}") - + // Specify or configure sending through Wolverine for all // messages through this Exchange .BufferedInMemory(); }) .StartAsync(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/messaging/transports/sqs/deadletterqueues.md b/docs/guide/messaging/transports/sqs/deadletterqueues.md index 288dded70..a12cde20b 100644 --- a/docs/guide/messaging/transports/sqs/deadletterqueues.md +++ b/docs/guide/messaging/transports/sqs/deadletterqueues.md @@ -15,7 +15,7 @@ var host = await Host.CreateDefaultBuilder() // No dead letter queueing opts.ListenToSqsQueue("incoming") .DisableDeadLetterQueueing(); - + // Use a different dead letter queue opts.ListenToSqsQueue("important") .ConfigureDeadLetterQueue("important_errors", q => diff --git a/docs/guide/messaging/transports/sqs/index.md b/docs/guide/messaging/transports/sqs/index.md index 5dfc45334..5754a93af 100644 --- a/docs/guide/messaging/transports/sqs/index.md +++ b/docs/guide/messaging/transports/sqs/index.md @@ -28,7 +28,7 @@ var host = await Host.CreateDefaultBuilder() // Let Wolverine create missing queues as necessary .AutoProvision() - // Optionally purge all queues on application startup. + // Optionally purge all queues on application startup. // Warning though, this is potentially slow .AutoPurgeOnStartup(); }).StartAsync(); @@ -55,7 +55,7 @@ var host = await Host.CreateDefaultBuilder() // Let Wolverine create missing queues as necessary .AutoProvision() - // Optionally purge all queues on application startup. + // Optionally purge all queues on application startup. // Warning though, this is potentially slow .AutoPurgeOnStartup(); }).StartAsync(); @@ -98,13 +98,13 @@ var host = await Host.CreateDefaultBuilder() // that you may need to configure }) - // And you can also add explicit AWS credentials + // And you can also add explicit AWS credentials .Credentials(new BasicAWSCredentials(config["AwsAccessKey"], config["AwsSecretKey"])) // Let Wolverine create missing queues as necessary .AutoProvision() - // Optionally purge all queues on application startup. + // Optionally purge all queues on application startup. // Warning though, this is potentially slow .AutoPurgeOnStartup(); }).StartAsync(); diff --git a/docs/guide/messaging/transports/sqs/interoperability.md b/docs/guide/messaging/transports/sqs/interoperability.md index bdd41d624..b14322969 100644 --- a/docs/guide/messaging/transports/sqs/interoperability.md +++ b/docs/guide/messaging/transports/sqs/interoperability.md @@ -18,8 +18,8 @@ using var host = await Host.CreateDefaultBuilder() opts.ListenToSqsQueue("incoming").ReceiveRawJsonMessage( // Specify the single message type for this queue - typeof(Message1), - + typeof(Message1), + // Optionally customize System.Text.Json configuration o => { @@ -42,8 +42,8 @@ using var host = await Host.CreateDefaultBuilder() opts.PublishAllMessages().ToSqsQueue("outgoing").SendRawJsonMessage( // Specify the single message type for this queue - typeof(Message1), - + typeof(Message1), + // Optionally customize System.Text.Json configuration o => { diff --git a/docs/guide/messaging/transports/sqs/listening.md b/docs/guide/messaging/transports/sqs/listening.md index 59cfa2410..2524ec8a0 100644 --- a/docs/guide/messaging/transports/sqs/listening.md +++ b/docs/guide/messaging/transports/sqs/listening.md @@ -13,7 +13,7 @@ var host = await Host.CreateDefaultBuilder() // Let Wolverine create missing queues as necessary .AutoProvision() - // Optionally purge all queues on application startup. + // Optionally purge all queues on application startup. // Warning though, this is potentially slow .AutoPurgeOnStartup(); diff --git a/docs/guide/messaging/transports/sqs/publishing.md b/docs/guide/messaging/transports/sqs/publishing.md index 5738de66b..a3512fd50 100644 --- a/docs/guide/messaging/transports/sqs/publishing.md +++ b/docs/guide/messaging/transports/sqs/publishing.md @@ -13,9 +13,9 @@ var host = await Host.CreateDefaultBuilder() opts.PublishMessage() .ToSqsQueue("outbound1") - + // Increase the outgoing message throughput, but at the cost - // of strict ordering + // of strict ordering .MessageBatchMaxDegreeOfParallelism(Environment.ProcessorCount); opts.PublishMessage() diff --git a/docs/guide/messaging/transports/tcp.md b/docs/guide/messaging/transports/tcp.md index e62fc714a..f5e84631b 100644 --- a/docs/guide/messaging/transports/tcp.md +++ b/docs/guide/messaging/transports/tcp.md @@ -71,7 +71,7 @@ await bus.EndpointFor("One").InvokeAsync(new SomeMessage()); var answer = bus.EndpointFor("One") .InvokeAsync(new Question()); ``` -snippet source | anchor +snippet source | anchor or use `ToServerAndPort()` to send messages to a port on another machine: diff --git a/docs/guide/runtime.md b/docs/guide/runtime.md index 93ace032c..39de45140 100644 --- a/docs/guide/runtime.md +++ b/docs/guide/runtime.md @@ -137,7 +137,7 @@ To opt into buffering, you use this syntax: opts.ListenToAzureServiceBusQueue("incoming") .BufferedInMemory(new BufferingLimits(1000, 200)); ``` -snippet source | anchor +snippet source | anchor At runtime, you have a local [TPL Dataflow queue](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library) between the Wolverine callers and the broker: @@ -170,7 +170,7 @@ opts.ListenToAzureServiceBusQueue("incoming") opts.PublishAllMessages().ToAzureServiceBusQueue("outgoing") .UseDurableOutbox(); ``` -snippet source | anchor +snippet source | anchor Or use policies to do this in one fell swoop (which may not be what you actually want, but you could do this!): @@ -180,7 +180,7 @@ Or use policies to do this in one fell swoop (which may not be what you actually ```cs opts.Policies.UseDurableOutboxOnAllSendingEndpoints(); ``` -snippet source | anchor +snippet source | anchor As shown below, the `Durable` endpoint option adds an extra step to the `Buffered` behavior to add database storage of the diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 1549fd523..f4b7c65cf 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -51,7 +51,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: @@ -89,12 +89,12 @@ public void handle_a_debit_that_makes_the_account_have_a_low_balance() delivery.ScheduleDelay.Value.ShouldNotBe(TimeSpan.Zero); }) .AccountId.ShouldBe(account.Id); - + // Assert that there are no messages of type AccountOverdrawn messages.ShouldHaveNoMessageOfType(); } ``` -snippet source | anchor +snippet source | anchor The supported extension methods so far are in the [TestingExtensions](https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/TestingExtensions.cs) class. @@ -120,15 +120,15 @@ directly: ```cs -[Transactional] +[Transactional] public static async Task Handle( - DebitAccount command, - Account account, - IDocumentSession session, + DebitAccount command, + Account account, + IDocumentSession session, IMessageContext messaging) { account.Balance -= command.Amount; - + // This just marks the account as changed, but // doesn't actually commit changes to the database // yet. That actually matters as I hopefully explain @@ -142,12 +142,12 @@ public static async Task Handle( else if (account.Balance < 0) { await messaging.SendAsync(new AccountOverdrawn(account.Id), new DeliveryOptions{DeliverWithin = 1.Hours()}); - + // Give the customer 10 days to deal with the overdrawn account await messaging.ScheduleAsync(new EnforceAccountOverdrawnDeadline(account.Id), 10.Days()); } - - // "messaging" is a Wolverine IMessageContext or IMessageBus service + + // "messaging" is a Wolverine IMessageContext or IMessageBus service // Do the deliver within rule on individual messages await messaging.SendAsync(new AccountUpdated(account.Id, account.Balance), new DeliveryOptions { DeliverWithin = 5.Seconds() }); @@ -172,9 +172,9 @@ public class when_the_account_is_overdrawn : IAsyncLifetime MinimumThreshold = 100, Id = Guid.NewGuid() }; - + private readonly TestMessageContext theContext = new TestMessageContext(); - + // I happen to like NSubstitute for mocking or dynamic stubs private readonly IDocumentSession theDocumentSession = Substitute.For(); @@ -183,22 +183,22 @@ public class when_the_account_is_overdrawn : IAsyncLifetime var command = new DebitAccount(theAccount.Id, 1200); await DebitAccountHandler.Handle(command, theAccount, theDocumentSession, theContext); } - + [Fact] public void the_account_balance_should_be_negative() { theAccount.Balance.ShouldBe(-200); } - + [Fact] public void raises_an_account_overdrawn_message() { - // ShouldHaveMessageOfType() is an extension method in + // ShouldHaveMessageOfType() is an extension method in // Wolverine itself to facilitate unit testing assertions like this theContext.Sent.ShouldHaveMessageOfType() .AccountId.ShouldBe(theAccount.Id); } - + [Fact] public void raises_an_overdrawn_deadline_message_in_10_days() { @@ -208,14 +208,14 @@ public class when_the_account_is_overdrawn : IAsyncLifetime .ShouldHaveEnvelopeForMessageType() .ScheduleDelay.ShouldBe(10.Days()); } - + public Task DisposeAsync() { return Task.CompletedTask; } } ``` -snippet source | anchor +snippet source | anchor The `TestMessageContext` mostly just collects an array of objects that are sent, published, or scheduled. The @@ -258,7 +258,7 @@ spy.WhenInvokedMessageOf(endpointName:"incoming") var response3 = await context.EndpointFor("incoming") .InvokeAsync(new NumberRequest(5, 6)); ``` -snippet source | anchor +snippet source | anchor ## Stubbing All External Transports @@ -311,15 +311,15 @@ using var host = await Host.CreateDefaultBuilder() { // do whatever you need to configure Wolverine }) - + // Override the Wolverine configuration to disable all // external transports, broker connectivity, and incoming/outgoing // messages to run completely locally .ConfigureServices(services => services.DisableAllExternalWolverineTransports()) - + .StartAsync(); ``` -snippet source | anchor +snippet source | anchor Finally, to put that in a little more context about how you might go about using it @@ -337,11 +337,11 @@ and [WebApplicationFactory](https://learn.microsoft.com/en-us/aspnet/core/test/i // to do the actual bootstrapping await using var host = await AlbaHost.For(x => { - // I'm overriding + // I'm overriding x.ConfigureServices(services => services.DisableAllExternalWolverineTransports()); }); ``` -snippet source | anchor +snippet source | anchor In the sample above, I'm bootstrapping the `IHost` for my production application with @@ -406,7 +406,7 @@ public async Task using_tracked_sessions() overdrawn.AccountId.ShouldBe(debitAccount.AccountId); } ``` -snippet source | anchor +snippet source | anchor The tracked session mechanism utilizes Wolverine's internal instrumentation to "know" when all the outstanding @@ -434,29 +434,29 @@ public async Task using_tracked_sessions_advanced(IHost otherWolverineSystem) var debitAccount = new DebitAccount(111, 300); var session = await host - - // Start defining a tracked session + + // Start defining a tracked session .TrackActivity() - + // Override the timeout period for longer tests .Timeout(1.Minutes()) - + // Be careful with this one! This makes Wolverine wait on some indication // that messages sent externally are completed .IncludeExternalTransports() - + // Make the tracked session span across an IHost for another process // May not be super useful to the average user, but it's been crucial // to test Wolverine itself .AlsoTrack(otherWolverineSystem) - // This is actually helpful if you are testing for error handling + // This is actually helpful if you are testing for error handling // functionality in your system .DoNotAssertOnExceptionsDetected() - + // Again, this is testing against processes, with another IHost .WaitForMessageToBeReceivedAt(otherWolverineSystem) - + // There are many other options as well .InvokeMessageAndWaitAsync(debitAccount); @@ -464,6 +464,6 @@ public async Task using_tracked_sessions_advanced(IHost otherWolverineSystem) overdrawn.AccountId.ShouldBe(debitAccount.AccountId); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/tutorials/mediator.md b/docs/tutorials/mediator.md index 3cbe63956..d4e114b1d 100644 --- a/docs/tutorials/mediator.md +++ b/docs/tutorials/mediator.md @@ -23,20 +23,19 @@ using var host = await Host.CreateDefaultBuilder() { opts.Services.AddMarten("some connection string") - // This adds quite a bit of middleware for + // This adds quite a bit of middleware for // Marten .IntegrateWithWolverine(); - + // You want this maybe! opts.Policies.AutoApplyTransactions(); - - + // But wait! Optimize Wolverine for usage as *only* // a mediator opts.Durability.Mode = DurabilityMode.MediatorOnly; }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor The `MediatorOnly` mode sharply reduces the overhead of using Wolverine you don't care about or need if Wolverine is only @@ -75,7 +74,7 @@ var connectionString = builder.Configuration.GetConnectionString("SqlServer"); builder.Host.UseWolverine(opts => { opts.PersistMessagesWithSqlServer(connectionString); - + // If you're also using EF Core, you may want this as well opts.UseEntityFrameworkCoreTransactions(); }); diff --git a/docs/tutorials/middleware.md b/docs/tutorials/middleware.md index 10ec6b489..e55d590cc 100644 --- a/docs/tutorials/middleware.md +++ b/docs/tutorials/middleware.md @@ -19,7 +19,7 @@ public static async Task Handle(DebitAccount command, IDocumentSession session, logger.LogInformation("Referenced account {AccountId} does not exist", command.AccountId); return; } - + // do the real processing } ``` @@ -61,8 +61,8 @@ Skipping ahead a little bit, if we had a handler for the `CreditAccount` command public static class CreditAccountHandler { public static void Handle( - CreditAccount command, - + CreditAccount command, + // Wouldn't it be nice to just have Wolverine "push" // the right account into this method? Account account, @@ -71,8 +71,8 @@ public static class CreditAccountHandler IDocumentSession session) { account.Balance += command.Amount; - - // Just mark this account as needing to be updated + + // Just mark this account as needing to be updated // in the database session.Store(account); } @@ -98,12 +98,12 @@ public static class AccountLookupMiddleware // The message *has* to be first in the parameter list // Before or BeforeAsync tells Wolverine this method should be called before the actual action public static async Task<(HandlerContinuation, Account?)> LoadAsync( - IAccountCommand command, - ILogger logger, - + IAccountCommand command, + ILogger logger, + // This app is using Marten for persistence - IDocumentSession session, - + IDocumentSession session, + CancellationToken cancellation) { var account = await session.LoadAsync(command.AccountId, cancellation); @@ -111,7 +111,7 @@ public static class AccountLookupMiddleware { logger.LogInformation("Unable to find an account for {AccountId}, aborting the requested operation", command.AccountId); } - + return (account == null ? HandlerContinuation.Stop : HandlerContinuation.Continue, account); } } @@ -133,11 +133,11 @@ Lastly, let's apply the newly built middleware to only the message handlers that ```cs builder.Host.UseWolverine(opts => { - // This middleware should be applied to all handlers where the + // This middleware should be applied to all handlers where the // command type implements the IAccountCommand interface that is the // "detected" message type of the middleware opts.Policies.ForMessagesOfType().AddMiddleware(typeof(AccountLookupMiddleware)); - + opts.UseFluentValidation(); // Explicit routing for the AccountUpdated @@ -148,10 +148,10 @@ builder.Host.UseWolverine(opts => // Throw the message away if it's not successfully // delivered within 10 seconds .DeliverWithin(10.Seconds()) - + // Not durable .BufferedInMemory(); }); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/tutorials/serverless.md b/docs/tutorials/serverless.md index 4c78c34d4..4f8fd1564 100644 --- a/docs/tutorials/serverless.md +++ b/docs/tutorials/serverless.md @@ -35,21 +35,20 @@ using var host = await Host.CreateDefaultBuilder() { opts.Services.AddMarten("some connection string") - // This adds quite a bit of middleware for + // This adds quite a bit of middleware for // Marten .IntegrateWithWolverine(); - + // You want this maybe! opts.Policies.AutoApplyTransactions(); - - + // But wait! Optimize Wolverine for usage within Serverless // and turn off the heavy duty, background processes // for the transactional inbox/outbox opts.Durability.Mode = DurabilityMode.Serverless; }).StartAsync(); ``` -snippet source | anchor +snippet source | anchor ## Pre-Generate All Types @@ -73,7 +72,7 @@ endpoints: opts .PublishAllMessages() .ToRabbitQueue(queueName) - + // This option is important inside of Serverless functions .SendInline(); }) diff --git a/src/Testing/CoreTests/Configuration/BootstrappingTests.cs b/src/Testing/CoreTests/Configuration/bootstrapping_specs.cs similarity index 83% rename from src/Testing/CoreTests/Configuration/BootstrappingTests.cs rename to src/Testing/CoreTests/Configuration/bootstrapping_specs.cs index f6faf4a43..e7a0c6960 100644 --- a/src/Testing/CoreTests/Configuration/BootstrappingTests.cs +++ b/src/Testing/CoreTests/Configuration/bootstrapping_specs.cs @@ -1,6 +1,7 @@ using JasperFx.CodeGeneration; using JasperFx.CodeGeneration.Frames; using JasperFx.CodeGeneration.Model; +using JasperFx.Core.Reflection; using Lamar; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -16,9 +17,9 @@ namespace CoreTests.Configuration; -public class BootstrappingTests : IntegrationContext +public class bootstrapping_specs : IntegrationContext { - public BootstrappingTests(DefaultApp @default) : base(@default) + public bootstrapping_specs(DefaultApp @default) : base(@default) { } @@ -68,6 +69,30 @@ public void can_customize_source_code_generation() .ShouldHaveHandler(x => x.Handle(null, null)); } + [Fact] + public async Task bootstrap_with_extension_finding_disabled() + { + #region sample_disabling_assembly_scanning + + using var host = await Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + opts.DisableConventionalDiscovery(); + }) + + // Note that this isn't part of UseWolverine() + .DisableWolverineExtensionScanning() + + .StartAsync(); + + #endregion + + var container = host.Services.As(); + + // IModuleService would be registered by the Module1Extension + container.Model.HasRegistrationFor().ShouldBeFalse(); + } + [Fact] public void application_service_registrations_win() { diff --git a/src/Wolverine/HostBuilderExtensions.cs b/src/Wolverine/HostBuilderExtensions.cs index 6365a9e4c..3ff02a1aa 100644 --- a/src/Wolverine/HostBuilderExtensions.cs +++ b/src/Wolverine/HostBuilderExtensions.cs @@ -21,6 +21,8 @@ namespace Wolverine; public static class HostBuilderExtensions { + internal static readonly string ExtensionScanningKey = "ExtensionScanning"; + /// /// Add Wolverine to an ASP.Net Core application with optional configuration to Wolverine /// @@ -33,6 +35,18 @@ public static IHostBuilder UseWolverine(this IHostBuilder builder, return builder.UseWolverine(new WolverineOptions(), overrides); } + /// + /// Use this to disable the automatic assembly scanning for Wolverine extensions + /// that can cause issues in specific Docker configurations + /// + /// + /// + public static IHostBuilder DisableWolverineExtensionScanning(this IHostBuilder builder) + { + builder.Properties[ExtensionScanningKey] = "disabled"; + return builder; + } + /// /// Add Wolverine to an ASP.Net Core application with optional configuration to Wolverine /// @@ -158,7 +172,7 @@ internal static IHostBuilder UseWolverine(this IHostBuilder builder, WolverineOp options.Services.InsertRange(0, services); - if (!options.DisableAssemblyScanForModules) + if (!context.Properties.ContainsKey(ExtensionScanningKey)) { ExtensionLoader.ApplyExtensions(options); } diff --git a/src/Wolverine/WolverineOptions.Extensions.cs b/src/Wolverine/WolverineOptions.Extensions.cs index 3a9aef1d9..6663fd982 100644 --- a/src/Wolverine/WolverineOptions.Extensions.cs +++ b/src/Wolverine/WolverineOptions.Extensions.cs @@ -7,6 +7,7 @@ public sealed partial class WolverineOptions private readonly IList _extensionTypes = new List(); internal List AppliedExtensions { get; } = []; + /// /// Applies the extension to this application /// diff --git a/src/Wolverine/WolverineOptions.cs b/src/Wolverine/WolverineOptions.cs index d1b4468a4..39d2a5084 100644 --- a/src/Wolverine/WolverineOptions.cs +++ b/src/Wolverine/WolverineOptions.cs @@ -107,11 +107,6 @@ public WolverineOptions(string? assemblyName) /// application start /// public bool AutoBuildMessageStorageOnStartup { get; set; } = true; - - /// - /// Direct Wolverine to not scan assemblies for modules that are marked by attribute - /// - public bool DisableAssemblyScanForModules { get; set; } internal TypeLoadMode ProductionTypeLoadMode { get; set; }