# Mediator

## About

It is like a middle-man or broker *(mediator)*, this pattern help reducing coupling between two or multiple Objects, by intruducing a `bus/channel` for communication, so that one Object can notify the other Object whenever it has to.

It provides separation of concerns and eliminates code duplication.

{% hint style="info" %}
A `Mediator` have a relationship of $$many \to many$$, meaning:

* `Many` Objects can notify/publish. *(One or multiple events)*
* `Many` Objects can subscribe and react to these events.
  {% endhint %}

{% hint style="success" %}
Depending on how you use it, `Mediator` can also act as [#observer](#observer "mention").

Ex.:

* If many different Objects generate events to many interested parties, it acts as `Mediator`.
* If only one Object generate events to many different parties, it acts as `Observer`.
  {% endhint %}

```typescript
interface Handler {
    // The Event name
    event: string;
    // What must be executed when this Event happens
    callback: Function
}

class Mediator {
    // The handlers are the interested parties
    handlers: Handler[];
    
    constructor() {
        this.handlers = [];
    }
    
    // Here we register events to the handlers and set what should execute when
    // the event happens.
    register(event: string, callback: Function) {
        this.handlers.push({ event, callback });
    }
    
    // When a notify of a specific event is called, all handlers that subscribed
    // to this event get notified.
    async notify(event: string, data: any) {
        for (const handler of this.handlers) {
            if (handler.event === event) await handler.callback(data);
        }
    }
}
```

## Chaining Usecases Example

In this example, lets suppose that we have these three usecases (`FinishRide`, `ProcessPayment` and `GenerateInvoice`), and the last two should be called after the Ride ends.

To avoid injecting and calling `ProcessPayment` and `GenerateInvoice` inside `FinishRide` class, we use a `Mediator` to generate the nofication that the Ride ended.

### **Notify from Application Layer**

<pre class="language-typescript" data-title="Usecases"><code class="lang-typescript"><strong>export class FinishRide {
</strong>    @inject('mediator')
    mediator?: Mediator;
    
    async execute(input: Input): Promise&#x3C;void> {
        // Get the Ride to be finished and put it in a Domain Object
        const ride = await this.rideRepository.getRideById(input.rideId);
        // Mutate the Domain Object
        ride.finish();
        // Persist the Domain Object
        await this.rideRepository.updateRide(ride);
        // Notify the Ride Completion
        await this.mediator.notify('rideCompleted', { rideId: ride.getRideId(), fare: ride.getFare() });
    }
}

export class ProcessPayment {
    async execute(): Promise&#x3C;void> { ... }
}

export class GenerateInvoice {
    async execute(): Promise&#x3C;void> { ... }
}
</code></pre>

{% code title="Main.ts" %}

```typescript
const processPayment = new ProcessPayment();
const generateInvoice = new GenerateInvoice();
const mediator = new Mediator();
mediator.register('rideCompleted', async function (event: any) {
    await processPayment.execute(event);
    await generateInvoice.execute(event);
});
Registry.getInstance().provide('mediator', mediator)
```

{% endcode %}

### **Notify from Domain Layer**

You could be **more strict**, and state that the Event notification is the Domain Object `Ride` responsability.

{% hint style="warning" %}
We avoid making the `finish()` method from `Ride` return the Event data, because otherwise, the method return type is tied to Events.
{% endhint %}

For this we can make these changes:

{% code title="Ride.ts" %}

```typescript
export class Ride extends Mediator {
    ...
    finish(): void {
        ...
        this.notify('triggerPublishRideCompletedEvent', { rideId: this.getRideId(), fare: this.getFare() });
    }
}
```

{% endcode %}

{% hint style="warning" %}
**As a curiosity:**

When making the `Ride` extend `Mediator`, the Mediator is actually acting as an [#observer](#observer "mention"), because it has a relationship of $$1 \to many$$, since **only** the `Ride` Object can notify *(create events)*.
{% endhint %}

{% code title="FinishRide.ts" %}

```typescript
export class FinishRide {
    @inject('mediator')
    mediator?: Mediator;
    
    async execute(input: Input): Promise<void> {
        // Get the Ride to be finished and put it in a Domain Object
        const ride = await this.rideRepository.getRideById(input.rideId);
        
        /*
            Note that the Mediator from `Ride` is a different instance of the Mediator
            from `Main.ts`, and for this reason we must make the notify from `Ride`
            trigger the notify for the injected Mediator.
        */
        ride.register('triggerPublishRideCompletedEvent', async (event: any) => {
            // Persist the Domain Object
            // Moved inside here so that Ride is persisted before the notify.
            await this.rideRepository.updateRide(ride);
            // Notify the Ride Completion
            await this.mediator.notify('rideCompleted', event);
        });
        // Mutate the Domain Object
        ride.finish();
    }
}
```

{% endcode %}

### **Notify from Domain Layer - With** `Domain Event`

It is possible to define the Event, more specifically, as a `Domain Event`.

{% code title="RideCompletedEvent.ts" %}

```typescript
export class RideCompletedEvent {
   constructor(readonly rideId: string, readonly fare: number) {} 
}
```

{% endcode %}

{% code title="Ride.ts" %}

```typescript
export class Ride {
    ...
    finish(): RideCompletedEvent {
        ...
        this.notify('rideCompleted', new RideCompletedEvent(this.getRideId(), this.getFare()));
    }
}
```

{% endcode %}
