diff --git a/docs/guide/durability/dead-letter-storage.md b/docs/guide/durability/dead-letter-storage.md
index b90457c4b..ca67288b1 100644
--- a/docs/guide/durability/dead-letter-storage.md
+++ b/docs/guide/durability/dead-letter-storage.md
@@ -82,7 +82,7 @@ app.MapDeadLettersEndpoints()
;
```
-snippet source | anchor
+snippet source | anchor
### Using the Dead Letters REST API
diff --git a/docs/guide/durability/efcore/multi-tenancy.md b/docs/guide/durability/efcore/multi-tenancy.md
index 5b44c434b..16331d848 100644
--- a/docs/guide/durability/efcore/multi-tenancy.md
+++ b/docs/guide/durability/efcore/multi-tenancy.md
@@ -184,11 +184,11 @@ public class MyMessageHandler
_factory = factory;
}
- public async Task HandleAsync(CreateItem command, string tenantId, CancellationToken cancellationToken)
+ public async Task HandleAsync(CreateItem command, TenantId tenantId, CancellationToken cancellationToken)
{
// Get an EF Core DbContext wrapped in a Wolverine IDbContextOutbox
// for message sending wrapped in a transaction spanning the DbContext and Wolverine
- var outbox = await _factory.CreateForTenantAsync(tenantId, cancellationToken);
+ var outbox = await _factory.CreateForTenantAsync(tenantId.Value, cancellationToken);
var item = new Item { Name = command.Name, Id = CombGuidIdGeneration.NewGuid() };
outbox.DbContext.Items.Add(item);
diff --git a/docs/guide/durability/marten/event-sourcing.md b/docs/guide/durability/marten/event-sourcing.md
index 1e207fd4d..a203c203c 100644
--- a/docs/guide/durability/marten/event-sourcing.md
+++ b/docs/guide/durability/marten/event-sourcing.md
@@ -529,7 +529,7 @@ public class MarkItemReady
```
snippet source | anchor
-~~~~
+
## Validation
Every possible attribute for triggering the "aggregate handler workflow" includes support for data requirements as
@@ -553,7 +553,7 @@ public static string GetLetter2([ReadAggregate(Required = false)] LetterAggregat
[WolverineGet("/letters3/{id}")]
public static LetterAggregate GetLetter3([ReadAggregate(OnMissing = OnMissing.ProblemDetailsWith404)] LetterAggregate letters) => letters;
```
-snippet source | anchor
+snippet source | anchor
## Forwarding Events
@@ -720,6 +720,7 @@ public static class FindLettersHandler
return new LetterAggregateEnvelope(aggregate);
}
+ /* ALTERNATIVE VERSION
[WolverineHandler]
public static LetterAggregateEnvelope Handle2(
FindAggregate command,
@@ -729,9 +730,10 @@ public static class FindLettersHandler
{
return aggregate == null ? null : new LetterAggregateEnvelope(aggregate);
}
+ */
}
```
-snippet source | anchor
+snippet source | anchor
If the aggregate doesn't exist, the HTTP request will stop with a 404 status code.
@@ -762,9 +764,128 @@ public static string GetLetter2([ReadAggregate(Required = false)] LetterAggregat
[WolverineGet("/letters3/{id}")]
public static LetterAggregate GetLetter3([ReadAggregate(OnMissing = OnMissing.ProblemDetailsWith404)] LetterAggregate letters) => letters;
```
-snippet source | anchor
+snippet source | anchor
There is also an option with `OnMissing` to throw a `RequiredDataMissingException` exception if a required data element
is missing. This option is probably most useful with message handlers where you may want to key off the exception with custom
error handling rules.
+
+## Targeting Multiple Streams at Once
+
+It's now possible to use the "aggregate handler workflow" while needing to append events to more
+than one event stream at a time.
+
+::: tip
+You can use read only views of event streams through `[ReadAggregate]` at will, and that will use
+Marten's `FetchLatest()` API underneath. For appending to multiple streams though, for now you will
+have to directly target `IEventStream` to help Marten know which stream you're appending events to.
+:::
+
+Using the canonical example of a use case where you move money from one account to another account and need both
+changes to be persisted in one atomic transaction. Let’s start with a simplified domain model of
+events and a “self-aggregating” Account type like this:
+
+
+
+```cs
+public record AccountCreated(double InitialAmount);
+public record Debited(double Amount);
+public record Withdrawn(double Amount);
+
+public class Account
+{
+ public Guid Id { get; set; }
+ public double Amount { get; set; }
+
+ public static Account Create(IEvent e)
+ => new Account { Id = e.StreamId, Amount = e.Data.InitialAmount};
+
+ public void Apply(Debited e) => Amount += e.Amount;
+ public void Apply(Withdrawn e) => Amount -= e.Amount;
+}
+```
+snippet source | anchor
+
+
+And you need to handle a command like this:
+
+
+
+```cs
+public record TransferMoney(Guid FromId, Guid ToId, double Amount);
+```
+snippet source | anchor
+
+
+Using the `[WriteAggregate]` attribute to denote the event streams we need to work with,
+we could write this message handler + HTTP endpoint:
+
+
+
+```cs
+public static class TransferMoneyHandler
+{
+ [WolverinePost("/accounts/transfer")]
+ public static void Handle(
+ TransferMoney command,
+
+ [WriteAggregate(nameof(TransferMoney.FromId))] IEventStream fromAccount,
+
+ [WriteAggregate(nameof(TransferMoney.ToId))] IEventStream toAccount)
+ {
+ // Would already 404 if either referenced account does not exist
+ if (fromAccount.Aggregate.Amount >= command.Amount)
+ {
+ fromAccount.AppendOne(new Withdrawn(command.Amount));
+ toAccount.AppendOne(new Debited(command.Amount));
+ }
+ }
+}
+```
+snippet source | anchor
+
+
+The `IEventStream` abstraction comes from Marten’s `FetchForWriting()` API that is our
+recommended way to interact with Marten streams in typical command handlers. This
+API is used underneath Wolverine’s “aggregate handler workflow”, but normally hidden
+from user written code if you’re only working with one stream at a time. In this case
+though, we’ll need to work with the raw `IEventStream` objects that both wrap the
+projected aggregation of each Account as well as providing a point where we can explicitly
+append events separately to each event stream. `FetchForWriting()` guarantees that you get
+the most up to date information for the Account view of each event stream regardless of
+how you have configured Marten’s `ProjectionLifecycle` for `Account` (kind of an important
+detail here!).
+
+The typical Marten transactional middleware within Wolverine is calling `SaveChangesAsync()`
+for us on the Marten unit of work IDocumentSession for the command. If there’s enough funds in
+the “From” account, this command will append a `Withdrawn` event to the “From” account and
+a `Debited` event to the “To” account. If either account has been written to between
+fetching the original information, Marten will reject the changes and throw its
+`ConcurrencyException` as an optimistic concurrency check.
+
+In unit testing, we could write a unit test for the “happy path” where you have enough funds to cover the transfer like this:
+
+
+
+```cs
+public class when_transfering_money
+{
+ [Fact]
+ public void happy_path_have_enough_funds()
+ {
+ // StubEventStream is a type that was recently added to Marten
+ // specifically to facilitate testing logic like this
+ var fromAccount = new StubEventStream(new Account { Amount = 1000 }){Id = Guid.NewGuid()};
+ var toAccount = new StubEventStream(new Account { Amount = 100}){Id = Guid.NewGuid()});
+
+ TransferMoneyHandler.Handle(new TransferMoney(fromAccount.Id, toAccount.Id, 100), fromAccount, toAccount);
+
+ // Now check the events we expected to be appended
+ fromAccount.Events.Single().ShouldBeOfType().Amount.ShouldBe(100);
+ toAccount.Events.Single().ShouldBeOfType().Amount.ShouldBe(100);
+ }
+}
+```
+snippet source | anchor
+
diff --git a/docs/guide/durability/postgresql.md b/docs/guide/durability/postgresql.md
index 1160bf19a..58677da4a 100644
--- a/docs/guide/durability/postgresql.md
+++ b/docs/guide/durability/postgresql.md
@@ -195,8 +195,8 @@ public class OurFancyPostgreSQLMultiTenancy : IWolverineExtension
.RegisterStaticTenantsByDataSource(tenants =>
{
tenants.Register("tenant1", _provider.GetRequiredKeyedService("tenant1"));
- tenants.Register("tenant1", _provider.GetRequiredKeyedService("tenant2"));
- tenants.Register("tenant1", _provider.GetRequiredKeyedService("tenant3"));
+ tenants.Register("tenant2", _provider.GetRequiredKeyedService("tenant2"));
+ tenants.Register("tenant3", _provider.GetRequiredKeyedService("tenant3"));
});
}
}
diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md
index 5fb3a372f..db9f0cd76 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/handlers/index.md b/docs/guide/handlers/index.md
index d6bb547b3..2594533a6 100644
--- a/docs/guide/handlers/index.md
+++ b/docs/guide/handlers/index.md
@@ -416,6 +416,13 @@ public static class ShipOrderHandler
snippet source | anchor
+::: warning
+You may need to use separate handlers for separate messages if you are wanting to use `Before/After/Validate/Load` methods that
+target a specific message type. Wolverine is not (yet) smart enough to filter out the application of the implied middleware by
+message type and may throw exceptions on code compilation in some cases. Again, the easy work around is to just use separate message
+handler types for different message types in this case.
+:::
+
The naming conventions for what Wolverine will consider to be either a "before" or "after" method is shown below:
Here's the conventions:
diff --git a/docs/guide/http/endpoints.md b/docs/guide/http/endpoints.md
index bc143ff3b..355301f87 100644
--- a/docs/guide/http/endpoints.md
+++ b/docs/guide/http/endpoints.md
@@ -145,7 +145,7 @@ public static class TodoCreationEndpoint
}
}
```
-snippet source | anchor
+snippet source | anchor
In the case above, `TodoCreationResponse` is the first item in the tuple, so Wolverine treats that as
@@ -319,7 +319,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:
@@ -352,7 +352,59 @@ To make a single class and method be both a message handler and HTTP endpoint, j
with the route directly on your message handler. As long as that method follows Wolverine's normal naming rules for message
discovery, Wolverine will treat it as both a message handler and as an HTTP endpoint. Here's an example from our tests:
-snippet: sample_using_problem_details_in_message_handler
+
+
+```cs
+public static class NumberMessageHandler
+{
+ public static ProblemDetails Validate(NumberMessage message)
+ {
+ if (message.Number > 5)
+ {
+ return new ProblemDetails
+ {
+ Detail = "Number is bigger than 5",
+ Status = 400
+ };
+ }
+
+ // All good, keep on going!
+ return WolverineContinue.NoProblems;
+ }
+
+ // This "Before" method would only be utilized as
+ // an HTTP endpoint
+ [WolverineBefore(MiddlewareScoping.HttpEndpoints)]
+ public static void BeforeButOnlyOnHttp(HttpContext context)
+ {
+ Debug.WriteLine("Got an HTTP request for " + context.TraceIdentifier);
+ CalledBeforeOnlyOnHttpEndpoints = true;
+ }
+
+ // This "Before" method would only be utilized as
+ // a message handler
+ [WolverineBefore(MiddlewareScoping.MessageHandlers)]
+ public static void BeforeButOnlyOnMessageHandlers()
+ {
+ CalledBeforeOnlyOnMessageHandlers = true;
+ }
+
+ // Look at this! You can use this as an HTTP endpoint too!
+ [WolverinePost("/problems2")]
+ public static void Handle(NumberMessage message)
+ {
+ Debug.WriteLine("Handled " + message);
+ Handled = true;
+ }
+
+ // These properties are just a cheap trick in Wolverine internal tests
+ public static bool Handled { get; set; }
+ public static bool CalledBeforeOnlyOnMessageHandlers { get; set; }
+ public static bool CalledBeforeOnlyOnHttpEndpoints { get; set; }
+}
+```
+snippet source | anchor
+
If you are using Wolverine.HTTP in your application, Wolverine is able to treat `ProblemDetails` similar to the built in
`HandlerContinuation` when running inside of message handlers.
diff --git a/docs/guide/http/fluentvalidation.md b/docs/guide/http/fluentvalidation.md
index aa9d2c551..128698713 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/index.md b/docs/guide/http/index.md
index c7b198b32..591d1edf8 100644
--- a/docs/guide/http/index.md
+++ b/docs/guide/http/index.md
@@ -57,7 +57,7 @@ public class CreateTodoHandler
}
}
```
-snippet source | anchor
+snippet source | anchor
Okay, but we still need to expose a web service endpoint for this functionality. We *could* utilize Wolverine within an MVC controller
@@ -81,7 +81,7 @@ public class TodoController : ControllerBase
}
}
```
-snippet source | anchor
+snippet source | anchor
Or we could do the same thing with Minimal API:
@@ -96,7 +96,7 @@ app.MapPost("/todoitems", async (CreateTodo command, IMessageBus bus) =>
return Results.Created($"/todoitems/{todo.Id}", todo);
}).Produces(201);
```
-snippet source | anchor
+snippet source | anchor
While the code above is certainly functional, and many teams are succeeding today using a similar strategy with older tools like
@@ -121,7 +121,7 @@ efficient delegation to the underlying Wolverine message handler:
// code of 200 instead of 201. If you care about that anyway.
app.MapPostToWolverine("/todoitems");
```
-snippet source | anchor
+snippet source | anchor
The code up above is very close to a functional equivalent to our early Minimal API or MVC Controller usage, but there's a
@@ -173,7 +173,7 @@ public static class TodoCreationEndpoint
}
}
```
-snippet source | anchor
+snippet source | anchor
The code above will actually generate the exact same OpenAPI documentation as the MVC Controller or Minimal API samples
diff --git a/docs/guide/http/marten.md b/docs/guide/http/marten.md
index a235061de..b7f5f7f6a 100644
--- a/docs/guide/http/marten.md
+++ b/docs/guide/http/marten.md
@@ -367,7 +367,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 0a59c5c38..686bc213d 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 edb027456..3b2313523 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 0dc238a65..e0244e871 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,
@@ -273,7 +273,7 @@ public static class UpdateEndpoint
}
}
```
-snippet source | anchor
+snippet source | anchor
You'll notice that the `LoadAsync()` method is looking up the `Todo` entity for the route parameter, where Wolverine would
diff --git a/docs/guide/http/policies.md b/docs/guide/http/policies.md
index 2155f3f09..ccb9554f5 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 45b9ee9ae..179c4a096 100644
--- a/docs/guide/http/problemdetails.md
+++ b/docs/guide/http/problemdetails.md
@@ -35,7 +35,7 @@ public class ProblemDetailsUsageEndpoint
public record NumberMessage(int Number);
```
-snippet source | anchor
+snippet source | anchor
Wolverine.Http now (as of 1.2.0) has a convention that sees a return value of `ProblemDetails` and looks at that as a
@@ -148,30 +148,55 @@ from the tests:
```cs
-public static ProblemDetails Validate(NumberMessage message)
+public static class NumberMessageHandler
{
- if (message.Number > 5)
+ public static ProblemDetails Validate(NumberMessage message)
{
- return new ProblemDetails
+ if (message.Number > 5)
{
- Detail = "Number is bigger than 5",
- Status = 400
- };
+ return new ProblemDetails
+ {
+ Detail = "Number is bigger than 5",
+ Status = 400
+ };
+ }
+
+ // All good, keep on going!
+ return WolverineContinue.NoProblems;
}
-
- // All good, keep on going!
- return WolverineContinue.NoProblems;
-}
-// Look at this! You can use this as an HTTP endpoint too!
-[WolverinePost("/problems2")]
-public static void Handle(NumberMessage message)
-{
- Debug.WriteLine("Handled " + message);
- Handled = true;
+ // This "Before" method would only be utilized as
+ // an HTTP endpoint
+ [WolverineBefore(MiddlewareScoping.HttpEndpoints)]
+ public static void BeforeButOnlyOnHttp(HttpContext context)
+ {
+ Debug.WriteLine("Got an HTTP request for " + context.TraceIdentifier);
+ CalledBeforeOnlyOnHttpEndpoints = true;
+ }
+
+ // This "Before" method would only be utilized as
+ // a message handler
+ [WolverineBefore(MiddlewareScoping.MessageHandlers)]
+ public static void BeforeButOnlyOnMessageHandlers()
+ {
+ CalledBeforeOnlyOnMessageHandlers = true;
+ }
+
+ // Look at this! You can use this as an HTTP endpoint too!
+ [WolverinePost("/problems2")]
+ public static void Handle(NumberMessage message)
+ {
+ Debug.WriteLine("Handled " + message);
+ Handled = true;
+ }
+
+ // These properties are just a cheap trick in Wolverine internal tests
+ public static bool Handled { get; set; }
+ public static bool CalledBeforeOnlyOnMessageHandlers { get; set; }
+ public static bool CalledBeforeOnlyOnHttpEndpoints { get; set; }
}
```
-snippet source | anchor
+snippet source | anchor
This functionality was added so that some handlers could be both an endpoint and message handler
diff --git a/docs/guide/messaging/transports/rabbitmq/object-management.md b/docs/guide/messaging/transports/rabbitmq/object-management.md
index 93482718e..10397daf9 100644
--- a/docs/guide/messaging/transports/rabbitmq/object-management.md
+++ b/docs/guide/messaging/transports/rabbitmq/object-management.md
@@ -85,11 +85,7 @@ builder.Host.UseWolverine(opts =>
// just to see things work
opts.PublishAllMessages()
.ToRabbitExchange("issue_events", exchange => exchange.BindQueue("issue_events"))
- .UseDurableOutbox()
- // Even when calling AddResourceSetupOnStartup(), we still
- // need to AutoProvision to ensure any declared queues, exchanges, or
- // bindings with the Rabbit MQ broker to be built as part of bootstrapping time
- .AutoProvision();
+ .UseDurableOutbox();
opts.ListenToRabbitQueue("issue_events").UseDurableInbox();
@@ -99,7 +95,12 @@ builder.Host.UseWolverine(opts =>
// how you *could* customize the connection to Rabbit MQ
factory.HostName = "localhost";
factory.Port = 5672;
- });
+ })
+
+ // Even when calling AddResourceSetupOnStartup(), we still
+ // need to AutoProvision to ensure any declared queues, exchanges, or
+ // bindings with the Rabbit MQ broker to be built as part of bootstrapping time
+ .AutoProvision();;
});
// This is actually important, this directs
@@ -136,7 +137,7 @@ app.MapGet("/", () => "Hello World!");
// Actually important to return the exit code here!
return await app.RunJasperFxCommands(args);
```
-snippet source | anchor
+snippet source | anchor
Note that this stateful resource model is also available at the command line as well for deploy time
diff --git a/docs/guide/migration.md b/docs/guide/migration.md
index cdc400a9d..7e5dedd56 100644
--- a/docs/guide/migration.md
+++ b/docs/guide/migration.md
@@ -137,7 +137,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.Tests/Marten/working_against_multiple_streams.cs b/src/Http/Wolverine.Http.Tests/Marten/working_against_multiple_streams.cs
index fe0e2a66a..090816e27 100644
--- a/src/Http/Wolverine.Http.Tests/Marten/working_against_multiple_streams.cs
+++ b/src/Http/Wolverine.Http.Tests/Marten/working_against_multiple_streams.cs
@@ -1,4 +1,5 @@
using Marten;
+using Marten.Events;
using Shouldly;
using WolverineWebApi.Accounts;
@@ -83,4 +84,33 @@ await Scenario(x =>
x.StatusCodeShouldBe(404);
});
}
-}
\ No newline at end of file
+}
+
+#region sample_when_transfering_money
+
+public class when_transfering_money
+{
+ [Fact]
+ public void happy_path_have_enough_funds()
+ {
+ // StubEventStream is a type that was recently added to Marten
+ // specifically to facilitate testing logic like this
+ var fromAccount = new StubEventStream(new Account { Amount = 1000 })
+ {
+ Id = Guid.NewGuid()
+ };
+
+ var toAccount = new StubEventStream(new Account { Amount = 100})
+ {
+ Id = Guid.NewGuid()
+ };
+
+ TransferMoneyHandler.Handle(new TransferMoney(fromAccount.Id, toAccount.Id, 100), fromAccount, toAccount);
+
+ // Now check the events we expected to be appended
+ fromAccount.Events.Single().ShouldBeOfType().Amount.ShouldBe(100);
+ toAccount.Events.Single().ShouldBeOfType().Amount.ShouldBe(100);
+ }
+}
+
+#endregion
\ No newline at end of file
diff --git a/src/Http/WolverineWebApi/Accounts/AccountCode.cs b/src/Http/WolverineWebApi/Accounts/AccountCode.cs
index ea9becc95..0b4feb8fa 100644
--- a/src/Http/WolverineWebApi/Accounts/AccountCode.cs
+++ b/src/Http/WolverineWebApi/Accounts/AccountCode.cs
@@ -2,9 +2,12 @@
using Marten.Events;
using Wolverine.Http;
using Wolverine.Http.Marten;
+using Wolverine.Marten;
namespace WolverineWebApi.Accounts;
+#region sample_account_domain_code
+
public record AccountCreated(double InitialAmount);
public record Debited(double Amount);
public record Withdrawn(double Amount);
@@ -21,17 +24,25 @@ public static Account Create(IEvent e)
public void Apply(Withdrawn e) => Amount -= e.Amount;
}
+#endregion
+
+#region sample_TransferMoney_command
+
public record TransferMoney(Guid FromId, Guid ToId, double Amount);
-public static class TransferMoneyEndpoint
+#endregion
+
+#region sample_TransferMoneyEndpoint
+
+public static class TransferMoneyHandler
{
[WolverinePost("/accounts/transfer")]
- public static void Post(
+ public static void Handle(
TransferMoney command,
- [Aggregate(nameof(TransferMoney.FromId))] IEventStream fromAccount,
+ [WriteAggregate(nameof(TransferMoney.FromId))] IEventStream fromAccount,
- [Aggregate(nameof(TransferMoney.ToId))] IEventStream toAccount)
+ [WriteAggregate(nameof(TransferMoney.ToId))] IEventStream toAccount)
{
// Would already 404 if either referenced account does not exist
if (fromAccount.Aggregate.Amount >= command.Amount)
@@ -40,4 +51,7 @@ public static void Post(
toAccount.AppendOne(new Debited(command.Amount));
}
}
-}
\ No newline at end of file
+}
+
+#endregion
+