diff --git a/docs/guide/durability/dead-letter-storage.md b/docs/guide/durability/dead-letter-storage.md index 01795ff7c..db097a4c9 100644 --- a/docs/guide/durability/dead-letter-storage.md +++ b/docs/guide/durability/dead-letter-storage.md @@ -50,7 +50,7 @@ app.MapDeadLettersEndpoints() ; ``` -snippet source | anchor +snippet source | anchor ### Using the Dead Letters REST API diff --git a/docs/guide/durability/marten/event-sourcing.md b/docs/guide/durability/marten/event-sourcing.md index 772251b34..bbf77250c 100644 --- a/docs/guide/durability/marten/event-sourcing.md +++ b/docs/guide/durability/marten/event-sourcing.md @@ -267,7 +267,7 @@ public static void Handle(OrderEventSourcingSample.MarkItemReady command, IEvent } } ``` -snippet source | anchor +snippet source | anchor Just as in other Wolverine [message handlers](/guide/handlers/), you can use @@ -365,9 +365,116 @@ public class MarkItemReady public string ItemName { get; init; } } ``` -snippet source | anchor +snippet source | anchor ## Forwarding Events See [Event Forwarding](./event-forwarding) for more information. + +## Returning the Updated Aggregate + +A common use case for the "aggregate handler workflow" has been to respond with the now updated state of the projected +aggregate that has just been updated by appending new events. Until now, that's effectively meant making a completely separate +call to the database through Marten to retrieve the latest updates. + +::: info +To understand more about the inner workings of the next section, see the Marten documentation on its [FetchLatest](https://martendb.io/events/projections/read-aggregates.html#fetchlatest) +API. +::: + +As a quick tip for performance, assuming that you are *not* mutating the projected documents within your command +handlers, you can opt for this significant Marten optimization to eliminate extra database round trips while +using the aggregate handler workflow: + +```csharp +builder.Services.AddMarten(opts => +{ + // Other Marten configuration + + // Use this setting to get the very best performance out + // of the UpdatedAggregate workflow and aggregate handler + // workflow over all + opts.Events.UseIdentityMapForAggregates = true; +}).IntegrateWithWolverine(); +``` + +::: info +The setting above cannot be a default in Marten because it can break some existing code with a very different +workflow that what the Critter Stack team recommends for the aggregate handler workflow. +::: + +Wolverine.Marten has a special response type for message handlers or HTTP endpoints we can use as a directive to tell Wolverine +to respond with the latest state of a projected aggregate as part of the command execution. Let's make this concrete by +taking the `MarkItemReady` command handler we've used earlier in this guide and building a slightly new version that +produces a response of the latest aggregate: + + + +```cs +[AggregateHandler] +public static ( + // Just tells Wolverine to use Marten's FetchLatest API to respond with + // the updated version of Order that reflects whatever events were appended + // in this command + UpdatedAggregate, + + // The events that should be appended to the event stream for this order + Events) Handle(OrderEventSourcingSample.MarkItemReady command, Order order) +{ + var events = new Events(); + + if (order.Items.TryGetValue(command.ItemName, out var item)) + { + // Not doing this in a purist way here, but just + // trying to illustrate the Wolverine mechanics + item.Ready = true; + + // Mark that the this item is ready + events.Add(new ItemReady(command.ItemName)); + } + else + { + // Some crude validation + throw new InvalidOperationException($"Item {command.ItemName} does not exist in this order"); + } + + // If the order is ready to ship, also emit an OrderReady event + if (order.IsReadyToShip()) + { + events.Add(new OrderReady()); + } + + return (new UpdatedAggregate(), events); +} +``` +snippet source | anchor + + +Note the usage of the `Wolverine.Marten.UpdatedAggregate` response in the handler. That type by itself is just a directive +to Wolverine to generate the necessary code to call `FetchLatest` and respond with that. The command handler above allows +us to use the command in a mediator usage like so: + + + +```cs +public static Task update_and_get_latest(IMessageBus bus, MarkItemReady command) +{ + // This will return the updated version of the Order + // aggregate that incorporates whatever events were appended + // in the course of processing the command + return bus.InvokeAsync(command); +} +``` +snippet source | anchor + + +Likewise, you can use `UpdatedAggregate` as the response body of an HTTP endpoint with Wolverine.HTTP [as shown here](/guide/http/marten.html#responding-with-the-updated-aggregate~~~~). + +::: info +This feature has been more or less requested several times, but was finally brought about because of the need +to consume Wolverine + Marten commands within Hot Chocolate mutations and always return the current state of +the projected aggregate being updated to the user interface. +::: + + diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index bd957da5e..8b845e1e9 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -242,7 +242,7 @@ var app = builder.Build(); // you will need to explicitly call this *before* MapWolverineEndpoints() await app.Services.ApplyAsyncWolverineExtensions(); ``` -snippet source | anchor +snippet source | anchor ## Wolverine Plugin Modules diff --git a/docs/guide/http/endpoints.md b/docs/guide/http/endpoints.md index 896d003bc..bbea021f1 100644 --- a/docs/guide/http/endpoints.md +++ b/docs/guide/http/endpoints.md @@ -167,7 +167,7 @@ public static OrderShipped Ship(ShipOrder command, Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor ## JSON Handling @@ -312,7 +312,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 9463644ae..05841c155 100644 --- a/docs/guide/http/fluentvalidation.md +++ b/docs/guide/http/fluentvalidation.md @@ -44,5 +44,5 @@ app.MapWolverineEndpoints(opts => // Wolverine.Http.FluentValidation opts.UseFluentValidationProblemDetailMiddleware(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guide/http/marten.md b/docs/guide/http/marten.md index 6cb41907b..370fcf67b 100644 --- a/docs/guide/http/marten.md +++ b/docs/guide/http/marten.md @@ -124,7 +124,7 @@ public static OrderShipped Ship(ShipOrder2 command, [Aggregate] Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor Using this version of the "aggregate workflow", you no longer have to supply a command in the request body, so you could @@ -143,7 +143,7 @@ public static OrderShipped Ship3([Aggregate] Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor A couple other notes: @@ -189,6 +189,9 @@ public class Item public class Order { + // For JSON serialization + public Order(){} + public Order(OrderCreated created) { foreach (var item in created.Items) Items[item.Name] = item; @@ -218,6 +221,13 @@ public class Order Items[ready.Name].Ready = true; } + public void Apply(OrderConfirmed confirmed) + { + IsConfirmed = true; + } + + public bool IsConfirmed { get; set; } + public bool IsReadyToShip() { return Shipped == null && Items.Values.All(x => x.Ready); @@ -226,7 +236,7 @@ public class Order public bool IsShipped() => Shipped.HasValue; } ``` -snippet source | anchor +snippet source | anchor To append a single event to an event stream from an HTTP endpoint, you can use a return value like so: @@ -245,7 +255,7 @@ public static OrderShipped Ship(ShipOrder command, Order order) return new OrderShipped(); } ``` -snippet source | anchor +snippet source | anchor Or potentially append multiple events using the `Events` type as a return value like this sample: @@ -281,7 +291,33 @@ public static (OrderStatus, Events) Post(MarkItemReady command, Order order) return (new OrderStatus(order.Id, order.IsReadyToShip()), events); } ``` -snippet source | anchor +snippet source | anchor + + +### Responding with the Updated Aggregate + +See the documentation from the message handlers on using [UpdatedAggregate](/guide/durability/marten/event-sourcing.html#returning-the-updated-aggregate) for more background on this topic. + +To return the updated state of a projected aggregate from Marten as the HTTP response from an endpoint using +the aggregate handler workflow, return the `UpdatedAggregate` marker type as the first "response value" of +your HTTP endpoint like so: + + + +```cs +[AggregateHandler] +[WolverinePost("/orders/{id}/confirm2")] +// The updated version of the Order aggregate will be returned as the response body +// from requesting this endpoint at runtime +public static (UpdatedAggregate, Events) ConfirmDifferent(ConfirmOrder command, Order order) +{ + return ( + new UpdatedAggregate(), + [new OrderConfirmed()] + ); +} +``` +snippet source | anchor ### Compiled Query Resource Writer Policy @@ -294,7 +330,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 d1b771237..0204ed574 100644 --- a/docs/guide/http/mediator.md +++ b/docs/guide/http/mediator.md @@ -45,7 +45,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 d6534b362..e055cd1ea 100644 --- a/docs/guide/http/metadata.md +++ b/docs/guide/http/metadata.md @@ -97,7 +97,7 @@ builder.Services.AddSwaggerGen(x => x.OperationFilter(); }); ``` -snippet source | anchor +snippet source | anchor ## Operation Id diff --git a/docs/guide/http/middleware.md b/docs/guide/http/middleware.md index ea6bfddc2..97e4cac67 100644 --- a/docs/guide/http/middleware.md +++ b/docs/guide/http/middleware.md @@ -49,7 +49,7 @@ Which is registered like this (or as described in [`Registering Middleware by Me opts.AddMiddlewareByMessageType(typeof(FakeAuthenticationMiddleware)); opts.AddMiddlewareByMessageType(typeof(CanShipOrderMiddleWare)); ``` -snippet source | anchor +snippet source | anchor The key point to notice there is that `IResult` is a "return value" of the middleware. In the case of an HTTP endpoint, diff --git a/docs/guide/http/policies.md b/docs/guide/http/policies.md index 88769f3ac..c51721946 100644 --- a/docs/guide/http/policies.md +++ b/docs/guide/http/policies.md @@ -65,7 +65,7 @@ app.MapWolverineEndpoints(opts => // 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 @@ -97,7 +97,7 @@ app.MapWolverineEndpoints(opts => // 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 1b8456f28..b839abe13 100644 --- a/docs/guide/http/problemdetails.md +++ b/docs/guide/http/problemdetails.md @@ -137,7 +137,7 @@ public static ProblemDetails Before(IShipOrder command, Order order) return WolverineContinue.NoProblems; } ``` -snippet source | anchor +snippet source | anchor ## Within Message Handlers diff --git a/docs/guide/messaging/transports/azureservicebus/interoperability.md b/docs/guide/messaging/transports/azureservicebus/interoperability.md index fa2fc493f..4d695b3b1 100644 --- a/docs/guide/messaging/transports/azureservicebus/interoperability.md +++ b/docs/guide/messaging/transports/azureservicebus/interoperability.md @@ -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/multi-tenancy.md b/docs/guide/messaging/transports/azureservicebus/multi-tenancy.md index 1b3f5b968..6e7a7b9bd 100644 --- a/docs/guide/messaging/transports/azureservicebus/multi-tenancy.md +++ b/docs/guide/messaging/transports/azureservicebus/multi-tenancy.md @@ -13,7 +13,68 @@ Wolverine tracks the tenant id across messages. Let's just jump straight into a simple example of the configuration: -snippet: sample_configuring_azure_service_bus_for_multi_tenancy + + +```cs +var builder = Host.CreateApplicationBuilder(); + +builder.UseWolverine(opts => +{ + // One way or another, you're probably pulling the Azure Service Bus + // connection string out of configuration + var azureServiceBusConnectionString = builder + .Configuration + .GetConnectionString("azure-service-bus"); + + // Connect to the broker in the simplest possible way + opts.UseAzureServiceBus(azureServiceBusConnectionString) + + // This is the default, if there is no tenant id on an outgoing message, + // use the default broker + .TenantIdBehavior(TenantedIdBehavior.FallbackToDefault) + + // Or tell Wolverine instead to just quietly ignore messages sent + // to unrecognized tenant ids + .TenantIdBehavior(TenantedIdBehavior.IgnoreUnknownTenants) + + // Or be draconian and make Wolverine assert and throw an exception + // if an outgoing message does not have a tenant id + .TenantIdBehavior(TenantedIdBehavior.TenantIdRequired) + + // Add new tenants by registering the tenant id and a separate fully qualified namespace + // to a different Azure Service Bus connection + .AddTenantByNamespace("one", builder.Configuration.GetValue("asb_ns_one")) + .AddTenantByNamespace("two", builder.Configuration.GetValue("asb_ns_two")) + .AddTenantByNamespace("three", builder.Configuration.GetValue("asb_ns_three")) + + // OR, instead, add tenants by registering the tenant id and a separate connection string + // to a different Azure Service Bus connection + .AddTenantByConnectionString("four", builder.Configuration.GetConnectionString("asb_four")) + .AddTenantByConnectionString("five", builder.Configuration.GetConnectionString("asb_five")) + .AddTenantByConnectionString("six", builder.Configuration.GetConnectionString("asb_six")); + + // This Wolverine application would be listening to a queue + // named "incoming" on all Azure Service Bus connections, including the default + opts.ListenToAzureServiceBusQueue("incoming"); + + // This Wolverine application would listen to a single queue + // at the default connection regardless of tenant + opts.ListenToAzureServiceBusQueue("incoming_global") + .GlobalListener(); + + // Likewise, you can override the queue, subscription, and topic behavior + // to be "global" for all tenants with this syntax: + opts.PublishMessage() + .ToAzureServiceBusQueue("message1") + .GlobalSender(); + + opts.PublishMessage() + .ToAzureServiceBusTopic("message2") + .GlobalSender(); +}); +``` +snippet source | anchor + ::: warning Wolverine has no way of creating new Azure Service Bus namespaces for you @@ -38,7 +99,7 @@ public static async Task send_message_to_specific_tenant(IMessageBus bus) await bus.PublishAsync(new Message1(), new DeliveryOptions { TenantId = "two" }); } ``` -snippet source | anchor +snippet source | anchor In the case above, in the Wolverine internals, it: diff --git a/docs/guide/messaging/transports/azureservicebus/object-management.md b/docs/guide/messaging/transports/azureservicebus/object-management.md index 880aed4e4..c39202df3 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: @@ -35,7 +35,7 @@ using var host = await Host.CreateDefaultBuilder() .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 diff --git a/docs/guide/messaging/transports/azureservicebus/topics.md b/docs/guide/messaging/transports/azureservicebus/topics.md index de2ca4962..f34de0bc0 100644 --- a/docs/guide/messaging/transports/azureservicebus/topics.md +++ b/docs/guide/messaging/transports/azureservicebus/topics.md @@ -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 diff --git a/docs/guide/messaging/transports/rabbitmq/multi-tenancy.md b/docs/guide/messaging/transports/rabbitmq/multi-tenancy.md index 752ee7bb9..f7c6a7fb3 100644 --- a/docs/guide/messaging/transports/rabbitmq/multi-tenancy.md +++ b/docs/guide/messaging/transports/rabbitmq/multi-tenancy.md @@ -55,12 +55,19 @@ builder.UseWolverine(opts => // brokers opts.ListenToRabbitQueue("incoming"); + opts.ListenToRabbitQueue("incoming_global") + + // This opts this queue out from being per-tenant, such that + // there will only be the single "incoming_global" queue for the default + // broker connection + .GlobalListener(); + // More on this in the docs.... opts.PublishMessage() - .ToRabbitQueue("outgoing"); + .ToRabbitQueue("outgoing").GlobalSender(); }); ``` -snippet source | anchor +snippet source | anchor ::: warning @@ -83,7 +90,7 @@ public static async Task send_message_to_specific_tenant(IMessageBus bus) await bus.PublishAsync(new Message1(), new DeliveryOptions { TenantId = "two" }); } ``` -snippet source | anchor +snippet source | anchor In the case above, in the Wolverine internals, it: diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 293e563f3..16a1e429f 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -114,7 +114,7 @@ var builder = WebApplication.CreateBuilder(args); // will assert this is missing on startup:( builder.Services.AddWolverineHttp(); ``` -snippet source | anchor +snippet source | anchor Also for Wolverine.Http users, the `[Document]` attribute behavior in the Marten integration is now "required by default." diff --git a/src/Http/Wolverine.Http.Marten/AggregateAttribute.cs b/src/Http/Wolverine.Http.Marten/AggregateAttribute.cs index c44c44114..c6d5f0900 100644 --- a/src/Http/Wolverine.Http.Marten/AggregateAttribute.cs +++ b/src/Http/Wolverine.Http.Marten/AggregateAttribute.cs @@ -65,6 +65,10 @@ public override Variable Modify(HttpChain chain, ParameterInfo parameter, IServi throw new InvalidOperationException( "Cannot determine an identity variable for this aggregate from the route arguments"); } + + // Store information about the aggregate handling in the chain just in + // case they're using LatestAggregate + new AggregateHandling(AggregateType, IdVariable).Store(chain); VersionVariable = findVersionVariable(chain); CommandType = chain.InputType(); diff --git a/src/Http/Wolverine.Http.Tests/Marten/using_aggregate_handler_workflow.cs b/src/Http/Wolverine.Http.Tests/Marten/using_aggregate_handler_workflow.cs index 964142db1..1ea723414 100644 --- a/src/Http/Wolverine.Http.Tests/Marten/using_aggregate_handler_workflow.cs +++ b/src/Http/Wolverine.Http.Tests/Marten/using_aggregate_handler_workflow.cs @@ -224,4 +224,27 @@ public async Task accept_response_returns_proper_status_and_url() acceptResponse.ShouldNotBeNull(); acceptResponse.Url.ShouldBe($"/orders/{status.OrderId}"); } + + [Fact] + public async Task return_updated_aggregate() + { + var result = await Scenario(x => + { + x.Post.Json(new StartOrder(["Socks", "Shoes", "Shirt"])).ToUrl("/orders/create"); + }); + + var status = result.ReadAsJson(); + status.ShouldNotBeNull(); + + result = await Scenario(x => + { + x.Post.Json(new ConfirmOrder(status.OrderId)).ToUrl($"/orders/{status.OrderId}/confirm2"); + + x.StatusCodeShouldBe(200); + }); + + var order = await result.ReadAsJsonAsync(); + order.IsConfirmed.ShouldBeTrue(); + + } } \ No newline at end of file diff --git a/src/Http/Wolverine.Http/HttpChain.ApiDescription.cs b/src/Http/Wolverine.Http/HttpChain.ApiDescription.cs index 8e536409d..59b780853 100644 --- a/src/Http/Wolverine.Http/HttpChain.ApiDescription.cs +++ b/src/Http/Wolverine.Http/HttpChain.ApiDescription.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Reflection; +using JasperFx.CodeGeneration.Frames; using JasperFx.Core.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Metadata; @@ -119,6 +120,18 @@ public ApiDescription CreateApiDescription(string httpMethod) return apiDescription; } + public override void UseForResponse(MethodCall methodCall) + { + if (methodCall.ReturnVariable == null) + throw new ArgumentOutOfRangeException(nameof(methodCall), + $"Method {methodCall} is invalid in this usage. Only a method that returns a single value (not tuples) can be used here."); + + ResourceType = methodCall.ReturnVariable.VariableType; + + Postprocessors.Add(methodCall); + ResourceVariable = methodCall.ReturnVariable; + } + private sealed record NormalizedResponseMetadata(int StatusCode, Type? Type, IEnumerable ContentTypes) { // if an attribute doesn't specific the content type, conform with OpenAPI internals and infer. diff --git a/src/Http/Wolverine.Http/HttpChain.cs b/src/Http/Wolverine.Http/HttpChain.cs index d1f58880b..8693b4ca2 100644 --- a/src/Http/Wolverine.Http/HttpChain.cs +++ b/src/Http/Wolverine.Http/HttpChain.cs @@ -60,6 +60,12 @@ public static bool IsValidResponseType(Type type) private readonly List _querystringVariables = []; public string OperationId { get; set; } + + /// + /// This may be overridden by some IResponseAware policies in place of the first + /// create variable of the method call + /// + public Variable? ResourceVariable { get; set; } // Make the assumption that the route argument has to match the parameter name private GeneratedType? _generatedType; @@ -157,7 +163,7 @@ private bool tryFindResourceType(MethodCall method, out Type resourceType) public IEnumerable HttpMethods => _httpMethods; - public Type? ResourceType { get; } + public Type? ResourceType { get; private set; } internal void MapToRoute(string method, string url, int? order = null, string? displayName = null) { diff --git a/src/Http/Wolverine.Http/Resources/JsonResourceWriterPolicy.cs b/src/Http/Wolverine.Http/Resources/JsonResourceWriterPolicy.cs index ee0c31da0..95b04ef1a 100644 --- a/src/Http/Wolverine.Http/Resources/JsonResourceWriterPolicy.cs +++ b/src/Http/Wolverine.Http/Resources/JsonResourceWriterPolicy.cs @@ -9,7 +9,7 @@ public bool TryApply(HttpChain chain) { if (chain.HasResourceType()) { - var resourceVariable = chain.Method.Creates.First(); + var resourceVariable = chain.ResourceVariable ?? chain.Method.Creates.First(); resourceVariable.OverrideName(resourceVariable.Usage + "_response"); if (Usage == JsonUsage.SystemTextJson) diff --git a/src/Http/WolverineWebApi/Marten/Orders.cs b/src/Http/WolverineWebApi/Marten/Orders.cs index aba1a07bf..fb49d83be 100644 --- a/src/Http/WolverineWebApi/Marten/Orders.cs +++ b/src/Http/WolverineWebApi/Marten/Orders.cs @@ -33,6 +33,9 @@ public class Item public class Order { + // For JSON serialization + public Order(){} + public Order(OrderCreated created) { foreach (var item in created.Items) Items[item.Name] = item; @@ -62,6 +65,13 @@ public void Apply(ItemReady ready) Items[ready.Name].Ready = true; } + public void Apply(OrderConfirmed confirmed) + { + IsConfirmed = true; + } + + public bool IsConfirmed { get; set; } + public bool IsReadyToShip() { return Shipped == null && Items.Values.All(x => x.Ready); @@ -254,4 +264,20 @@ public static (AcceptResponse, Events) Confirm(ConfirmOrder command, Order order [new OrderConfirmed()] ); } + + #region sample_returning_updated_aggregate_as_response_from_http_endpoint + + [AggregateHandler] + [WolverinePost("/orders/{id}/confirm2")] + // The updated version of the Order aggregate will be returned as the response body + // from requesting this endpoint at runtime + public static (UpdatedAggregate, Events) ConfirmDifferent(ConfirmOrder command, Order order) + { + return ( + new UpdatedAggregate(), + [new OrderConfirmed()] + ); + } + + #endregion } diff --git a/src/Http/WolverineWebApi/Program.cs b/src/Http/WolverineWebApi/Program.cs index bd9071919..0e6ae139a 100644 --- a/src/Http/WolverineWebApi/Program.cs +++ b/src/Http/WolverineWebApi/Program.cs @@ -21,7 +21,6 @@ using WolverineWebApi.Marten; using WolverineWebApi.Samples; using WolverineWebApi.WebSockets; -using Order = WolverineWebApi.Order; #region sample_adding_http_services @@ -63,6 +62,11 @@ opts.Connection(Servers.PostgresConnectionString); opts.DatabaseSchemaName = "http"; opts.DisableNpgsqlLogging = true; + + // Use this setting to get the very best performance out + // of the UpdatedAggregate workflow and aggregate handler + // workflow over all + opts.Events.UseIdentityMapForAggregates = true; }).IntegrateWithWolverine(); @@ -128,11 +132,11 @@ OpenApiEndpoints.BuildComparisonRoutes(app); -app.MapGet("/orders/{orderId}", [Authorize] Results>(int orderId) - => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId))); +app.MapGet("/orders/{orderId}", [Authorize] Results>(int orderId) + => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new TinyOrder(orderId))); -app.MapPost("/orders", Results>(CreateOrder command) - => command.OrderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(command.OrderId))); +app.MapPost("/orders", Results>(CreateOrder command) + => command.OrderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new TinyOrder(command.OrderId))); app.MapHub("/updates"); diff --git a/src/Http/WolverineWebApi/Order.cs b/src/Http/WolverineWebApi/TinyOrder.cs similarity index 66% rename from src/Http/WolverineWebApi/Order.cs rename to src/Http/WolverineWebApi/TinyOrder.cs index 52c667dc5..bf9e204db 100644 --- a/src/Http/WolverineWebApi/Order.cs +++ b/src/Http/WolverineWebApi/TinyOrder.cs @@ -1,13 +1,13 @@ namespace WolverineWebApi; -public class Order +public class TinyOrder { - public Order(int orderId) + public TinyOrder(int orderId) { OrderId = orderId; } - public Order() + public TinyOrder() { } diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Event3Handler1609469393.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Event3Handler1609469393.cs deleted file mode 100644 index 4a2bb6d6e..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Event3Handler1609469393.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: Event3Handler1609469393 - public class Event3Handler1609469393 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public Event3Handler1609469393(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var event3 = (MartenTests.Event3)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(event3.AggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - var outgoing1 = MartenTests.FooHandler.Handle(event3, eventStream.Aggregate); - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing1).ConfigureAwait(false); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: Event3Handler1609469393 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementA2Handler79726078.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementA2Handler79726078.cs deleted file mode 100644 index 2f974ebd3..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementA2Handler79726078.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -#pragma warning disable -using Microsoft.Extensions.Logging; -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementA2Handler79726078 - public class IncrementA2Handler79726078 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Microsoft.Extensions.Logging.ILogger _logger; - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementA2Handler79726078(Microsoft.Extensions.Logging.ILogger logger, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _logger = logger; - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementA2 = (MartenTests.IncrementA2)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementA2.SelfLetteredAggregateId, cancellation).ConfigureAwait(false); - - if (eventStream.Aggregate == null) throw new Wolverine.Marten.UnknownAggregateException(typeof(MartenTests.SelfLetteredAggregate), incrementA2.SelfLetteredAggregateId); - var selfLetteredAggregate = new MartenTests.SelfLetteredAggregate(); - - // The actual message execution - var outgoing1 = eventStream.Aggregate.Handle(incrementA2, _logger); - - eventStream.AppendOne(outgoing1); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementA2Handler79726078 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementABHandler79726094.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementABHandler79726094.cs deleted file mode 100644 index 68bd36e02..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementABHandler79726094.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementABHandler79726094 - public class IncrementABHandler79726094 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementABHandler79726094(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementAB = (MartenTests.IncrementAB)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForExclusiveWriting(incrementAB.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - var outgoing1 = MartenTests.SpecialLetterHandler.Handle(incrementAB, eventStream.Aggregate); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementABHandler79726094 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementAHandler1658474384.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementAHandler1658474384.cs deleted file mode 100644 index cac875b16..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementAHandler1658474384.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementAHandler1658474384 - public class IncrementAHandler1658474384 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementAHandler1658474384(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementA = (MartenTests.IncrementA)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementA.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - var outgoing1 = letterAggregateHandler.Handle(incrementA, eventStream.Aggregate, documentSession); - - eventStream.AppendOne(outgoing1); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementAHandler1658474384 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementB2Handler483010605.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementB2Handler483010605.cs deleted file mode 100644 index a34e88b29..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementB2Handler483010605.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementB2Handler483010605 - public class IncrementB2Handler483010605 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementB2Handler483010605(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementB2 = (MartenTests.IncrementB2)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementB2.SelfLetteredAggregateId, cancellation).ConfigureAwait(false); - - if (eventStream.Aggregate == null) throw new Wolverine.Marten.UnknownAggregateException(typeof(MartenTests.SelfLetteredAggregate), incrementB2.SelfLetteredAggregateId); - var selfLetteredAggregate = new MartenTests.SelfLetteredAggregate(); - - // The actual message execution - var outgoing1 = await eventStream.Aggregate.Handle(incrementB2).ConfigureAwait(false); - - eventStream.AppendOne(outgoing1); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementB2Handler483010605 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBCHandler483010622.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBCHandler483010622.cs deleted file mode 100644 index a98cdec9b..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBCHandler483010622.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementBCHandler483010622 - public class IncrementBCHandler483010622 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementBCHandler483010622(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementBC = (MartenTests.IncrementBC)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementBC.LetterAggregateId, incrementBC.Version, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - var outgoing1 = letterAggregateHandler.Handle(incrementBC, eventStream.Aggregate); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementBCHandler483010622 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBHandler1255189857.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBHandler1255189857.cs deleted file mode 100644 index 747c35175..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementBHandler1255189857.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -#pragma warning disable -using Microsoft.Extensions.Logging; -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementBHandler1255189857 - public class IncrementBHandler1255189857 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Microsoft.Extensions.Logging.ILogger _logger; - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementBHandler1255189857(Microsoft.Extensions.Logging.ILogger logger, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _logger = logger; - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementB = (MartenTests.IncrementB)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementB.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - var outgoing1 = await letterAggregateHandler.Handle(incrementB, eventStream.Aggregate, _logger).ConfigureAwait(false); - - eventStream.AppendOne(outgoing1); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementBHandler1255189857 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCDHandler1083073314.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCDHandler1083073314.cs deleted file mode 100644 index ae02a28e6..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCDHandler1083073314.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementCDHandler1083073314 - public class IncrementCDHandler1083073314 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementCDHandler1083073314(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementCD = (MartenTests.IncrementCD)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementCD.LetterAggregateId, incrementCD.Version, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - (var outgoing1, var outgoing2) = letterAggregateHandler.Handle(incrementCD, eventStream.Aggregate); - - eventStream.AppendOne(outgoing1); - eventStream.AppendOne(outgoing2); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementCDHandler1083073314 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCHandler1473693498.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCHandler1473693498.cs deleted file mode 100644 index 5e6254aec..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementCHandler1473693498.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementCHandler1473693498 - public class IncrementCHandler1473693498 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementCHandler1473693498(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementC = (MartenTests.IncrementC)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementC.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - letterAggregateHandler.Handle(incrementC, eventStream); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementCHandler1473693498 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementDHandler1876978025.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementDHandler1876978025.cs deleted file mode 100644 index 8c6fed298..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementDHandler1876978025.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementDHandler1876978025 - public class IncrementDHandler1876978025 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementDHandler1876978025(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementD = (MartenTests.IncrementD)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementD.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - await letterAggregateHandler.Handle(incrementD, eventStream).ConfigureAwait(false); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementDHandler1876978025 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementMany2Handler448896552.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementMany2Handler448896552.cs deleted file mode 100644 index f0881e745..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementMany2Handler448896552.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementMany2Handler448896552 - public class IncrementMany2Handler448896552 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementMany2Handler448896552(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementMany2 = (MartenTests.IncrementMany2)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementMany2.SelfLetteredAggregateId, cancellation).ConfigureAwait(false); - - if (eventStream.Aggregate == null) throw new Wolverine.Marten.UnknownAggregateException(typeof(MartenTests.SelfLetteredAggregate), incrementMany2.SelfLetteredAggregateId); - var selfLetteredAggregate = new MartenTests.SelfLetteredAggregate(); - - // The actual message execution - var outgoing1 = eventStream.Aggregate.Handle(incrementMany2); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementMany2Handler448896552 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyAsyncHandler2038967698.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyAsyncHandler2038967698.cs deleted file mode 100644 index e8e5f7ef5..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyAsyncHandler2038967698.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementManyAsyncHandler2038967698 - public class IncrementManyAsyncHandler2038967698 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementManyAsyncHandler2038967698(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementManyAsync = (MartenTests.IncrementManyAsync)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementManyAsync.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - var outgoing1 = await letterAggregateHandler.Handle(incrementManyAsync, eventStream.Aggregate, documentSession).ConfigureAwait(false); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementManyAsyncHandler2038967698 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyHandler1569177634.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyHandler1569177634.cs deleted file mode 100644 index 2fda2667c..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/IncrementManyHandler1569177634.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: IncrementManyHandler1569177634 - public class IncrementManyHandler1569177634 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public IncrementManyHandler1569177634(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var incrementMany = (MartenTests.IncrementMany)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(incrementMany.LetterAggregateId, cancellation).ConfigureAwait(false); - - var letterAggregateHandler = new MartenTests.LetterAggregateHandler(); - - // The actual message execution - var outgoing1 = letterAggregateHandler.Handle(incrementMany, eventStream.Aggregate, documentSession); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: IncrementManyHandler1569177634 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage1Handler726704086.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage1Handler726704086.cs deleted file mode 100644 index 91b56d1f1..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage1Handler726704086.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -#pragma warning disable - -namespace Internal.Generated.WolverineHandlers -{ - // START: LetterMessage1Handler726704086 - public class LetterMessage1Handler726704086 : Wolverine.Runtime.Handlers.MessageHandler - { - - - public override System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var letterMessage1 = (MartenTests.LetterMessage1)context.Envelope.Message; - - - // The actual message execution - MartenTests.ResponseHandler.Handle(letterMessage1); - - return System.Threading.Tasks.Task.CompletedTask; - } - - } - - // END: LetterMessage1Handler726704086 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage2Handler839379855.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage2Handler839379855.cs deleted file mode 100644 index 6b50ecd97..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/LetterMessage2Handler839379855.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -#pragma warning disable - -namespace Internal.Generated.WolverineHandlers -{ - // START: LetterMessage2Handler839379855 - public class LetterMessage2Handler839379855 : Wolverine.Runtime.Handlers.MessageHandler - { - - - public override System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var letterMessage2 = (MartenTests.LetterMessage2)context.Envelope.Message; - - - // The actual message execution - MartenTests.ResponseHandler.Handle(letterMessage2); - - return System.Threading.Tasks.Task.CompletedTask; - } - - } - - // END: LetterMessage2Handler839379855 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Outgoing1Handler1264108911.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Outgoing1Handler1264108911.cs deleted file mode 100644 index 76d510674..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/Outgoing1Handler1264108911.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -#pragma warning disable - -namespace Internal.Generated.WolverineHandlers -{ - // START: Outgoing1Handler1264108911 - public class Outgoing1Handler1264108911 : Wolverine.Runtime.Handlers.MessageHandler - { - - - public override System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var outgoing1 = (MartenTests.Outgoing1)context.Envelope.Message; - - - // The actual message execution - MartenTests.Outgoing1Handler.Handle(outgoing1); - - return System.Threading.Tasks.Task.CompletedTask; - } - - } - - // END: Outgoing1Handler1264108911 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAAAHandler1649029811.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAAAHandler1649029811.cs deleted file mode 100644 index 2b79540a4..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAAAHandler1649029811.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseAAAHandler1649029811 - public class RaiseAAAHandler1649029811 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseAAAHandler1649029811(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseAAA = (MartenTests.RaiseAAA)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseAAA.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - var outgoing1 = MartenTests.RaiseLetterHandler.Handle(raiseAAA, eventStream); - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing1).ConfigureAwait(false); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseAAAHandler1649029811 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAABCCHandler1413048758.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAABCCHandler1413048758.cs deleted file mode 100644 index 94ff56718..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseAABCCHandler1413048758.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseAABCCHandler1413048758 - public class RaiseAABCCHandler1413048758 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseAABCCHandler1413048758(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseAABCC = (MartenTests.RaiseAABCC)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseAABCC.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - (var outgoing1, var outgoing2) = MartenTests.RaiseLetterHandler.Handle(raiseAABCC, eventStream.Aggregate); - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing1).ConfigureAwait(false); - - if (outgoing2 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing2); - - } - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseAABCCHandler1413048758 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseABCHandler1483138068.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseABCHandler1483138068.cs deleted file mode 100644 index e503e6663..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseABCHandler1483138068.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseABCHandler1483138068 - public class RaiseABCHandler1483138068 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseABCHandler1483138068(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseABC = (MartenTests.RaiseABC)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseABC.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - (var outgoing1, var outgoing2) = MartenTests.RaiseLetterHandler.Handle(raiseABC, eventStream.Aggregate); - - if (outgoing1 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing1); - - } - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing2).ConfigureAwait(false); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseABCHandler1483138068 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseBBCCCHandler1900945687.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseBBCCCHandler1900945687.cs deleted file mode 100644 index ed6c631bf..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseBBCCCHandler1900945687.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseBBCCCHandler1900945687 - public class RaiseBBCCCHandler1900945687 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseBBCCCHandler1900945687(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseBBCCC = (MartenTests.RaiseBBCCC)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseBBCCC.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - (var outgoing1, var outgoing2, var outgoing3) = MartenTests.RaiseLetterHandler.Handle(raiseBBCCC, eventStream.Aggregate); - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing1).ConfigureAwait(false); - - if (outgoing2 != null) - { - - // Capturing any possible events returned from the command handlers - eventStream.AppendMany(outgoing2); - - } - - - // Outgoing, cascaded message - await context.EnqueueCascadingAsync(outgoing3).ConfigureAwait(false); - - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseBBCCCHandler1900945687 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseLotsAsyncHandler89313884.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseLotsAsyncHandler89313884.cs deleted file mode 100644 index b99eab92d..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseLotsAsyncHandler89313884.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseLotsAsyncHandler89313884 - public class RaiseLotsAsyncHandler89313884 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseLotsAsyncHandler89313884(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseLotsAsync = (MartenTests.RaiseLotsAsync)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseLotsAsync.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - var outgoing1 = MartenTests.RaiseLetterHandler.Handle(raiseLotsAsync, eventStream.Aggregate); - - // Apply events to Marten event stream - await foreach (var letterAggregateEvent in outgoing1) eventStream.AppendOne(letterAggregateEvent); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseLotsAsyncHandler89313884 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseOnlyDHandler1609388090.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseOnlyDHandler1609388090.cs deleted file mode 100644 index 83adacdc2..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/RaiseOnlyDHandler1609388090.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -#pragma warning disable -using Wolverine.Marten.Publishing; - -namespace Internal.Generated.WolverineHandlers -{ - // START: RaiseOnlyDHandler1609388090 - public class RaiseOnlyDHandler1609388090 : Wolverine.Runtime.Handlers.MessageHandler - { - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public RaiseOnlyDHandler1609388090(Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) - { - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var raiseOnlyD = (MartenTests.RaiseOnlyD)context.Envelope.Message; - - await using var documentSession = _outboxedSessionFactory.OpenSession(context); - var eventStore = documentSession.Events; - - // Loading Marten aggregate - var eventStream = await eventStore.FetchForWriting(raiseOnlyD.LetterAggregateId, cancellation).ConfigureAwait(false); - - - // The actual message execution - var outgoing1 = MartenTests.RaiseLetterHandler.Handle(raiseOnlyD, eventStream.Aggregate); - - eventStream.AppendOne(outgoing1); - await documentSession.SaveChangesAsync(cancellation).ConfigureAwait(false); - } - - } - - // END: RaiseOnlyDHandler1609388090 - - -} - diff --git a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/ResponseHandler2107844337.cs b/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/ResponseHandler2107844337.cs deleted file mode 100644 index 36b30592b..000000000 --- a/src/Persistence/MartenTests/Internal/Generated/WolverineHandlers/ResponseHandler2107844337.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -#pragma warning disable - -namespace Internal.Generated.WolverineHandlers -{ - // START: ResponseHandler2107844337 - public class ResponseHandler2107844337 : Wolverine.Runtime.Handlers.MessageHandler - { - - - public override System.Threading.Tasks.Task HandleAsync(Wolverine.Runtime.MessageContext context, System.Threading.CancellationToken cancellation) - { - // The actual message body - var response = (MartenTests.Response)context.Envelope.Message; - - - // The actual message execution - MartenTests.ResponseHandler.Handle(response); - - return System.Threading.Tasks.Task.CompletedTask; - } - - } - - // END: ResponseHandler2107844337 - - -} - diff --git a/src/Persistence/MartenTests/aggregate_handler_workflow.cs b/src/Persistence/MartenTests/aggregate_handler_workflow.cs index 6d9184c7c..b437e0d91 100644 --- a/src/Persistence/MartenTests/aggregate_handler_workflow.cs +++ b/src/Persistence/MartenTests/aggregate_handler_workflow.cs @@ -30,6 +30,8 @@ public async Task InitializeAsync() { m.Connection(Servers.PostgresConnectionString); m.Projections.Snapshot(SnapshotLifecycle.Inline); + + m.DisableNpgsqlLogging = true; }) .UseLightweightSessions() .IntegrateWithWolverine(); @@ -219,6 +221,25 @@ public async Task if_only_returning_outgoing_messages_no_events() events.OfType>().Any().ShouldBeFalse(); } } + + [Fact] + public async Task using_updated_aggregate_as_response() + { + var streamId = Guid.NewGuid(); + using (var session = theStore.LightweightSession()) + { + session.Events.StartStream(streamId, new AEvent(), new BEvent()); + await session.SaveChangesAsync(); + } + + var (tracked, updated) + = await theHost.InvokeMessageAndWaitAsync(new Raise(streamId, 2, 3)); + + tracked.Sent.AllMessages().ShouldBeEmpty(); + + updated.ACount.ShouldBe(3); + updated.BCount.ShouldBe(4); + } } public record Event1(Guid AggregateId); @@ -325,8 +346,26 @@ public static async IAsyncEnumerable Handle(RaiseLotsAsync command, Lett yield return new CEvent(); yield return new CEvent(); } + + public static (UpdatedAggregate, Events) Handle(Raise command, LetterAggregate aggregate) + { + var events = new Events(); + for (int i = 0; i < command.A; i++) + { + events.Add(new AEvent()); + } + + for (int i = 0; i < command.B; i++) + { + events.Add(new BEvent()); + } + + return (new UpdatedAggregate(), events); + } } +public record Raise(Guid LetterAggregateId, int A, int B); + public record RaiseLotsAsync(Guid LetterAggregateId); public record RaiseOnlyD(Guid LetterAggregateId); diff --git a/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs b/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs index e2e7a2d38..1b22d33e0 100644 --- a/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs +++ b/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs @@ -1,5 +1,6 @@ using Marten.Events; using Marten.Schema; +using Wolverine; using Wolverine.Attributes; using Wolverine.Marten; @@ -52,4 +53,63 @@ public static void Handle(OrderEventSourcingSample.MarkItemReady command, IEvent } #endregion -} \ No newline at end of file +} + + +public static class MarkItemReadyHandler3 +{ + [WolverineIgnore] // just keeping this out of codegen and discovery + + #region sample_MarkItemReadyHandler_with_response_for_updated_aggregate + + [AggregateHandler] + public static ( + // Just tells Wolverine to use Marten's FetchLatest API to respond with + // the updated version of Order that reflects whatever events were appended + // in this command + UpdatedAggregate, + + // The events that should be appended to the event stream for this order + Events) Handle(OrderEventSourcingSample.MarkItemReady command, Order order) + { + var events = new Events(); + + if (order.Items.TryGetValue(command.ItemName, out var item)) + { + // Not doing this in a purist way here, but just + // trying to illustrate the Wolverine mechanics + item.Ready = true; + + // Mark that the this item is ready + events.Add(new ItemReady(command.ItemName)); + } + else + { + // Some crude validation + throw new InvalidOperationException($"Item {command.ItemName} does not exist in this order"); + } + + // If the order is ready to ship, also emit an OrderReady event + if (order.IsReadyToShip()) + { + events.Add(new OrderReady()); + } + + return (new UpdatedAggregate(), events); + } + + #endregion + + #region sample_using_UpdatedAggregate_with_invoke_async + + public static Task update_and_get_latest(IMessageBus bus, MarkItemReady command) + { + // This will return the updated version of the Order + // aggregate that incorporates whatever events were appended + // in the course of processing the command + return bus.InvokeAsync(command); + } + + #endregion +} + diff --git a/src/Persistence/Wolverine.Marten/AggregateHandlerAttribute.cs b/src/Persistence/Wolverine.Marten/AggregateHandlerAttribute.cs index ba8a23c9d..e9c4e1fe9 100644 --- a/src/Persistence/Wolverine.Marten/AggregateHandlerAttribute.cs +++ b/src/Persistence/Wolverine.Marten/AggregateHandlerAttribute.cs @@ -7,8 +7,10 @@ using Marten; using Marten.Events; using Marten.Events.Aggregation; +using Marten.Linq.Members; using Marten.Schema; using Wolverine.Attributes; +using Wolverine.Codegen; using Wolverine.Configuration; using Wolverine.Marten.Codegen; using Wolverine.Marten.Publishing; @@ -97,6 +99,8 @@ public override void Modify(IChain chain, GenerationRules rules, IServiceContain RelayAggregateToHandlerMethod(loader.ReturnVariable, firstCall, AggregateType); chain.Postprocessors.Add(MethodCall.For(x => x.SaveChangesAsync(default))); + + new AggregateHandling(AggregateType, new Variable(AggregateIdMember.GetRawMemberType(), "aggregateId")).Store(chain); } internal static void DetermineEventCaptureHandling(IChain chain, MethodCall firstCall, Type aggregateType) diff --git a/src/Persistence/Wolverine.Marten/Codegen/LoadAggregateFrame.cs b/src/Persistence/Wolverine.Marten/Codegen/LoadAggregateFrame.cs index e75c677f0..05cb93dd0 100644 --- a/src/Persistence/Wolverine.Marten/Codegen/LoadAggregateFrame.cs +++ b/src/Persistence/Wolverine.Marten/Codegen/LoadAggregateFrame.cs @@ -1,4 +1,5 @@ using System.Reflection; +using JasperFx.CodeGeneration; using JasperFx.CodeGeneration.Frames; using JasperFx.CodeGeneration.Model; using JasperFx.Core.Reflection; @@ -22,7 +23,7 @@ public override IEnumerable FindVariables(IMethodVariables chain) _command = chain.FindVariable(_att.CommandType!); yield return _command; - Arguments[0] = new MemberAccessVariable(_command, _att.AggregateIdMember!); + Arguments[0] = new Variable(_att.AggregateIdMember.GetRawMemberType(),"aggregateId"); if (_att.LoadStyle == ConcurrencyStyle.Optimistic && _att.VersionMember != null) { Arguments[1] = new MemberAccessVariable(_command, _att.VersionMember); @@ -31,6 +32,12 @@ public override IEnumerable FindVariables(IMethodVariables chain) foreach (var variable in base.FindVariables(chain)) yield return variable; } + public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) + { + writer.WriteLine($"var aggregateId = {_command.Usage}.{_att.AggregateIdMember.Name};"); + base.GenerateCode(method, writer); + } + internal static MethodInfo FindMethod(AggregateHandlerAttribute att) { var isGuidIdentified = att.AggregateIdMember!.GetMemberType() == typeof(Guid); diff --git a/src/Persistence/Wolverine.Marten/IAggregateHandling.cs b/src/Persistence/Wolverine.Marten/IAggregateHandling.cs new file mode 100644 index 000000000..ae3bd5cec --- /dev/null +++ b/src/Persistence/Wolverine.Marten/IAggregateHandling.cs @@ -0,0 +1,27 @@ +using JasperFx.CodeGeneration.Model; +using Wolverine.Configuration; + +namespace Wolverine.Marten; + +internal record AggregateHandling(Type AggregateType, Variable AggregateId) +{ + public void Store(IChain chain) + { + chain.Tags[nameof(AggregateHandling)] = this; + } + + public static bool TryLoad(IChain chain, out AggregateHandling handling) + { + if (chain.Tags.TryGetValue(nameof(AggregateHandling), out var raw)) + { + if (raw is AggregateHandling h) + { + handling = h; + return true; + } + } + + handling = default; + return false; + } +} diff --git a/src/Persistence/Wolverine.Marten/UpdatedAggregate.cs b/src/Persistence/Wolverine.Marten/UpdatedAggregate.cs new file mode 100644 index 000000000..dff29df4c --- /dev/null +++ b/src/Persistence/Wolverine.Marten/UpdatedAggregate.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using JasperFx.CodeGeneration.Frames; +using JasperFx.CodeGeneration.Model; +using JasperFx.Core.Reflection; +using Marten; +using Marten.Events; +using Marten.Internal; +using Wolverine.Configuration; + +namespace Wolverine.Marten; + +/// +/// Use this as a response from a message handler +/// or HTTP endpoint using the aggregate handler workflow +/// to response with the updated version of the aggregate being +/// altered *after* any new events have been applied +/// +public class UpdatedAggregate : IResponseAware +{ + public static void ConfigureResponse(IChain chain) + { + if (AggregateHandling.TryLoad(chain, out var handling)) + { + var idType = handling.AggregateId.VariableType; + + // TODO -- with https://github.com/JasperFx/wolverine/issues/1167, this might need to try to create value + // type first + var openType = idType == typeof(Guid) ? typeof(FetchLatestByGuid<>) : typeof(FetchLatestByString<>); + var frame = openType.CloseAndBuildAs(handling.AggregateId, handling.AggregateType); + + chain.UseForResponse(frame); + } + else + { + throw new InvalidOperationException($"UpdatedAggregate cannot be used because Chain {chain} is not marked as an aggregate handler. Are you missing an [AggregateHandler] or [Aggregate] attribute on the handler?"); + } + + } +} + +internal class FetchLatestByGuid : MethodCall where T : class +{ + public FetchLatestByGuid(Variable id) : base(typeof(IEventStore), ReflectionHelper.GetMethod(x => x.FetchLatest(Guid.Empty, CancellationToken.None))) + { + if (id.VariableType != typeof(Guid)) + { + throw new ArgumentOutOfRangeException( + "Wolverine does not yet support strong typed identifiers for the aggregate workflow. See https://github.com/JasperFx/wolverine/issues/1167"); + } + + Arguments[0] = id; + } +} + +internal class FetchLatestByString : MethodCall where T : class +{ + public FetchLatestByString(Variable id) : base(typeof(IEventStore), ReflectionHelper.GetMethod(x => x.FetchLatest("", CancellationToken.None))) + { + if (id.VariableType != typeof(string)) + { + throw new ArgumentOutOfRangeException( + "Wolverine does not yet support strong typed identifiers for the aggregate workflow. See https://github.com/JasperFx/wolverine/issues/1167"); + } + + Arguments[0] = id; + } +} \ No newline at end of file diff --git a/src/Testing/CoreTests/Acceptance/using_custom_response.cs b/src/Testing/CoreTests/Acceptance/using_custom_response.cs new file mode 100644 index 000000000..7d798911d --- /dev/null +++ b/src/Testing/CoreTests/Acceptance/using_custom_response.cs @@ -0,0 +1,91 @@ +using System.Diagnostics; +using Wolverine.Tracking; +using Xunit; + +namespace CoreTests.Acceptance; + +public class using_custom_response : IntegrationContext +{ + public using_custom_response(DefaultApp @default) : base(@default) + { + } + + [Fact] + public async Task use_synchronous_response() + { + var session = await Host.InvokeMessageAndWaitAsync(new SendTag1("blue")); + session.Received.SingleMessage() + .Tag.ShouldBe("blue"); + } + + [Fact] + public async Task use_response_that_returns_Task() + { + var session = await Host.InvokeMessageAndWaitAsync(new SendTag2("green")); + session.Received.SingleMessage() + .Tag.ShouldBe("green"); + } + + [Fact] + public async Task use_response_that_returns_ValueTask() + { + var session = await Host.InvokeMessageAndWaitAsync(new SendTag3("red")); + session.Received.SingleMessage() + .Tag.ShouldBe("red"); + } + + [Fact] + public async Task use_synchronous_response_expect_response() + { + var (session, message) = await Host.InvokeMessageAndWaitAsync(new SendTag1("blue")); + message.Tag.ShouldBe("blue"); + } + + [Fact] + public async Task use_response_that_returns_Task_expect_response() + { + var (session, message) = await Host.InvokeMessageAndWaitAsync(new SendTag2("green")); + message.Tag.ShouldBe("green"); + } + + [Fact] + public async Task use_response_that_returns_ValueTask_expect_response() + { + var (session, message) = await Host.InvokeMessageAndWaitAsync(new SendTag3("purple")); + message.Tag.ShouldBe("purple"); + } +} + +public record SendTag1(string Tag); +public record SendTag2(string Tag); +public record SendTag3(string Tag); + +public static class SendTagHandler +{ + public static TaggedResponse Handle(SendTag1 message) => new TaggedResponse(message.Tag); + public static AsyncTaggedResponse Handle(SendTag2 message) => new AsyncTaggedResponse(message.Tag); + public static ValueTaskTaggedResponse Handle(SendTag3 message) => new ValueTaskTaggedResponse(message.Tag); + + public static void Handle(TaggedMessage message) => Debug.WriteLine("Got tag " + message.Tag); + +} + +public record TaggedMessage(string Tag); + +public class TaggedResponse(string Tag) : IResponse +{ + public TaggedMessage Build() => new TaggedMessage(Tag); +} + +public class AsyncTaggedResponse(string Tag) : IResponse +{ + public Task Build() => Task.FromResult(new TaggedMessage(Tag)); +} + +public class ValueTaskTaggedResponse(string Tag) : IResponse +{ + public ValueTask Build() => new ValueTask(new TaggedMessage(Tag)); +} + + + diff --git a/src/Wolverine/Codegen/MessageMemberVariable.cs b/src/Wolverine/Codegen/MessageMemberVariable.cs new file mode 100644 index 000000000..93f7f8314 --- /dev/null +++ b/src/Wolverine/Codegen/MessageMemberVariable.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using JasperFx.CodeGeneration.Model; +using JasperFx.Core.Reflection; + +namespace Wolverine.Codegen; + +/// +/// Represents a value for a member of the incoming message type +/// +public class MessageMemberVariable : Variable +{ + public MessageMemberVariable(MemberInfo member, Type messageType) : base(member.GetRawMemberType(), $"(({messageType.FullNameInCode()})context.Envelope.Message).{member.Name}") + { + } +} \ No newline at end of file diff --git a/src/Wolverine/Configuration/Chain.cs b/src/Wolverine/Configuration/Chain.cs index c8606ab80..92aae0fc6 100644 --- a/src/Wolverine/Configuration/Chain.cs +++ b/src/Wolverine/Configuration/Chain.cs @@ -2,6 +2,7 @@ using System.Security.Claims; using JasperFx.CodeGeneration; using JasperFx.CodeGeneration.Frames; +using JasperFx.CodeGeneration.Model; using JasperFx.Core; using JasperFx.Core.Reflection; using Wolverine.Attributes; @@ -128,6 +129,30 @@ protected void applyAttributesAndConfigureMethods(GenerationRules rules, IServic { attribute.Modify(this, rules, container); } + + tryApplyResponseAware(); + } + + + /// + /// Find all variables returned by any handler call in this chain + /// that can be cast to T + /// + /// + /// + public IEnumerable ReturnVariablesOfType() + { + return HandlerCalls().SelectMany(x => x.Creates).Where(x => x.VariableType.CanBeCastTo()); + } + + /// + /// Find all variables returned by any handler call in this chain + /// that can be cast to the supplied type + /// + /// + public IEnumerable ReturnVariablesOfType(Type interfaceType) + { + return HandlerCalls().SelectMany(x => x.Creates).Where(x => x.VariableType.CanBeCastTo(interfaceType)); } private static Type[] _typesToIgnore = new Type[] @@ -275,4 +300,38 @@ public void ApplyImpliedMiddlewareFromHandlers(GenerationRules generationRules) } } } + + public abstract void UseForResponse(MethodCall methodCall); + + protected internal void tryApplyResponseAware() + { + var responseAwares = ReturnVariablesOfType(typeof(IResponseAware)).ToArray(); + if (responseAwares.Length == 0) return; + if (responseAwares.Length > 1) + throw new InvalidOperationException( + $"Cannot use more than one IResponseAware policy per chain. Chain {this} has {responseAwares.Select(x => x.ToString()).Join(", ")}"); + + typeof(Applier<>).CloseAndBuildAs(this, responseAwares[0].VariableType).Apply(); + } +} + +internal interface IApplier +{ + void Apply(); +} + +internal class Applier : IApplier where T : IResponseAware +{ + private readonly IChain _chain; + + public Applier(IChain chain) + { + _chain = chain; + } + + + public void Apply() + { + T.ConfigureResponse(_chain); + } } \ No newline at end of file diff --git a/src/Wolverine/Configuration/IChain.cs b/src/Wolverine/Configuration/IChain.cs index 80c021089..5304c181a 100644 --- a/src/Wolverine/Configuration/IChain.cs +++ b/src/Wolverine/Configuration/IChain.cs @@ -81,17 +81,6 @@ public interface IChain /// void Audit(MemberInfo member, string? heading = null); - /// - /// Find all variables returned by any handler call in this chain - /// that can be cast to T - /// - /// - /// - public IEnumerable ReturnVariablesOfType() - { - return HandlerCalls().SelectMany(x => x.Creates).Where(x => x.VariableType.CanBeCastTo()); - } - /// /// Help out the code generation a little bit by telling this chain /// about a service dependency that will be used. Helps connect @@ -101,6 +90,29 @@ public IEnumerable ReturnVariablesOfType() public void AddDependencyType(Type type); void ApplyImpliedMiddlewareFromHandlers(GenerationRules generationRules); + + /// + /// Special usage to make the single result of this method call be the actual response type + /// for the chain. For HTTP, this becomes the resource type written to the response. For message handlers, + /// this could be part of InvokeAsync() or just a cascading message + /// + /// + void UseForResponse(MethodCall methodCall); + + /// + /// Find all variables returned by any handler call in this chain + /// that can be cast to T + /// + /// + /// + IEnumerable ReturnVariablesOfType(); + + /// + /// Find all variables returned by any handler call in this chain + /// that can be cast to the supplied type + /// + /// + IEnumerable ReturnVariablesOfType(Type interfaceType); } #endregion \ No newline at end of file diff --git a/src/Wolverine/IResponse.cs b/src/Wolverine/IResponse.cs new file mode 100644 index 000000000..1be697803 --- /dev/null +++ b/src/Wolverine/IResponse.cs @@ -0,0 +1,82 @@ +using System.Reflection; +using JasperFx.CodeGeneration; +using JasperFx.CodeGeneration.Frames; +using JasperFx.Core; +using JasperFx.Core.Reflection; +using Wolverine.Configuration; +using Wolverine.Runtime; +using Wolverine.Runtime.Handlers; + +namespace Wolverine; + +// TODO -- might have to take off the IWolverineReturnType when we get to http +public interface IResponse : IWolverineReturnType +{ + // Method name will be Build or BuildAsync +} + +// It's only a handler policy because HTTP will need to deal +// with it a little bit differently +internal class ResponsePolicy : IHandlerPolicy +{ + public const string SyncMethod = "Build"; + public const string AsyncMethod = "BuildAsync"; + + public void Apply(IReadOnlyList chains, GenerationRules rules, IServiceContainer container) + { + foreach (var chain in chains) + { + var responses = chain.As().ReturnVariablesOfType(); + foreach (var response in responses) + { + var method = findMethod(response.VariableType); + if (method == null) + { + throw new InvalidCustomResponseException( + $"Invalid Wolverine response exception for {response.VariableType.FullNameInCode()}, no public {SyncMethod}/{AsyncMethod} method found"); + } + + foreach (var parameter in method.GetParameters()) chain.AddDependencyType(parameter.ParameterType); + + response.UseReturnAction(_ => + { + var buildResponse = new MethodCall(response.VariableType, method) + { + Target = response, + CommentText = $"Placed by Wolverine's {nameof(IResponse)} policy" + }; + + buildResponse.ReturnVariable.OverrideName("response_of_" + buildResponse.ReturnVariable.Usage); + + var captureAsCascading = new CaptureCascadingMessages(buildResponse.ReturnVariable); + + return new IfElseNullGuardFrame.IfNullGuardFrame( + response, + buildResponse, captureAsCascading); + }, "Custom Response Policy"); + } + } + } + + private MethodInfo findMethod(Type responseType) + { + return + responseType.GetMethod(SyncMethod, + BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance) + ?? responseType.GetMethod(AsyncMethod, + BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance) + ?? responseType.GetInterfaces().FirstValue(findMethod); + } +} + +public class InvalidCustomResponseException : Exception +{ + public InvalidCustomResponseException(string? message) : base(message) + { + } +} + +public interface IResponseAware : IWolverineReturnType +{ + static abstract void ConfigureResponse(IChain chain); +} \ No newline at end of file diff --git a/src/Wolverine/Runtime/Handlers/HandlerChain.cs b/src/Wolverine/Runtime/Handlers/HandlerChain.cs index d9440aacb..d08e5321b 100644 --- a/src/Wolverine/Runtime/Handlers/HandlerChain.cs +++ b/src/Wolverine/Runtime/Handlers/HandlerChain.cs @@ -305,6 +305,17 @@ public override Type InputType() return MessageType; } + public override void UseForResponse(MethodCall methodCall) + { + var response = methodCall.ReturnVariable; + response.OverrideName("response_of_" + response.Usage); + + Postprocessors.Add(methodCall); + + var cascading = new CaptureCascadingMessages(response); + Postprocessors.Add(cascading); + } + public IEnumerable PublishedTypes() { var ignoredTypes = new[] @@ -416,9 +427,7 @@ protected void applyCustomizations(GenerationRules rules, IServiceContainer cont if (!_hasConfiguredFrames) { _hasConfiguredFrames = true; - - - + applyAttributesAndConfigureMethods(rules, container); foreach (var attribute in MessageType diff --git a/src/Wolverine/WolverineOptions.cs b/src/Wolverine/WolverineOptions.cs index bc7bb13a6..291e18b53 100644 --- a/src/Wolverine/WolverineOptions.cs +++ b/src/Wolverine/WolverineOptions.cs @@ -51,8 +51,9 @@ public WolverineOptions(string? assemblyName) Policies.Add(); Policies.Add(); + Policies.Add(); Policies.Add(); - } + } public Guid UniqueNodeId { get; } = Guid.NewGuid();