Command Processing
Every action invocation (and property edit for that matter) is normally reified into a concrete Command object, basically a wrapper around the CommandDto that also captures some timing metrics about the execution as well as the outcome.
The main uses cases are:
-
as a means to allow asynchronous child commands to be executed, using the WrapperFactory service;
-
as a means to audit (persist) commands, by implementing the CommandSubscriber SPI.
The Command Log extension does provide such an implementation.
Another option to achieve this is to use the ExecutionSubscriber SPI.
The commandPublishing() element can be used to explicitly enable or disable command publishing for the action invocation.
CommandDtoProcessor implementations
The commandDtoProcessor() element allows an implementation of CommandDtoProcessor to be specified.
This interface has the following API:
public interface CommandDtoProcessor {
CommandDto process( (1)
CommandDto dto); (2)
}
| 1 | The returned CommandDto.
This will typically be the CommandDto passed in, but may be supplemented in some way. |
| 2 | The CommandDto obtained already from the Command. |
This interface is used by the framework-provided implementations of ContentMappingService for the REST API, allowing Commands implementations that also implement CommandWithDto to be further customised as they are serialized out.
The primary use case for this capability is in support of primary/secondary replication.
-
on the primary, Commands are serialized to XML. This includes the identity of the target object and the argument values of all parameters.
Any
Blobs andClobs are deliberately excluded from this XML (they are instead stored as references). This is to prevent the storage requirements for Command from becoming excessive. ACommandDtoProcessorcan be provided to re-attach blob information if required. -
replaying Commands requires this missing parameter information to be reinstated. The
CommandDtoProcessortherefore offers a hook to dynamically re-attach the missingBloborClobargument.
As a special case, returning null means that the command’s DTO is effectively excluded when retrieving the list of commands.
If replicating from master to slave, this effectively allows certain commands to be ignored.
The CommandDtoProcessor.Null class provides a convenience implementation for this requirement.
|
If |
Example
Consider the following method:
@Action(
domainEvent = IncomingDocumentRepository.UploadDomainEvent.class,
commandDtoProcessor = DeriveBlobArg0FromReturnedDocument.class
)
public Document upload(final Blob blob) {
final String name = blob.getName();
final DocumentType type = DocumentTypeData.INCOMING.findUsing(documentTypeRepository);
final ApplicationUser me = meService.me();
String atPath = me != null ? me.getAtPath() : null;
if (atPath == null) {
atPath = "/";
}
return incomingDocumentRepository.upsertAndArchive(type, atPath, name, blob);
}
The Blob argument will not be persisted in the memento of the Command, but the information is implicitly available in the Document that is returned by the action.
The DeriveBlobArg0FromReturnedDocument processor retrieves this information and dynamically adds:
public class DeriveBlobArg0FromReturnedDocument
extends CommandDtoProcessorForActionAbstract {
@Override
public CommandDto process(Command command, CommandDto commandDto) {
final Bookmark result = commandWithDto.getResult();
if(result == null) {
return commandDto;
}
try {
final Document document = bookmarkService.lookup(result, Document.class);
if (document != null) {
ParamDto paramDto = getParamDto(commandDto, 0);
CommonDtoUtils.setValueOn(paramDto, ValueType.BLOB, document.getBlob(), bookmarkService);
}
} catch(Exception ex) {
return commandDto;
}
return commandDto;
}
@Inject
BookmarkService bookmarkService;
}
Null implementation
The null implementation can be used to simply indicate that no DTO should be returned for a Command. The effect is to ignore it for replay purposes:
pubc interface CommandDtoProcessor {
...
class Null implements CommandDtoProcessor {
public CommandDto process(Command command, CommandDto commandDto) {
return null;
}
}
}