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 +