Search code examples
node.jsdomain-driven-designcqrsevent-sourcingwolkenkit

How do I make sure an aggregate exists before publishing an event?


I was looking through the Wolkenkit API ... and it's not 100% clear how to know which commands require an aggregate ID and which ones do not.

From what I can tell, the client api offers something like this

app.accounting.invoice().issue({
  amount: 1000
});

which is fine for creating a new invoice, but it shouldn't be possible to run the following that intends to update something that exists

app.accounting.invoice().update({
  amount: 10
});

I assume this check should go into the command function, but how do I write it?

const commands = {
  update (invoice, command, mark) {
    const canInvoiceBeUpdated = // ...

    if (!canInvoiceBeUpdated) {
      return mark.asRejected('...');
    }

    // ... update invoice

    mark.asDone();
  }
};

What goes into canInvoiceBeUpdated check?


Solution

  • Answered 2018-06-08 by @goloroden in the wolkenkit slack

    I'll try to explain it to you: If you want a new aggregate, you omit the ID. So, e.g., to stick with the chat example, when you want to send a new message you do:

    app.communication.message().send({ /* ... */ });
    

    If, instead, you want to edit an existing message, e.g. to like it, then you have to provide the ID of the message:

    const messageId = '...';
    
    app.communication.message(messageId).like({ /* ... */ });
    

    Within every command you will probably want to check that it only works on a new aggregate (which we call a constructor command) or only on an existing aggregate. The easiest way to check this is to use the aggregate's exists function, which returns true for new aggregates, and false otherwise. So, inside of a command, you could do something like this:

    const commands = {
      send (message, command, mark) {
        if (!message.exists()) {
          return mark.asRejected('Aggregate does not exist yet.');
        }
    
        // ...
    
        mark.asDone();
      }
    };
    

    If you don't want to do this manually every time, you could also use a middleware for this, such as https://github.com/thenativeweb/wolkenkit-command-tools … then the previous example comes down to:

    const { only } = require('wolkenkit-command-tools');
    
    // ...
    
    const commands = {
      send: [
        only.ifExists(),
        (message, command, mark) {
          // ...
    
          mark.asDone();
        }
      ]
    };
    

    Please note that the current version of this middleware module is 3.0, but that until wolkenkit 2.0 will be released, you will have to use version 2.0 of wolkenkit-command-tools.