Skip to content
Aaron Hanusa edited this page Feb 6, 2021 · 35 revisions

ServiceCommand is an actor responsible for orchestrating the execution of initialization logic, validation/business rule execution, and command logic (data proxy invocations, workflow logic, etc.), respectively. ServiceCommand inherits from CommandBase, and instances of it are returned from the public methods of ServiceBase.

ServiceCommand is meant to serve as an easy way to deliver custom command functionality from your service implementations without having to create your own concrete command class implementations. However, if you don't find ServiceCommand to be flexible enough, you can also create your own commands that inherit from CommandBase or alternatively, implement ICommand.

ServiceCommand uses a functional design pattern that wraps functions (Func) and actions (Action) that participate in the command execution pipeline. This pattern was used to offer a quick and easy way to expose command methods from your service classes.

ServiceCommand exposes many contructor overloads, each requiring different variations of arguments of type Func and Action covering different use cases. For example, many times you will not need initialization logic or business rules to execute before executing command logic and interacting with data proxies. Other times, you may want to inject initialization logic into the command execution pipeline, but forego business rule validation.

Below are a couple of examples of service class methods that consume ServiceCommand:

A simple example:

public ICommand<InventoryItem> GetByProductCommand(long productID)
{
    var proxy = DataProxy as IInventoryItemDataProxy;
    return new ServiceCommand<InventoryItem>
    (
        executeMethod: () => proxy.GetByProductAsync(productID)
    );
}

In this example, we expose the GetByProductCommand method that returns ICommand<InventoryItem>. Within this method, we instantiate a new ServiceCommand (ServiceCommand<InventoryItem>) passing in a function for the command execution method. This function simply interacts with the service's data proxy to retrieve an inventory item.

Note that in this example, we chose the constructor overload that only accepts the execution method, as we don't require any initialization logic or validation of any validation/business rules in our execution pipeline. Also note that we include the parameter names with the constructor args for better readability and that this practice is optional.

A busier example

Here is an example that specifies the logic to execute during command execution and injects business rules into the command execution pipeline:

public ICommand<OrderItem> SubmitCommand(long orderItemID)
{
    var proxy = DataProxy as IOrderItemDataProxy;
    return new ServiceCommand<OrderItem>
    (
        initializationAsyncMethod: () => _logger.LogInfoAsync($"Submit Command Request Intialized at {DateTime.Now}"),
        getBusinessRulesMethod: () => GetBusinessRulesForSubmitAsync(orderItemID),
        executeMethod: () => proxy.SubmitAsync(orderItemID, DateTime.Now),
    );
}

private async Task<IEnumerable<IRule>> GetBusinessRulesForSubmitAsync(long orderItemID)
{
    var orderItem = await DataProxy.GetByIDAsync(orderItemID);
    return new[] { new CanSubmitOrderItemRule(orderItem) };
}

In this example, we create an instance of ServiceCommand, but this time we supply a method implementations for all of the command execution pipeline methods.

Clone this wiki locally