Docs / ArchitectureKit 1.0 / Fundamentals / Defining State

Defining State

The state holds what a command needs to decide on.

Define it as a struct, call the NewState function with its initial value, and call the Evolve function for every event type that changes it:

type Book struct {
  IsAcquired bool
  IsBorrowed bool
}

var bookState = architecturekit.NewState(Book{}).
  Evolve(func(book Book, event BookAcquired) Book {
    book.IsAcquired = true
    return book
  }).
  Evolve(func(book Book, event BookBorrowed) Book {
    book.IsBorrowed = true
    return book
  }).
  Evolve(func(book Book, event BookReturned) Book {
    book.IsBorrowed = false
    return book
  })

The event type is taken from the event's EventType function, so it does not have to be repeated.

Note that calling Evolve twice for the same event type panics, and so does calling it with nil as the function, or with a pointer as the event type, such as *BookBorrowed rather than BookBorrowed, or with an interface, such as architecturekit.Event.

Every read starts from a copy of the initial value, so an Evolve function may change the state it gets without changing what the next read starts from. A copy shares nothing with a value like Book{}, and neither with an initial value whose maps, slices, pointers and channels are nil, so leave them nil, and let the Evolve functions create them when they need them. If the initial value holds a map, a pointer or a channel that is not nil, or a slice with room for elements, every copy shares it, and the state needs a Clone function that copies it (see Caching States). Without one, reading the state fails with an error of the category ErrPermanent (see Handling Errors), rather than let an Evolve function change the initial value of every later read.

Reading an event without a rule fails, since it usually points to a missing rule or a wrong subject. If a subject holds events that matter for no decision, such as BookInspected, which only records that somebody looked at a book, call the Ignore function for their type. The state then takes them without changing, and says so, rather than an Evolve function that returns the state unchanged and needs a comment to explain why:

var bookState = architecturekit.NewState(Book{}).
  Evolve(func(book Book, event BookAcquired) Book {
    book.IsAcquired = true
    return book
  }).
  // ...
  Ignore[BookInspected]()

Note that the data of an ignored event is not decoded, but its schema is still part of Schemas, since the event is still written. Ignoring an event type that has an Evolve rule, or ignoring it twice, panics, and so does calling FromLatest for it. So does ignoring a pointer or an interface as the event type, as with Evolve.

Note that Execute refuses to write an event that the state of the decider has no rule for, since the state could not read the subject any more afterwards (see Executing Commands). To find out whether a state has a rule for an event type, call the HasRule function on the state with the event type.