Implementing an Event Driven System in Go
Tags: events, go
Published: 2019-09-03 21:32:23

![Events in Go](https://i0.wp.com/stephenafamo.com/blog/wp-content/uploads/2019/09/Events-in-Go.jpg?fit=1500%2C750&ssl=1)

In this post, we will implement an event driven system in Go.

We are going to imagine a fictional application where we want to send out events for when a new account is created and another for when an account is deleted.

Let’s assume that the current structure of our program looks like this:

```
working-dir
|
|__auth.go/
| |__auth.go
|
|__main.go
|__go.mod
|__go.sum
```

We would like this system to:

* Be type safe. No `interface{}`, no need to type cast.
* Be able to define a custom payload for each event.

## Defining Events

To make it safe, we will create our events in a separate package and only export the complete events. Let’s call this package events. We’ll update our directory structure like this.

```
working-dir
|
|__auth.go/
| |__auth.go
|
|__events/
|__main.go
|__go.mod
|__go.sum
```

Each event would be a unique type and would define the required payload for each event. Every handler would explicitly know what data it will receive.

Here would be our event for when a user is created:

```go
// events/user_created.go
package events

import (
    "time"
)

var UserCreated userCreated

// UserCreatedPayload is the data for when a user is created
type UserCreatedPayload struct {
    Email string
    Time  time.Time
}

type userCreated struct {
    handlers []interface{ Handle(UserCreatedPayload) }
}

// Register adds an event handler for this event
func (u *userCreated) Register(handler interface{ Handle(UserCreatedPayload) }) {
    u.handlers = append(u.handlers, handler)
}

// Trigger sends out an event with the payload
func (u userCreated) Trigger(payload UserCreatedPayload) {
    for _, handler := range u.handlers {
        go handler.Handle(payload)
    }
}

```

We can then create another event for when a user is deleted:

```go
// events/user_deleted.go
package events

import (
    "time"
)

var UserDeleted userDeleted

// UserDeletedPayload is the data for when a user is Deleted
type UserDeletedPayload struct {
    Email string
    Time  time.Time
}

type userDeleted struct {
    handlers []interface{ Handle(UserDeletedPayload) }
}

// Register adds an event handler for this event
func (u *userDeleted) Register(handler interface{ Handle(UserDeletedPayload) }) {
    u.handlers = append(u.handlers, handler)
}

// Trigger sends out an event with the payload
func (u userDeleted) Trigger(payload UserDeletedPayload) {
    for _, handler := range u.handlers {
        go handler.Handle(payload)
    }
}

```

Our directory structure now looks like this:

```
working-dir
|
|__auth.go/
|   |__auth.go
|
|__events/
|   |__user_created.go
|   |__user_deleted.go
|
|__main.go
|__go.mod
|__go.sum
```

A good thing about this system is that the event variable types are not exported, therefore, they cannot be changed or assigned to something different outside the package.

## Listening for Events

To listen for an event, we import our `events` package and then `register` our handler to an event.

First, we create a listener that sends notifications to an admin and to slack when a user is created.

```go
// create_notifier.go
package main

import (
    "time"

    "github.com/stephenafamo/demo/events"
)

func init() {
    createNotifier := userCreatedNotifier{
        adminEmail: "the.boss@example.com",
        slackHook: "https://hooks.slack.com/services/...",
    }

    events.UserCreated.Register(createNotifier)
}

type userCreatedNotifier struct{
    adminEmail string
    slackHook string
}

func (u userCreatedNotifier) notifyAdmin(email string, time time.Time) {
    // send a message to the admin that a user was created
}

func (u userCreatedNotifier) sendToSlack(email string, time time.Time) {
    // send to a slack webhook that a user was created
}

func (u userCreatedNotifier) Handle(payload events.UserCreatedPayload) {
    // Do something with our payload
    u.notifyAdmin(payload.Email, payload.Time)
    u.sendToSlack(payload.Email, payload.Time)
}
```

Let’s add another listener that does the same when a user is deleted.

```go
// delete_notifier.go
package main

import (
    "time"

    "github.com/stephenafamo/demo/events"
)

func init() {
    createNotifier := userCreatedNotifier{
        adminEmail: "the.boss@example.com",
        slackHook: "https://hooks.slack.com/services/...",
    }

    events.UserCreated.Register(createNotifier)
}

type userCreatedNotifier struct{
    adminEmail string
    slackHook string
}

func (u userCreatedNotifier) notifyAdmin(email string, time time.Time) {
    // send a message to the admin that a user was created
}

func (u userCreatedNotifier) sendToSlack(email string, time time.Time) {
    // send to a slack webhook that a user was created
}

func (u userCreatedNotifier) Handle(payload events.UserCreatedPayload) {
    // Do something with our payload
    u.notifyAdmin(payload.Email, payload.Time)
    u.sendToSlack(payload.Email, payload.Time)
}
```

Now, we will have a directory structure that looks like this:

```
working-dir
|
|__auth.go/
|   |__auth.go
|
|__events/
|   |__user_created.go
|   |__user_deleted.go
|
|__create_notifier.go
|__delete_notifier.go
|__main.go
|__go.mod
|__go.sum
```

## Triggering Events

Now that we have our listeners set up, we can then trigger these events from our `auth` package \(or anywhere else\).

```go
// auth.go
package auth

import (
    "time"

    "github.com/stephenafamo/demo/events"
    // Other imported packages
)

func CreateUser() {
    // ...
    events.UserCreated.Trigger(events.UserCreatedPayload{
        Email: "new.user@example.com",
        Time: time.Now(),
    })
    // ...
}

func DeleteUser() {
    // ...
    events.UserDeleted.Trigger(events.UserDeletedPayload{
        Email: "deleted.user@example.com",
        Time: time.Now(),
    })
    // ...
}
```

## Conclusion

We saw a way to define events in a type safe manner, how to listen for these events and how to trigger them.

Nothing fancy. As with all things Go, a good solution is a boring solution.

The post [Implementing an Event Driven System in Go](https://stephenafamo.com/blog/implementing-an-event-driven-system-in-go/) appeared first on [Stephen AfamO's Blog](https://stephenafamo.com/blog).