Search
K
  1. Command Bus

In-Process Command Bus Pattern

Similarly to the Event Bus, you can take a decoupled, command driven approach with the distinction that a command can only have a single handler which may or may not return a result. Whereas an event can have many handlers and they cannot return results back to the publisher.

1. Define A Command

This is the data contract that will be handed to the command handler. Mark the class with either the ICommand or ICommand<TResult> interface in order to make any class a command. Use the former if no result is expected and the latter if a result is expected back from the handler.

public class GetFullName : ICommand<string>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

2. Define A Command Handler

This is the code that will be executed when a command of the above type is executed. Implement either the ICommandHandler<TCommand, TResult> or ICommandHandler<TCommand> interface depending on whether a result needs to be returned or not.

public class FullNameHandler : ICommandHandler<GetFullName, string>
{
    public Task<string> ExecuteAsync(GetFullName command, CancellationToken ct)
    {
        var result = command.FirstName + " " + command.LastName;
        return Task.FromResult(result);
    }
}

3. Execute The Command

Simply call the ExecuteAsync() extension method on the command object.

var fullName = await new GetFullName()
{
    FirstName = "john",
    LastName = "snow"
}
.ExecuteAsync();

Generic Commands & Handlers

Generic commands & handlers require a bit of special handling. Say for example, you have a generic command type and a generic handler that's supposed to handle that generic command such as the following:

//command
public class MyCommand<T> : ICommand<IEnumerable<T>> { ... }

//handler
public class MyCommandHandler<T> : ICommandHandler<MyCommand<T>, IEnumerable<T>> { ... }

In order to make this work, you need to register the association between the two with open generic types like so:

app.Services.RegisterGenericCommand(typeof(MyCommand<>), typeof(MyCommandHandler<>));

Once registered, it's business as usual and you can execute generic commands such as this:

var results = await new MyCommand<SomeType>().ExecuteAsync();
var results = await new MyCommand<AnotherType>().ExecuteAsync();

Manipulating Endpoint Error State

By implementing command handlers using the CommandHandler<> abstract types instead of the interfaces mentioned above, you are able to manipulate the validation/error state of the endpoint that issued the command like so:

GetFullNameEndpoint.cs
public class GetFullNameEndpoint : EndpointWithoutRequest<string>
{
    ...

    public override async Task HandleAsync(CancellationToken c)
    {
        AddError("an error added by the endpoint!");

        //command handler will be adding/throwing it's own validation errors
        Response = await new GetFullName
        {
            FirstName = "yoda",
            LastName = "minch"
        }.ExecuteAsync();
    }
}
FullNameHandler.cs
public class FullNameHandler : CommandHandler<GetFullName, string>
{
    public override Task<string> ExecuteAsync(GetFullName cmd, CancellationToken ct = default)
    {
        if (cmd.FirstName.Length < 5)
            AddError(c => c.FirstName, "first name is too short!");

        if (cmd.FirstName == "yoda")
            ThrowError("no jedi allowed here!");

        ThrowIfAnyErrors();

        return Task.FromResult(cmd.FirstName + " " + cmd.LastName);
    }
}

In this particular case, the client will receive the following error response:

json
{
    "statusCode": 400,
    "message": "One or more errors occured!",
    "errors": {
        "GeneralErrors": [
            "an error added by the endpoint!",
            "no jedi allowed here!"
        ],
        "FirstName": [
            "first name is too short!"
        ]
    }
}

Dependency Injection

Dependencies in command handlers can be resolved as described here.


© FastEndpoints 2024