# Event system

# Quick guide

If you want to understand how events work in nuxt-ioc please skip this quick guide and go to Introduction section.

We suggest to put all the event classes into your domain directories with which they are connected. If the domain event is triggered from "ShoppingCart" domain, the event class should be there too.

Event class:

// @Domain/MyDomain/Event/SomethingHappenedEvent

export default class SomethingHappenedEvent {
  public eventField: string;
  public actionCount: number;

  // [...] any other fields on event payload you would need
}
1
2
3
4
5
6
7
8

Publisher:

// @Domain/MyDomain/Service/MyService

import { Injectable, Inject, Events } from 'nuxt-ioc';
import SomethingHappenedEvent from '@Domain/MyDomain/Event/SomethingHappenedEvent';

@Injectable()
export default class MyService {
  @Inject(Events)
  private gEvents: Events;

  public async someAction(): Promise<void> {
    // Here some action occured that changed the system state
    // this.anything();

    this.gEvents.trigger(SomethingHappenedEvent);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Subscriber / listener:

// @Domain/MyDomain/Service/MyOtherService

import { Injectable, Inject, Listen } from 'nuxt-ioc';
import SomethingHappenedEvent from '@Domain/MyDomain/Event/SomethingHappenedEvent';

@Injectable()
export default class MyOtherService {
  @Listen(SomethingHappenedEvent)
  public async reactToEvent(): Promise<void> {
    // react to the event somehow
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# Introduction

Sometimes our system executes an action that is important to other parts of the system. If this is the case there are two ways to go to inform these external systems:

# Command pattern

You inject the other class and fire a method on it. This is called a command pattern.

import { Injectable, Inject } from 'nuxt-ioc';
import MyRepository from './MyRepository';
import MyOtherService from '@Domani/OtherDomain/MyOtherService';

@Injectable()
export default class MyService {
  @Inject(MyRepository)
  private gMyRepository: MyRepository;

  @Inject(MyOtherService)
  private gMyOtherService: MyOtherService;

  public async someAction(): Promise<void> {
    // Here some action occured that changed the system state
    await this.gMyRepository.fireSomething();

    // Here we send command the other service to do some stuff
    await this.gMyOtherService.triggerSomeAction();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

This seems pretty obvious. But what is wrong with this approach? In fact - nothing.

Command pattern solves problems when one object depends directly on the other, but in some cases you might find yourself in a place, where one class needs to inform many other classes and that's how you create a dependency hell.

TIP

This is a very simple example and you should read more about command pattern on great refactoring.guru site.

Lets imagine dependency hell:

import { Injectable, Inject } from 'nuxt-ioc';
import MyRepository from './MyRepository';
import MyOtherService from '@Domani/OtherDomain/MyOtherService';
// [...] other imports

@Injectable()
export default class MyService {
  @Inject(MyRepository)
  private gMyRepository: MyRepository;

  @Inject(MyOtherService)
  private gMyOtherService: MyOtherService;

  @Inject(MySecondOtherService)
  private gMySecondOtherService: MySecondOtherService;

  @Inject(MyThirdOtherService)
  private gMyThirdOtherService: MyThirdOtherService;

  @Inject(MyFourthOtherService)
  private gMyFourthOtherService: MyFourthOtherService;

  public async someAction(): Promise<void> {
    // Here some action occured that changed the system state
    await this.gMyRepository.fireSomething();

    // And here we have command hell
    await this.gMyOtherService.triggerSomeAction();
    await this.gMySecondOtherService.triggerSomeAction();
    await this.gMyThirdOtherService.reactToSomeAction();
    await this.gMyFourthOtherService.notifyAboutSomeAction();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

The code above might seem "smelly" to you and you are right. From more science approach you can say that this class has low cohesion.

What does it mean? In simple words:

It means that one method in this class forces this class to depend on several other classes.

As you see, all those dependencies are injected only to fire them in this one, particular method.

You might probably recall programming good practice sentence:

High cohesion, low coupling

TIP

To explain cohesion and coupling we can use this great explanation:

Cohesion - how closely related everything is with one another.

Coupling - how everything is connected to one another.

Now we know this class has low cohesion and high coupling which is the oposite of what we want.

How can we change it then? How to solve our dependency problem?

# Events

To solve our problem we can use the observer pattern

TIP

You might also recognize this pattern as PubSub, Event Bus or Observer - they are a bit different in implementation but for simplicity of this guide we will leave those differences aside.

Take a look at the code below:

import { Injectable, Inject, Events } from 'nuxt-ioc';
import MyRepository from './MyRepository';
import SomeActionFiredEvent from '@Domain/MyDomain/Event/SomeActionFiredEvent';

@Injectable()
export default class MyService {
  @Inject(MyRepository)
  private gMyRepository: MyRepository;

  @Inject(Events)
  private gEvents: Events;

  public async someAction(): Promise<void> {
    // Here some action occured that changed the system state
    await this.gMyRepository.fireSomething();

    this.gEvents.trigger(SomeActionFiredEvent);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

We are sending a Domain Event informing that something happened. Any part of the system that should require on this system event can react to it and do its job.

MyOtherService.ts, MySecondOtherService.ts etc. can react like this

import { Injectable, Inject, Listen } from 'nuxt-ioc';
import SomeActionFiredEvent from '@Domain/MyDomain/Event/SomeActionFiredEvent';

@Injectable()
export default class MyOtherService {
  @Listen(SomeActionFiredEvent)
  public async triggerSomeAction(): Promise<void> {
    // here we do something after `SomeActionFiredEvent` fired

    this.gEvents.trigger(SomeActionFiredEvent);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

This pattern allows us to clean-up dependency hell from the publisher service (MyService) and react to this event in other classes e.g.: MyOtherService, MySecondOtherService, MyThirdOtherService, MyFourthOtherService etc.

Given classes do not know who is the event sender (publisher) but they just know how to react if this event is triggered.

What happened with our cohesion and coupling now? Classes depend only on what they really have to and can communicate using event system.

# Events philosophy

In classic PubSub pattern you would recognise two elements that make a message possible between publisher and subscriber:

  1. Message Topic
  2. Message itself (payload)

Let's take an example from nice PubSubJS library:

const mySubscriberCallback = (message) => {
  console.log(message);
};

PubSub.subscribe('MY TOPIC', mySubscriberCallback);

// publish a topic asynchronously
PubSub.publish('MY TOPIC', 'hello world!');
1
2
3
4
5
6
7
8

You might see that the topic and payload can be whatever you want. By convention many teams decide to use const values or enums to keep topics clean and without collisions.

enum Topic {
  FIRST_TOPIC = 'FIRST_TOPIC',
  SECOND_TOPIC = 'SECOND_TOPIC',
}

const mySubscriberCallback = (message) => {
  console.log(message);
};

PubSub.subscribe(Topic.FIRST_TOPIC, mySubscriberCallback);

// publish a topic asynchronously
PubSub.publish(Topic.FIRST_TOPIC, 'hello world!');
1
2
3
4
5
6
7
8
9
10
11
12
13

We can say that this is a good pattern but what about the message payload? Can we make it more structurized, typed maybe? No we can't. But here comes the solution.

If the message is build from TOPIC and MESSAGE where TOPIC should be UNIQUE and MESSAGE should have strict field set you can use a class.

class SomeActionFiredEvent {
  public message: string;
}
1
2
3

Hope you are not confused but class contains a unique name (this particular class) and a payload/message which are the class properties.

So how we can use it? Very easy:

export class SomeActionFiredEvent {
  public message: string;
}

@Injectable()
export default class MyOtherService {
  @Inject(Events)
  private gEvents: Events;

  public async triggerSomeAction(): Promise<void> {
    // here we do something after `SomeActionFiredEvent` fired

    this.gEvents.trigger(SomeActionFiredEvent, {
      message: 'Some string', // this is type-checked
    });
  }

  @Listen(SomeActionFiredEvent)
  public someSubscriber(payload: SomeActionFiredEvent) {
    console.log(payload.message); // this automatically suggest .message field and shows its type (string)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

That's it!

Of course your events may contain more complex types as payload, some examples below:

enum OptionsToSelect {
  OPTION_ONE,
  OPTION_TWO,
  OPTION_LAST,
}

export class SomeActionFiredEvent {
  public message: string;
  public selectedType: OptionsToSelct;
}
1
2
3
4
5
6
7
8
9
10

WARNING

Even this is a class representation of an Event we do not recommend to create methods on this class, even if this would work. An Event Class should only contain class properties, most likely all should be public.

# System Events

nuxt-ioc provides following, automatically triggered events that informs you about current Nuxt.js state

BeforeFrontRenderEvent - triggered just before field serialization