Using Preconditions
By default, Execute writes events without any preconditions. To add some, implement the Preconditions function on the command and return the preconditions to use, which makes the command Preconditioned. Create the preconditions with the functions of the client SDK.
If a precondition does not hold, nothing is written, and Execute returns an error of the category ErrConflict (see Handling Errors).
Preventing Duplicates#
If a command may only write events in case its subject does not yet have any events, use the NewIsSubjectPristinePrecondition function:
func (c AcquireBook) Preconditions() []eventsourcingdb.Precondition {
return []eventsourcingdb.Precondition{
eventsourcingdb.NewIsSubjectPristinePrecondition(c.Subject()),
}
}
Requiring an Existing Subject#
If a command may only write events in case its subject already has at least one event, use the NewIsSubjectPopulatedPrecondition function:
func (c ReturnBook) Preconditions() []eventsourcingdb.Precondition {
return []eventsourcingdb.Precondition{
eventsourcingdb.NewIsSubjectPopulatedPrecondition(c.Subject()),
}
}
Guarding Against Concurrent Changes#
If a command may only write events in case its subject has not changed since the caller last read it, use the NewIsSubjectOnEventIDPrecondition function. For that, add a field for the ID of the last event the caller has seen:
type BorrowBook struct {
BookID string
ReaderID string
BorrowedUntil string
ExpectedEventID string
}
func (c BorrowBook) Preconditions() []eventsourcingdb.Precondition {
return []eventsourcingdb.Precondition{
eventsourcingdb.NewIsSubjectOnEventIDPrecondition(c.Subject(), c.ExpectedEventID),
}
}
Note that the caller has to provide the event ID. A view can keep it for that purpose (see Defining Views).
Enforcing Rules Across Subjects#
If a command may only write events depending on an EventQL query, use the NewIsEventQLQueryTruePrecondition function. Preconditions can be combined, and all of them must hold. For example, to acquire every ISBN only once, extend the preconditions of AcquireBook:
func (c AcquireBook) Preconditions() []eventsourcingdb.Precondition {
return []eventsourcingdb.Precondition{
eventsourcingdb.NewIsSubjectPristinePrecondition(c.Subject()),
eventsourcingdb.NewIsEventQLQueryTruePrecondition(fmt.Sprintf(
"FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-acquired' AND e.data.isbn == '%s' PROJECT INTO COUNT() == 0",
c.ISBN,
)),
}
}
Note that the query must return a single row with a single value, which is interpreted as a boolean.