Documentation

About Events

This page describes the fundamentals of Events in Serialized.

Events capture something important that has happened in your application that should be stored. In contrast to an update command in a traditional database, each event contains a unique identifier, type, timestamp and data. This makes it possible to track each individual data change in your application over time. This page describes the fundamentals of Events in Serialized and their properties.

In the Serialized API, events are represented as JSON objects. In practice, events are typically defined in the programming language of your choice using a class or struct. The event is then serialized to JSON before it is sent to Serialized for storage. If you use the Serialized SDKs, this is done automatically for you.

The following is an example of an event in JSON format:

{  "eventId": "a2bcae3a-85f6-4a6c-a50f-936479cc17b4",  "eventType": "OrderPlaced",  "data": {    "orderId": "b1b0ee7c-c758-4f7b-8185-2bf599435eb9",    "orderAmount": 1000  }}

An event contains the following properties:

  • Id
  • Type
  • Timestamp
  • Data (optional)
  • Encrypted data (optional)

Each of these properties will now be described in more detail.

Event types

Event types are used to describe the type of event. Event types are string values that you define in your application. It is advised that you write them in a human-readable format.

The event type should be a string with alpha-numeric characters (- is also allowed). Choose your event types with care and always prefer business terms over technical words when selecting names for your event types. The event type should be written in past tense, since the event describes a change that has already happened. For example, OrderPlaced and GameFinished are examples of good event types.

Identity

Your events have a unique identifier that is of the format of UUID. You can optionally supply this ID in the event or omit it. If you don't provide an ID, Serialized will generate a random unique ID when the Event is stored.

{  "eventType": "OrderPlaced",  "eventId": "a2bcae3a-85f6-4a6c-a50f-936479cc17b4",  "data": {    "orderId": "b1b0ee7c-c758-4f7b-8185-2bf599435eb9",    "orderAmount": 1000  }}
{  "eventType": "OrderPlaced",  "data": {    "orderId": "b1b0ee7c-c758-4f7b-8185-2bf599435eb9",    "orderAmount": 1000  }}

Immutability

In Serialized you can not change an event that has been saved. To modify the current state you instead store another Event that represents that change. Each event is considered to be immutable.

Event data

The format of the data field is a generic JSON object, so you can store any structured data within this field. All JSON field types are supported and nesting is also supported.

This is an example of an event containing mixed data types and nested data:

{  "eventId": "a2bcae3a-85f6-4a6c-a50f-936479cc17b4",  "eventType": "OrderPlaced",  "data": {    "orderId": "b1b0ee7c-c758-4f7b-8185-2bf599435eb9",    "orderAmount": 1000,    "active": true,    "orderItems": [      {        "productId": "7a3e0996-2868-4229-800e-a209edc0b5de",        "quantity": 1      },      {        "productId": "e743f2c7-0182-4de0-bd78-9f9112745def",        "quantity": 2      }    ]  }}

Using our SDKs, the data field is automatically serialized to JSON when you store an event. This means that you can define the data field as a native object in your programming language and the SDK will take care of the serialization:

AggregateClient<OrderState> orderClient = createClient();var saveRequest = saveRequest().withAggregateId(orderId).withEvents(List.of(new OrderPlaced(orderId))).build()orderClient.save(saveRequest);
const orderClient = Serialized.create(config).aggregateClient(Order);await orderClient.save({orderId, events: [new OrderPlaced(orderId)]})

The data field is optional. Since events have a type that provide meaning to the application purely by their name, it is sometimes enough to store an event without additional data.

Event timestamps

Each event has a timestamp that is automatically set by Serialized when the event is stored. The timestamp is in the form of milliseconds since the Unix epoch (1970-01-01T00:00:00Z). For example, 1598931200000 represents the date Tue Sep 01 2020 03:33:20.

The timestamp is used primarily internally in Serialized. It is advised that you do not rely on the timestamp for any application logic, but instead use a timestamp that is generated by your server application and store it as part of the event data when your logic requires it.

{  "eventId": "a2bcae3a-85f6-4a6c-a50f-936479cc17b4",  "eventType": "OrderPlaced",  "data": {    "orderId": "b1b0ee7c-c758-4f7b-8185-2bf599435eb9",    "orderAmount": 1000,    "placedAt": 1598931200000  }}

This makes explicit what the timestamp represents and also makes it possible to use the timestamp when processing events downstream.

Saving events

Events are always stored as part of an Aggregate. The Aggregate encapsulates the process that the event is part of. The reason for this constraint is to provide a strict consistency mechanism.

For example, the business logic for an Order aggregate could contain the rules that multiple events of the type OrderConfirmed cannot be saved for the specific aggregate.

To read more on how to store events, continue to the section about aggregates.

Reading events

To provide the querying application with a better view, events are usually processed in some way before they are queried. To read more about how to process events into queryable data, see the section about Projections.

To read a raw stream of events, you can use Feeds. Feeds provide a way to read events from a specific aggregate type without processing.

Further reading