Docs / ArchitectureKit 2.0 / Fundamentals / Executing Commands

Executing Commands

Executing a command reads the state of its subject, has the decider decide on it, and writes the events the decider returns.

To execute a command, call the Execute function with a context, the store, the decider, and the command:

writtenEvents, err := architecturekit.Execute(
  context.TODO(),
  store,
  acquireBook,
  AcquireBook{
    BookID: "42",
    Title:  "2001 – A Space Odyssey",
    Author: "Arthur C. Clarke",
    ISBN:   "978-0756906788",
  },
)
if err != nil {
  // ...
}

Execute reads the events of the command's subject, evolves the state from them, calls the decider, and writes the events it returns. The function returns the written events, including the fields added by the server. If the decider returns no events, nothing is written, and the function returns nil.

If one of the events the decider returns is nil, Execute writes none of them, and fails with an error of the category ErrPermanent that names the index of the event, since an event that is nil has neither a type nor data. A nil pointer counts as nil, too, such as a *BookBorrowed that was declared but never set. The test fixture reports such an event the same way (see Testing Deciders).

Every event the decider returns needs a rule on the state of the decider, an Evolve function or Ignore. Execute writes the events to the subject that the same state reads for the next command, and the database keeps every event, so an event without a rule would leave a subject that the state can not read any more. If the state has no rule for one of the events, Execute writes none of them, and fails with an error of the category ErrPermanent that names the event type (see Handling Errors). The test fixture reports such an event the same way (see Testing Deciders).

Nor does Execute write any of the events if the data of one of them can not be encoded as JSON, for example because it holds a float NaN. It then fails with an error of the category ErrPermanent that names the event type and the reason, since trying again would fail the same way. Execute checks all events for nil first, then all of them for a rule, and encodes them last. The test fixture checks in the same order, and reports such an event the same way (see Testing Deciders).

The written events come as they are stored, with their data as JSON. To read the data of one of them, call the Decode function with the type of the event. It returns an Envelope, the same a projection gets (see Defining Projections), with the data in its Data field:

var titles []string

for _, event := range writtenEvents {
  switch event.Type {
  case (BookAcquired{}).EventType():
    acquired, err := architecturekit.Decode[BookAcquired](event)
    if err != nil {
      // ...
    }

    titles = append(titles, acquired.Data.Title)
  }
}

Note that Decode fails with an error of the category ErrPermanent for an event of another type, rather than leaving the fields of the wrong struct empty, and for data that does not fit the type. A pointer as the type, such as *BookAcquired, or an interface, such as architecturekit.Event, makes it panic, as with Evolve.

Note that Execute only reads the events of the command's subject itself, not those of nested subjects.