Skip to content

Commit 3963e56

Browse files
authored
Merge branch 'JasperFx:main' into StoreObjects
2 parents cfe30af + 4d49c71 commit 3963e56

File tree

61 files changed

+819
-1109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+819
-1109
lines changed

docs/guide/durability/dead-letter-storage.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ app.MapDeadLettersEndpoints()
5050
5151
;
5252
```
53-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L141-L151' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_dead_letter_endpoints' title='Start of snippet'>anchor</a></sup>
53+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L145-L155' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_dead_letter_endpoints' title='Start of snippet'>anchor</a></sup>
5454
<!-- endSnippet -->
5555

5656
### Using the Dead Letters REST API

docs/guide/durability/marten/event-sourcing.md

+109-2
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public static void Handle(OrderEventSourcingSample.MarkItemReady command, IEvent
267267
}
268268
}
269269
```
270-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L25-L54' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_markitemreadyhandler_with_explicit_stream' title='Start of snippet'>anchor</a></sup>
270+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L26-L55' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_markitemreadyhandler_with_explicit_stream' title='Start of snippet'>anchor</a></sup>
271271
<!-- endSnippet -->
272272

273273
Just as in other Wolverine [message handlers](/guide/handlers/), you can use
@@ -365,9 +365,116 @@ public class MarkItemReady
365365
public string ItemName { get; init; }
366366
}
367367
```
368-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L8-L19' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_markitemready_with_explicit_identity' title='Start of snippet'>anchor</a></sup>
368+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L9-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_markitemready_with_explicit_identity' title='Start of snippet'>anchor</a></sup>
369369
<!-- endSnippet -->
370370

371371
## Forwarding Events
372372

373373
See [Event Forwarding](./event-forwarding) for more information.
374+
375+
## Returning the Updated Aggregate <Badge type="tip" text="3.5" />
376+
377+
A common use case for the "aggregate handler workflow" has been to respond with the now updated state of the projected
378+
aggregate that has just been updated by appending new events. Until now, that's effectively meant making a completely separate
379+
call to the database through Marten to retrieve the latest updates.
380+
381+
::: info
382+
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)
383+
API.
384+
:::
385+
386+
As a quick tip for performance, assuming that you are *not* mutating the projected documents within your command
387+
handlers, you can opt for this significant Marten optimization to eliminate extra database round trips while
388+
using the aggregate handler workflow:
389+
390+
```csharp
391+
builder.Services.AddMarten(opts =>
392+
{
393+
// Other Marten configuration
394+
395+
// Use this setting to get the very best performance out
396+
// of the UpdatedAggregate workflow and aggregate handler
397+
// workflow over all
398+
opts.Events.UseIdentityMapForAggregates = true;
399+
}).IntegrateWithWolverine();
400+
```
401+
402+
::: info
403+
The setting above cannot be a default in Marten because it can break some existing code with a very different
404+
workflow that what the Critter Stack team recommends for the aggregate handler workflow.
405+
:::
406+
407+
Wolverine.Marten has a special response type for message handlers or HTTP endpoints we can use as a directive to tell Wolverine
408+
to respond with the latest state of a projected aggregate as part of the command execution. Let's make this concrete by
409+
taking the `MarkItemReady` command handler we've used earlier in this guide and building a slightly new version that
410+
produces a response of the latest aggregate:
411+
412+
<!-- snippet: sample_MarkItemReadyHandler_with_response_for_updated_aggregate -->
413+
<a id='snippet-sample_markitemreadyhandler_with_response_for_updated_aggregate'></a>
414+
```cs
415+
[AggregateHandler]
416+
public static (
417+
// Just tells Wolverine to use Marten's FetchLatest API to respond with
418+
// the updated version of Order that reflects whatever events were appended
419+
// in this command
420+
UpdatedAggregate,
421+
422+
// The events that should be appended to the event stream for this order
423+
Events) Handle(OrderEventSourcingSample.MarkItemReady command, Order order)
424+
{
425+
var events = new Events();
426+
427+
if (order.Items.TryGetValue(command.ItemName, out var item))
428+
{
429+
// Not doing this in a purist way here, but just
430+
// trying to illustrate the Wolverine mechanics
431+
item.Ready = true;
432+
433+
// Mark that the this item is ready
434+
events.Add(new ItemReady(command.ItemName));
435+
}
436+
else
437+
{
438+
// Some crude validation
439+
throw new InvalidOperationException($"Item {command.ItemName} does not exist in this order");
440+
}
441+
442+
// If the order is ready to ship, also emit an OrderReady event
443+
if (order.IsReadyToShip())
444+
{
445+
events.Add(new OrderReady());
446+
}
447+
448+
return (new UpdatedAggregate(), events);
449+
}
450+
```
451+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L63-L101' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_markitemreadyhandler_with_response_for_updated_aggregate' title='Start of snippet'>anchor</a></sup>
452+
<!-- endSnippet -->
453+
454+
Note the usage of the `Wolverine.Marten.UpdatedAggregate` response in the handler. That type by itself is just a directive
455+
to Wolverine to generate the necessary code to call `FetchLatest` and respond with that. The command handler above allows
456+
us to use the command in a mediator usage like so:
457+
458+
<!-- snippet: sample_using_UpdatedAggregate_with_invoke_async -->
459+
<a id='snippet-sample_using_updatedaggregate_with_invoke_async'></a>
460+
```cs
461+
public static Task<Order> update_and_get_latest(IMessageBus bus, MarkItemReady command)
462+
{
463+
// This will return the updated version of the Order
464+
// aggregate that incorporates whatever events were appended
465+
// in the course of processing the command
466+
return bus.InvokeAsync<Order>(command);
467+
}
468+
```
469+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/OrderEventSourcingSample/Alternatives/Signatures.cs#L103-L113' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_updatedaggregate_with_invoke_async' title='Start of snippet'>anchor</a></sup>
470+
<!-- endSnippet -->
471+
472+
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~~~~).
473+
474+
::: info
475+
This feature has been more or less requested several times, but was finally brought about because of the need
476+
to consume Wolverine + Marten commands within Hot Chocolate mutations and always return the current state of
477+
the projected aggregate being updated to the user interface.
478+
:::
479+
480+

docs/guide/extensions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ var app = builder.Build();
242242
// you will need to explicitly call this *before* MapWolverineEndpoints()
243243
await app.Services.ApplyAsyncWolverineExtensions();
244244
```
245-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L103-L111' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_calling_applyasyncwolverineextensions' title='Start of snippet'>anchor</a></sup>
245+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L107-L115' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_calling_applyasyncwolverineextensions' title='Start of snippet'>anchor</a></sup>
246246
<!-- endSnippet -->
247247

248248
## Wolverine Plugin Modules

docs/guide/http/endpoints.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public static OrderShipped Ship(ShipOrder command, Order order)
167167
return new OrderShipped();
168168
}
169169
```
170-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L106-L119' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_emptyresponse' title='Start of snippet'>anchor</a></sup>
170+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L116-L129' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_emptyresponse' title='Start of snippet'>anchor</a></sup>
171171
<!-- endSnippet -->
172172

173173
## JSON Handling
@@ -312,7 +312,7 @@ and register that strategy within our `MapWolverineEndpoints()` set up like so:
312312
// Customizing parameter handling
313313
opts.AddParameterHandlingStrategy<NowParameterStrategy>();
314314
```
315-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L203-L208' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_adding_custom_parameter_handling' title='Start of snippet'>anchor</a></sup>
315+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L207-L212' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_adding_custom_parameter_handling' title='Start of snippet'>anchor</a></sup>
316316
<!-- endSnippet -->
317317

318318
And lastly, here's the application within an HTTP endpoint for extra context:

docs/guide/http/fluentvalidation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ app.MapWolverineEndpoints(opts =>
4444
// Wolverine.Http.FluentValidation
4545
opts.UseFluentValidationProblemDetailMiddleware();
4646
```
47-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L153-L174' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
47+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L157-L178' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_endpoints' title='Start of snippet'>anchor</a></sup>
4848
<!-- endSnippet -->

docs/guide/http/marten.md

+42-6
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static OrderShipped Ship(ShipOrder2 command, [Aggregate] Order order)
124124
return new OrderShipped();
125125
}
126126
```
127-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L121-L136' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_aggregate_attribute_1' title='Start of snippet'>anchor</a></sup>
127+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L131-L146' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_aggregate_attribute_1' title='Start of snippet'>anchor</a></sup>
128128
<!-- endSnippet -->
129129

130130
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)
143143
return new OrderShipped();
144144
}
145145
```
146-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L138-L150' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_aggregate_attribute_2' title='Start of snippet'>anchor</a></sup>
146+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L148-L160' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_aggregate_attribute_2' title='Start of snippet'>anchor</a></sup>
147147
<!-- endSnippet -->
148148

149149
A couple other notes:
@@ -189,6 +189,9 @@ public class Item
189189

190190
public class Order
191191
{
192+
// For JSON serialization
193+
public Order(){}
194+
192195
public Order(OrderCreated created)
193196
{
194197
foreach (var item in created.Items) Items[item.Name] = item;
@@ -218,6 +221,13 @@ public class Order
218221
Items[ready.Name].Ready = true;
219222
}
220223

224+
public void Apply(OrderConfirmed confirmed)
225+
{
226+
IsConfirmed = true;
227+
}
228+
229+
public bool IsConfirmed { get; set; }
230+
221231
public bool IsReadyToShip()
222232
{
223233
return Shipped == null && Items.Values.All(x => x.Ready);
@@ -226,7 +236,7 @@ public class Order
226236
public bool IsShipped() => Shipped.HasValue;
227237
}
228238
```
229-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L11-L73' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_order_aggregate_for_http' title='Start of snippet'>anchor</a></sup>
239+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L11-L83' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_order_aggregate_for_http' title='Start of snippet'>anchor</a></sup>
230240
<!-- endSnippet -->
231241

232242
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)
245255
return new OrderShipped();
246256
}
247257
```
248-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L106-L119' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_emptyresponse' title='Start of snippet'>anchor</a></sup>
258+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L116-L129' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_emptyresponse' title='Start of snippet'>anchor</a></sup>
249259
<!-- endSnippet -->
250260

251261
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)
281291
return (new OrderStatus(order.Id, order.IsReadyToShip()), events);
282292
}
283293
```
284-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L200-L230' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_returning_multiple_events_from_http_endpoint' title='Start of snippet'>anchor</a></sup>
294+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L210-L240' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_returning_multiple_events_from_http_endpoint' title='Start of snippet'>anchor</a></sup>
295+
<!-- endSnippet -->
296+
297+
### Responding with the Updated Aggregate
298+
299+
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.
300+
301+
To return the updated state of a projected aggregate from Marten as the HTTP response from an endpoint using
302+
the aggregate handler workflow, return the `UpdatedAggregate` marker type as the first "response value" of
303+
your HTTP endpoint like so:
304+
305+
<!-- snippet: sample_returning_updated_aggregate_as_response_from_http_endpoint -->
306+
<a id='snippet-sample_returning_updated_aggregate_as_response_from_http_endpoint'></a>
307+
```cs
308+
[AggregateHandler]
309+
[WolverinePost("/orders/{id}/confirm2")]
310+
// The updated version of the Order aggregate will be returned as the response body
311+
// from requesting this endpoint at runtime
312+
public static (UpdatedAggregate, Events) ConfirmDifferent(ConfirmOrder command, Order order)
313+
{
314+
return (
315+
new UpdatedAggregate(),
316+
[new OrderConfirmed()]
317+
);
318+
}
319+
```
320+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Marten/Orders.cs#L268-L282' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_returning_updated_aggregate_as_response_from_http_endpoint' title='Start of snippet'>anchor</a></sup>
285321
<!-- endSnippet -->
286322

287323
### Compiled Query Resource Writer Policy
@@ -294,7 +330,7 @@ Register it in `WolverineHttpOptions` like this:
294330
```cs
295331
opts.UseMartenCompiledQueryResultPolicy();
296332
```
297-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L180-L182' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_user_marten_compiled_query_policy' title='Start of snippet'>anchor</a></sup>
333+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L184-L186' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_user_marten_compiled_query_policy' title='Start of snippet'>anchor</a></sup>
298334
<!-- endSnippet -->
299335

300336
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.

docs/guide/http/mediator.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ app.MapPostToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
4545
app.MapDeleteToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
4646
app.MapPutToWolverine<CustomRequest, CustomResponse>("/wolverine/request");
4747
```
48-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L214-L226' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_optimized_mediator_usage' title='Start of snippet'>anchor</a></sup>
48+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L218-L230' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_optimized_mediator_usage' title='Start of snippet'>anchor</a></sup>
4949
<!-- endSnippet -->
5050

5151
With this mechanism, Wolverine is able to optimize the runtime function for Minimal API by eliminating IoC service locations

docs/guide/http/metadata.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ builder.Services.AddSwaggerGen(x =>
9797
x.OperationFilter<WolverineOperationFilter>();
9898
});
9999
```
100-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L43-L50' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_custom_swashbuckle_filter' title='Start of snippet'>anchor</a></sup>
100+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L42-L49' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_custom_swashbuckle_filter' title='Start of snippet'>anchor</a></sup>
101101
<!-- endSnippet -->
102102

103103
## Operation Id

docs/guide/http/middleware.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Which is registered like this (or as described in [`Registering Middleware by Me
4949
opts.AddMiddlewareByMessageType(typeof(FakeAuthenticationMiddleware));
5050
opts.AddMiddlewareByMessageType(typeof(CanShipOrderMiddleWare));
5151
```
52-
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L184-L187' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_http_middleware_by_type' title='Start of snippet'>anchor</a></sup>
52+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/WolverineWebApi/Program.cs#L188-L191' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_http_middleware_by_type' title='Start of snippet'>anchor</a></sup>
5353
<!-- endSnippet -->
5454

5555
The key point to notice there is that `IResult` is a "return value" of the middleware. In the case of an HTTP endpoint,

0 commit comments

Comments
 (0)