Defining commands
To define a command add an appropriately named function to the commands
object of the aggregate the command refers to. This function receives two parameters, the aggregate instance and the command.
Inside of the function, add code that decides whether the command may be run. If you need to reject the command, call the command.reject
function and provide a reason.
E.g., to issue an invoice, use the following code:
const commands = {
issue (invoice, command) {
const canInvoiceBeIssued = // ...
if (!canInvoiceBeIssued) {
return command.reject('...');
}
// ...
}
};
Some commands require asynchronous code. Therefore, you can use the keywords async
and await
. To be able do this, define the command using the async
keyword:
const commands = {
async issue (invoice, command) {
// ...
const result = await validateInvoice();
// ...
}
};
For a detailed list of a command's properties, see the data structure of commands.
Reserved command names
Do not name a command
transferOwnership
orauthorize
, since these are reserved names.
Accessing the command data
To decide whether a command may be run you may need to access the command data. For that, use the command.data
property.
E.g., to verify whether the amount that is given in the issue
command is positive, use the following code:
if (command.data.amount > 0) {
// ...
}
File storage for large documents
Commands represent a user's request to the system. Although they contain data, they should not contain large documents such as PDFs, images or videos. If you want to store such documents, think about using file storage.
Accessing the aggregate state
To decide whether a command may be run you may need to access the aggregate state. For that, use the state
property of the aggregate.
E.g., to verify whether an invoice was already issued, use the following code (assuming that the aggregate state contains a property isIssued
set to true
):
if (invoice.state.isIssued) {
// ...
}
To get the aggregate ID, access the property id
of the aggregate directly, as the ID is not part of the state.
Publishing events
Typically you need to publish events to let the world know about the outcome of the command. For that, call the events.publish
function on the aggregate and provide the name and the data of the event.
E.g., to publish an issued
event for an invoice, use the following code:
invoice.events.publish('issued', {
amount: command.data.amount
});
If an event does not have any data, you can omit the second parameter:
invoice.events.publish('issued');
Verifying whether an aggregate exists
Some commands are intended to initialize new aggregates. Other commands, though, are supposed to change the state of existing aggregates. To ensure that commands are only executed if they are to be executed, you may need to determine whether you are dealing with a new or an existing aggregate. That's what the exists
function is for. Depending on the state of the aggregate, this function returns either true
or false
.
E.g., to verify whether an invoice aggregate already exists, use the following code:
if (invoice.exists()) {
// ...
}
Middlewares simplify things
Instead of calling the
exists
function manually, you can alternatively use a middleware such as wolkenkit-command-tools.The two functions
only.ifExists
andonly.ifNotExists
serve the same purpose, but can be integrated more elegantly.