Docs / ArchitectureKit / Guides / Versioning Events

Versioning Events

The database keeps the schema of an event type forever. If the shape of an event changes, introduce a new event type, and translate the stored events of the old type with an upcaster.

Suppose an earlier version of the library wrote events of the type io.eventsourcingdb.library.book-lent, with the fields lentTo and until. To translate them into BookBorrowed events, call the Upcast function on the state and hand over the old event type and a function that receives the stored event and returns the translated events:

bookState.Upcast(
  "io.eventsourcingdb.library.book-lent",
  func(event eventsourcingdb.Event) ([]eventsourcingdb.Event, error) {
    var old struct {
      LentTo string `json:"lentTo"`
      Until  string `json:"until"`
    }
    if err := json.Unmarshal(event.Data, &old); err != nil {
      return nil, err
    }

    data, err := json.Marshal(BookBorrowed{
      BorrowedBy:    old.LentTo,
      BorrowedUntil: old.Until,
    })
    if err != nil {
      return nil, err
    }

    event.Type = BookBorrowed{}.EventType()
    event.Data = data

    return []eventsourcingdb.Event{event}, nil
  },
)

The function has the type Upcaster. Upcasters run before the Evolve rules, and they may return more than one event. If a returned event has an upcaster of its own, that one runs as well, so every version needs only a single step to the next one. The translated events are never written back.

Note that calling Upcast twice for the same event type panics.

Note that upcasters only apply to the state. Projections receive events as they are stored.