This post is part of the series SOLID Wash Tunnel.

Definition

"Command Message pattern is used to invoke functionality provided by other applications. It would typically use Remote Procedure Invocation by taking advantage of Messaging."

- Enterprise Integration Patterns

I want to start by saying that the command message pattern is not only used to invoke functionality from other applications, but also from within the same application. For example a different module, which is still part of the same running process.

One application (or module) constructs a command, which is basically a message or put simplier just an object. The command is sent over to a message bus of some kind. The bus can be an in-memory implementation, or a sepparate process.

An other application (or module) is subscribed to the bus and receives the command, upon which it handles it based on some business logic.

UML class diagram.

Command Message vs Command

We should not confuse command message pattern with the GoF definition of the command pattern!

In the GoF definition the command itself is responsible to execute an operation. Which is not the case in the command message pattern, where the execution part of it, is deferred to a command handler which is typically in a different application, but may very well be within the same application.

It is important to mention that they do share some similarities.

Stand-alone Objects

A commands is a method call turned into a stand-alone object. This promotes sepparation of concern, loose coupling, and provides us with capabilities like:

  • Passing commands as method arguments.
  • Storing commands inside other objects (in-memory), files or databases.
  • Queueing commands.
  • Scheduling commands.
  • Command execution interopability via serialization and execution in-or-out of process.
  • Command invocation decoupling from the object handling the invocation.
Reversible Operations

The command's execution can be stored for reversing its effects. In our implementation we will not be supporting reversible operations. But, generally speaking, there are two ways to implement it:

  • Keeping a history log of the executed commands, and replaying them up to a certain point, which runs untill (but not including) the failed command, so the state of the system gets restored.
  • Or when a command fails you can perform logical inverse or compensating operations to restore the state of the system.

Implementation

The command message pattern is used accross the codebase. Lets elaborate one use case, and relate it to the UML diagram above.

ISignal

ISignal is analogous to Command. It is an abstraction of any object that is a command.

public interface ISignal
{

}

WashProgramSelectedSignal

WashProgramSelectedSignal is analogous to ConcreteCommand. It is an implementation of ISignal that contains an IWashProgram that the user has selected. Remember we said that a command is a stand-alone object that contains all information needed to perform an operation. That information in this case is the Program property.

public class WashProgramSelectedSignal : ISignal
{
public IWashProgram Program { get; }

public WashProgramSelectedSignal(IWashProgram program)
{
Program = program;
}
}

ISignalHandler

ISignalHandler is analogous to CommandHandler. It is an abstraction of an object that receives a command and acts upon it. It contains one method Handle(T signal), where T is any object which is an ISignal.

public interface ISignalHandler<in T> : ISignalHandler
where T : ISignal
{
void Handle(T signal);
}

WashProgramSelectedSignalHandler

WashProgramSelectedSignalHandler is analogous to ConcreteCommandHandler. It is an implementation of ISignalHandler<in T> where T is WashProgramSelectedSignal. The handler implements the Handle method which in our case uses the injected IMemory and stores the WashProgramSelectedSignal inside of memory.

The class itself is defined within the WashProgramSelectedSignal command as a private class. This is a matter of preference, but I suggest following this approach because it promotes high cohesion.

public class WashProgramSelectedSignal : ISignal
{
...

private class WashProgramSelectedSignalHandler : ISignalHandler<WashProgramSelectedSignal>
{
private readonly IMemory _memory;

public WashProgramSelectedSignalHandler(IMemory memory)
{
_memory = memory;
}

public void Handle(WashProgramSelectedSignal signal)
=> _memory.SetOrOverride("WPSS", signal);
}
}

ISignalTransmitter

ISignalTransmitter is analogous to Bus. It is an abstraction of an object that dispatches or transmits a specific command. It contains one method Transmit<T>, where T is any object which is an ISignal.

public interface ISignalTransmitter
{
void Transmit<T>(T signal) where T : ISignal;
}

Motherboard

Motherboard is analogous to ConcreteBus. It is an implementation of ISignalTransmitter. In real life the bus or transmitter would be a motherboard, which is the housing of many electronic components, and represents the bus that those components use to communicate between each other. The communication is represented via signals which are basically commands.

We will elaborate more on the Motherboard, when we talk about the mediator pattern.

UserPanel

UserPanel is a class within Module A which uses the ISignalTransmitter to dispatch a WashProgramSelectedSignal command.

public class UserPanel : 
IUserPanel,
ICustomerInformationCollector,
IWashProcessStarter

{
private readonly ISignalTransmitter _transmitter;
private readonly IWashProgramFactory _programFactory;

public UserPanel(
ISignalTransmitter transmitter,
IWashProgramFactory programFactory)
{
_transmitter = transmitter;
_programFactory = programFactory;
}

...

public ICustomerInformationCollector SelectBuiltInProgram(ProgramType type)
{
IWashProgram program = _programFactory.Create(type);
_transmitter.Transmit(new WashProgramSelectedSignal(program));

return this;
}

...
}

Principles


PrincipleAppliedExplanation
SRPWashProgramSelectedSignalHandler deals only with handling WashProgramSelectedSignal command.
OCPAny handler implementing ISignalHandler is closed for modification, but open for extension. Often the handlers are extended to support logging via the decorator pattern.
LSPAny ISignal can be supplied to the ISignalTransmitter.Transmit<T> method.
ISPAny of the implementations of ISignalHandler, makes use of all the methods of ISignalHandler.
DIPAny of the ISignal implementations, does not depend on its handler. Although the handlers are defined within the signal classes themselves, the signals are not logically coupled to them.

Continue the series on Mediator.

If you found this article helpful please give it a share in your favorite forums 😉.
The solution project is available at GitHub.