DDD & co., part 6: From model to code

tl;dr: Having modeled a domain with DDD helps to write code that clearly expresses the domain and does not contain other aspects. However, the result is only a description of the domain in JavaScript, not executable software, as the technical foundation for the application is still missing.

The result of the fourth episode was a model of the TodoMVC domain. Its artefacts such as bounded contexts, aggregates, commands, and domain events are part of the domain, which is why a common scope must first be established.

This can be done easily by creating a new directory called todomvc. That is all you need for the domain because the domain itself does not execute any logic.

The same applies to the bounded context planning, which must therefore be created as a directory below todomvc.

Within this context, things become more interesting. The context is where the todo aggregate is located. The aggregate is responsible for the actual logic of the individual tasks. Therefore, the next step is to create the file todo.js (or course you can also use a different programming language than JavaScript, since the concepts can be transferred).

The domain and bounded contexts are directories, aggregates are files

In the file todo.js you can now create commands and domain events. For that you can define two sections, which need to be exported:

const commands = {};
const events = {};

module.exports = { commands, events };

The individual commands and domain events are implemented as functions. Since commands may contain asynchronous code, they require a callback. They also need access to the aggregate itself and the data contained in the command. This results in a signature as in the following example:

const commands = {
  note (todo, data, done) {
    // ...
  }
};

The note function must verify whether a title has been passed and, if successful, publish a noted event:

const commands = {
  note (todo, data, done) {
    if (!data.title) {
      return done(new Error('Title is missing.'));
    }

    todo.events.publish('noted', {
      title: data.title
    });

    done(null);
  }
};

In order to improve the readability of the code, the technical formulation of a done callback with an error object can be replaced by a more descriptive formulation. In addition to the actual data, commands can also contain metadata, which is why it makes sense to change the parameters accordingly:

const commands = {
  note (todo, command, mark) {
    if (!command.data.title) {
      return mark.asRejected('Title is missing.');
    }

    todo.events.publish('noted', {
      title: command.data.title;
    });

    mark.asDone();
  }
};

Domain events change the status

After the command has been implemented, the aggregate must also react to domain events. Events lead to changes in the state of the aggregate, which can be expressed as follows:

const events = {
  noted (todo, event) {
    todo.setState({
      title: event.data.title
    });
  }
};

Since the status change is always synchronous, there is no need for a callback at this point. Otherwise, the same considerations apply to the parameters as for the commands.

Using the state raises the question of how to initialize it. In addition to commands and events, there is another section called initialState, which also needs to be exported:

const initialState = {
  title: ''
};

const commands = {
  // ...
};

const events = {
  // ...
};

module.exports = { initialState, commands, events };

All other commands and domain events can be defined in the same way. The procedure also shows well which common state is managed by the aggregate - it is therefore easy to see where the transactional boundaries are, which is very important when modeling a domain.

Running the code?

The code developed so far is not yet an executable application. Instead, this is just a description of the domain in JavaScript.

In fact, however, the aggregate does not require any further code, since all other aspects such as access to the event store or a REST or a WebSocket API can be added from the outside.

This means that the code remains very close to the original domain modeling and can be read and, in some cases, evaluated even by a domain expert.

Twitter Facebook LinkedIn

Golo Roden

Founder, CTO, and managing partner

Since we want to deliver elegant yet simple solutions of high quality for complex problems, we care about details with love: things are done when they are done, and we give them the time they need to mature.