# Defining Events and State

In this step, you define the **events** of a book, the **state** that is built from them, and register the **schemas** of the events with EventSourcingDB.

## Defining Events

An event describes something that has happened. In the library, a book goes through three of them: it is **acquired**, **borrowed**, and **returned**.

In ArchitectureKit, an event is a struct with JSON annotations that implements the **`EventType`** function, which returns the **event type**. Create a file `events.go`:

```go
package main

type BookAcquired struct {
  Title  string `json:"title"`
  Author string `json:"author"`
  ISBN   string `json:"isbn"`
}

func (BookAcquired) EventType() string {
  return "io.eventsourcingdb.library.book-acquired"
}

type BookBorrowed struct {
  BorrowedBy    string `json:"borrowedBy"`
  BorrowedUntil string `json:"borrowedUntil"`
}

func (BookBorrowed) EventType() string {
  return "io.eventsourcingdb.library.book-borrowed"
}

type BookReturned struct{}

func (BookReturned) EventType() string {
  return "io.eventsourcingdb.library.book-returned"
}
```

The struct becomes the **data** of the event. Other parts of an event are taken from elsewhere: the **subject**, which names the book an event belongs to, comes from the command that causes it, and the **source** from the store that you create below. The event types are written as **reverse domain names**, as EventSourcingDB requires (see **[Event Types](/docs/eventsourcingdb/event-types)**).

EventSourcingDB can check every event against a **JSON schema** of its type. ArchitectureKit derives that schema from the struct: it describes exactly the fields that the struct has, requires all of them, and allows no others. `BookReturned` carries no data, so its schema describes an empty object.

To constrain a value further than its Go type does, for example to require the return date to be a **date**, give its type a `Schema` function (see **[Describing Events with Schemas](/docs/architecturekit/go/describing-events-with-schemas)**). For details on events, see **[Defining Events](/docs/architecturekit/go/defining-events)**.

## Defining the State

To decide whether a book can be borrowed, you need to know whether it exists and whether it is borrowed right now. That is the **state** of a book, and it is built from the book's events.

Create a file `book.go`:

```go
package main

import "github.com/thenativeweb/architecturekit-golang/v2/architecturekit"

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
  })
```

**`NewState`** takes the initial value, and every call to **`Evolve`** says how an event changes the state. The event type is taken from the event's `EventType` function, so it does not have to be repeated.

Note that the state holds only what decisions need: neither the title nor the author of a book decides whether it can be borrowed, so the state leaves them out. Listing books with their titles is a job for the reading side, which you build in **[Defining a View and a Query](/learn/tutorials/first-steps-with-architecturekit/go/defining-a-view-and-a-query)**. For details, see **[Defining State](/docs/architecturekit/go/defining-state)**.

## Registering the Schemas

EventSourcingDB checks events against a schema only once the schema is registered. The state knows the schemas of all events it evolves by, and **`RegisterSchemas`** registers them through the **store**, which reads events from and writes them to EventSourcingDB.

Create a file `store.go` that connects to EventSourcingDB, creates the store, and registers the schemas:

```go
package main

import (
  "context"
  "net/url"

  "github.com/thenativeweb/architecturekit-golang/v2/architecturekit"
  "github.com/thenativeweb/eventsourcingdb-client-golang/v2/eventsourcingdb"
)

func newStore() (*architecturekit.Store, error) {
  baseURL, err := url.Parse("http://localhost:3000")
  if err != nil {
    return nil, err
  }

  client, err := eventsourcingdb.NewClient(baseURL, "secret")
  if err != nil {
    return nil, err
  }

  store := architecturekit.NewStore(client, "https://library.eventsourcingdb.io")

  err = architecturekit.RegisterSchemas(context.TODO(), store, bookState.Schemas())
  if err != nil {
    return nil, err
  }

  return store, nil
}
```

The second argument of **`NewStore`** is the **source** of all events that the store writes (see **[Sources](/docs/eventsourcingdb/sources)**). **`Schemas`** returns the schemas of all events the state evolves by, and **`RegisterSchemas`** registers them with EventSourcingDB.

To try it, create a file `main.go`:

```go
package main

import (
  "fmt"
  "log"
)

func main() {
  _, err := newStore()
  if err != nil {
    log.Fatal(err)
  }

  fmt.Println("Registered the event schemas.")
}
```

Run the program:

```shell
go run .
```

It prints:

```text
Registered the event schemas.
```

Run it a second time. The output is the same, because `RegisterSchemas` is meant to be called on every start: for an event type that EventSourcingDB knows already, it checks that the registered schema is exactly the one from the code.

To see what EventSourcingDB knows now, list its event types:

```shell
curl \
  -X QUERY \
  -H "authorization: Bearer secret" \
  http://localhost:3000/api/v2/data/read-event-types
```

The response contains the three event types, each with its schema, followed by an `end` item:

```json
{"type":"eventType","data":{"type":"io.eventsourcingdb.library.book-acquired","hasEvents":false,"schema":{"additionalProperties":false,"properties":{"author":{"type":"string"},"isbn":{"type":"string"},"title":{"type":"string"}},"required":["title","author","isbn"],"type":"object"}}}
{"type":"eventType","data":{"type":"io.eventsourcingdb.library.book-borrowed","hasEvents":false,"schema":{"additionalProperties":false,"properties":{"borrowedBy":{"type":"string"},"borrowedUntil":{"type":"string"}},"required":["borrowedBy","borrowedUntil"],"type":"object"}}}
{"type":"eventType","data":{"type":"io.eventsourcingdb.library.book-returned","hasEvents":false,"schema":{"additionalProperties":false,"properties":{},"type":"object"}}}
{"type":"end","data":{}}
```

All three are **phantoms**, with `hasEvents` set to `false`, since no events of these types have been written yet (see **[A Word on Phantom Event Types](/learn/tutorials/first-steps-with-eventsourcingdb/listing-event-types#a-word-on-phantom-event-types)**).

## Keeping Schemas Stable

A registered schema **can not change**. Suppose you added a field `Pages` to `BookAcquired` – its schema would then change, too, and the program would refuse to start, with a message that begins with the current date and time:

```text
2026/10/07 08:22:31 permanent failure: the schema of "io.eventsourcingdb.library.book-acquired" differs from the registered one, which cannot change; introduce a new event type and an upcaster instead
exit status 1
```

So leave the events as they are for this tutorial. To change the shape of an event in a real application, introduce a new event type, and translate the stored events of the old type with an upcaster (see **[Versioning Events](/docs/architecturekit/go/versioning-events)**). For details on schemas, see **[Registering Event Schemas](/docs/architecturekit/go/registering-event-schemas)**.
