---
title: Style Guidelines
weight: 2
---

Follow the [Protobuf style guidelines](https://developers.google.com/protocol-buffers/docs/style). What follows are additional guidelines required by the Event Bus.

## Indentation

Use two-space indentation. This is consistent with the official protobuf library as well as Twirp.

## Comments

Comments are required on all messages and enums defined in the protobuf file. These comments should answer "what" or "when". Do not write comments that answer "who", "where", or "why" -- there are utilities in the Event Bus to answer these questions programmatically.

## Ownership

Every EventBus event schema must provide a valid Twitch LDAP group specifying the owner. This group must start with `team-`. Belonging to this LDAP group grants certain permission management capabilities, as described [here]({{< ref "schema/event_type_ownership.md" >}}).

Specify an owning LDAP group through a message level protobuf option in the top-level event schema definition, as in the following example:

```
import "eventbus/owner.proto";

message DogCreate {
  option (eventbus.owner.ldap_group) = "team-dog-lovers";

  // The doggo's name
  string name = 1;
  // Always yes
  bool is_good_boy = 2;
}
```

## Event Naming {#naming}

All events must abide by the following two naming rules:

1) Event names must start with a clear and specific noun denoting the entity. For example, `Follow` could be an ambiguous noun as people can potentially follow different things, so `UserFollowUser` more clearly represents when a user follows another user, and `UserFollowGame` for following a game.

2) The event name must end in a present tense verb. The most common verbs are "create", "update", "delete" but there are other commonly-used verbs listed in the next section.

Note that your service name _does not_ appear in the event name. This is intentional. Ownership of data will naturally migrate between services over time, so the Event Bus dashboard displays which service is the current publisher for every event stream.

### Common Verbs

This list is not exhaustive, and other verbs may make sense for special cases, but the vast majority of events should be covered by these verbs.

Create
: Signals a new entity was created. Usually contains an `id` field which is a distinct identifier of this entity.

Update
: Signals an entity was mutated in some way. Usually contains the same `id` as the create, plus fields denoting what was updated.

Delete
: Signals that an entity is no longer accessible because it was removed.

Undelete
: Sometimes, it is possible for a deletion to be reversed, and this verb can be used to denote that. e.g.: when a twitch user had first requested their account deleted, then comes back to re-activate their account.

Destroy
: Signals a permanent removal of data, such as for compliance reasons.

## Triggered by Data Changes

Events are designed to indicate when changes happen to underlying data. From a high-level, one can imagine a `create` event triggering when a database entry is added, `update` event when it’s modified, and `delete` event when it’s removed.

Although your data model might not technically be architected in this way, understand that subscribers internalize this model when they think about consuming events. Try to design your event schemas to be consistent with subscriber expectations, rather than how your service is designed today.

## Provide Enough Context

An event won’t always contain all of the data a subscriber needs for their feature. If it did, then that event would require constant modification to keep up with ever-changing feature requirements across the entire company. This would introduce too much churn and fragility.

Instead, subscribers must acknowledge that it’s acceptable for events to sometimes be missing fields that their feature needs. It’s the responsibility of the subscribers to make the API calls necessary to fill-in the additional data their feature requires. However, it’s the responsibility of the publisher to include enough context in the event for subscribers to do this successfully.

For example, if you wish to publish an event for when a user follows a broadcaster, that event must minimally include the user IDs of the follower and the broadcaster. If a subscriber needs additional information (e.g. the username of the broadcaster), the subscriber can retrieve that information by calling the relevant APIs.

## Order Agnostic

Events are not always processed immediately and not always processed in order. When designing your event, try to not include fields that rely on timeliness or order to be useful.

For example, it might be tempting to add a “total followers” field to an event that triggers when a user follows a broadcaster. However if a subscriber is tracking this “total followers” field to update their feature, receiving events out-of-order will cause errors in their logic.

## Default Values {#default-values}

Consider default value behavior for forwards/backwards compatibility. In general, if a field is used and omitted by a producer, it is interpreted as the default value (`0`, `false`, `""`, empty list, etc). When possible, design your schema such that the default value makes sense.

For example, consider an event schema for when a user sends a whisper to another user. Today these whispers are raw text, but imagine we add the ability to support markdown in the future. We could add an `is_markdown` field to the event schema. Because `is_markdown` will default to `false`, implicitly all past messages will be treated as `is_markdown = false`.

Alternatively, we can create an enum where the zero value is the original format:

```protobuf
enum MessageFormat {
  TEXT = 0;
  MARKDOWN = 1;
}
```

## Sub-Messages

If something needs to be optional as a concept, but requires some other companion field(s) to be set, then consider a message to encapsulate them all. Take this set of fields for example:

```protobuf
message Person {
  string name = 1;

  // Latitude and longitude must both be set, or neither set.
  float64 latitude = 2;
  float64 longitude = 3;
}
```

This is better represented as:

```protobuf
message Person {
  string name = 1;
  Location location = 2;
}

message Location {
  float64 latitude = 1;
  float64 longitude = 2;
}
```

## Change Types for Update Events

For Update events, subscribers will often care about _what_ changed. For example, when a user changes their profile settings, was it the email, phone number, language, description, or profile image that changed? If a subscriber only cares about profile images, changes to other fields shouldn't cause the subscriber to repeat computation or trigger unnecessary work.

To accomodate this, the Event Bus ships with [change types](https://git.xarth.tv/eventbus/schema/blob/master/proto-lib/eventbus/change.proto). For example:

```protobuf
message SomeUpdate {
  eventbus.change.BoolChange foo   = 1;
  eventbus.change.Int64Change bar  = 2;
  eventbus.change.StringChange baz = 3;
}
```

This allows subscribers to easily determine whether or not a particular field changed between updates:

```golang
if event.Foo.Updated {
  fmt.Println("New value of foo is:", event.Foo.Value)
}
```

We recommend using these change types on every field that can be mutated. For an example of this in production, take a look at the [UserUpdate](https://git.xarth.tv/eventbus/schema/blob/master/events/user/update.proto) event.

If you require a change type that does not already exist, you can create your own! For example, the [UserMultiFactorAuthUpdate](https://git.xarth.tv/eventbus/schema/blob/master/events/user_multi_factor_auth/update.proto) event added a custom `StatusChange` type.

## Undefined Growth Enums

Avoid undefined growth enums. These are enums that might grow over time and have unclear characteristcs when they change in size. For example:

```protobuf
enum AwsRegion {
  US_EAST_1 = 0;
  US_EAST_2 = 1;
  US_WEST_1 = 2;
  ...
}
```

When an AWS region is added in the future, subscribers will have language-variable behavior in what is decoded when using an older version of the schema. They may get no value or they may get some specific value.

At minimum, set the *zero value* to something of a flag value. For example:

```protobuf
enum AwsRegion {
  UNDEFINED = 0;
  US_EAST_1 = 1;
  US_EAST_2 = 2;
  US_WEST_1 = 3;
  ...
}
```
That said, this particular enum on AWS regions may be better served by a lookup, as detailed in the next section.

## Unnecessary Enums {#unnecessary-enums}

Don't recreate enums that are already available elsewhere. For example:

```protobuf
enum USState {
  ALASKA = 1;
  ALABAMA = 2;
  ...
}
```

It is preferable in these cases to use a standardized value that can be looked up in a database, especially ones that ship in common language libraries:

```protobuf
message Foo {
  // Two letter country code as an ISO-3166-1 Alpha-2
  // e.g. 'us', 'ca'
  string country_code = 1;
}
```

Good examples of these candidates include:

* Country/region info as above in [ISO-3166](https://en.wikipedia.org/wiki/ISO_3166) (and -1, -2)
* Timezones in [IANA (Olson) tz database](https://www.iana.org/time-zones)
* any string that is codified in an RFC (like HTTP verbs in RFC 7231)
* In general, if something has any risk of having to then be re-mapped by hand to _another_ enum, consider if the string representation of the value is instead more useful.

## Further Reading

Check out our [Schema Evolution]({{< ref "schema/evolution.md" >}}) guide for a walkthrough of a schema going through various changes over time and how to implement these changes.
