Dependency inversion explained
Tags: oop, beginners
Published: 2021-11-01 23:02:22

When we start to programming, it's usual to develop simple algorithms. Who has never created a CRUD of users? Althought, as time goes by, our knowledge increases and our challenges too.
Now, imagine that you are developing an application where everytime the user creates his account or forget his password, the system sends to him an email. As you are getting started, you choose to use the easiest and cheaper way to send emails: using SMTP.

I'm going to use Typescript in example but you can use any oriented object language.

As said before, our imaginary application will send email when the user register and forgot password. Our system will send an email with different subjects and different texts and html. So, we have these 2 services and nodemailer component.
```ts
// NodemailerMailProvider.ts
import nodemailer from 'nodemailer'

type SendMailParams = {
  to: string
  subject: string
  text: string
  html: string
}

export class NodemailerMailProvider {
  private transporter: nodemailer.Transporter;

  constructor(){
    this.transporter = nodemailer.createTransport(/* transport options */);
  }

  async sendMail({html,subject,text,to}: SendMailParams){
    let info = await this.transporter.sendMail({
      from: '"Our Application" <contact@ourapp.com>',
      to,
      subject,
      text,
      html,
    });
  
    console.log("Message sent: %s", info.messageId);
    console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
  }
}
``` 
```ts
//SendWelcomeEmailService.ts
import { NodemailerMailProvider } from "../providers";

export class SendWelcomeEmailService {
  private mailProvider: NodemailerMailProvider

  constructor(mailProvider: NodemailerMailProvider){
    this.mailProvider = mailProvider
  }

  public async execute(email: string, name: string): Promise<void> {
   await this.mailProvider.sendMail({
    to: email,
    subject: "Welcome",
    text: `Welcome to our application, ${name}!`,
    html: `<b>Welcome to our application, ${name}!</b>`,  
   })
  }
}
```
```ts
// SendForgotEmailService.ts
import { NodemailerMailProvider } from "../providers/NodemailerMailProvider";

export class SendForgotEmailService {
  private mailProvider: NodemailerMailProvider

  constructor(mailProvider: NodemailerMailProvider){
    this.mailProvider = mailProvider
  }

  public async execute(email: string): Promise<void> {
   await this.mailProvider.sendMail({
    to: email,
    subject: "Password recovery",
    text: `A password recovery was requested`,
    html: `A <b>password recovery</b> was requested`,  
   })
  }
}
```

But the system grows and starts to send a lot of emails. So, you decide to use Amazon SES. And now you have `SESMailProvider`
```ts
import nodemailer, { Transporter } from 'nodemailer';
import aws from 'aws-sdk';

type SendMailParams = {
  to: string
  subject: string
  text: string
  html: string
} 

export default class SESMailProvider  {
  private client: Transporter;

  constructor() {
    this.client = nodemailer.createTransport({
      SES: new aws.SES({
        apiVersion: '2010-12-01',
        region: 'us-east-1',
      }),
    });
  }

  public async sendMail({
    to,
    subject,
    html,
    text
  }: SendMailParams): Promise<void> {
    await this.client.sendMail({
      from: '"Our Application" <contact@ourapp.com>',
      to,
      subject,
      text,
      html
    });
  }
}
```

But now we have a problem, in the constructor of `SendForgotEmailService` and `SendWelcomeEmailService` we have this dependency `mailProvider: NodemailerMailProvider`. Now we have to substitute the type of `mailProvider` to `SESMailProvider`.

```ts
private mailProvider: NodemailerMailProvider

constructor(mailProvider: NodemailerMailProvider){
  this.mailProvider = mailProvider
}

// now is going to be 
private mailProvider: SESMailProvider

constructor(mailProvider: SESMailProvider){
  this.mailProvider = mailProvider
}
```
This change affects only 2 services now, but imagine that our application sends email when user log in, when we want o send some notification as a like or a new post. As the system grows, we will have a lot of actions that send an email. So, if we want to change the MailProvider we will need to change in all constructors. That's not good. To solve it, you can create an interface and make the providers implement it.

```ts
// IMailProvider.ts
export type SendMailParams = {
  to: string
  subject: string
  text: string
  html: string
} 

export interface IMailProvider {
  sendMail(params: SendMailParams): Promise<void>
}
```
Now, the providers are going to implement this interface. How both providers has the same method `sendMail` with the same signature the only change we need to do is to add `implements IMailProvider`

```ts
export class NodemailerMailProvider implements IMailProvider {}
export class SESMailProvider implements IMailProvider {}
```
And after all the services will not recive `NodemailerMailProvider`, `SESMailProvider` or any other implementation. They will recive an `IMailProvider`.
```ts
private mailProvider: IMailProvider

constructor(mailProvider: IMailProvider){
  this.mailProvider = mailProvider
}
```

Ok, now, we need to create the instance of the services. I'll create just for `SendWelcomeEmailService` to avoid repetition. 

```ts
const sendWelcomeEmail = new SendWelcomeEmailService();
```

This will result in a error because `SendWelcomeEmailService` recives an `IMailProvider`. But what is an `IMailProvider`? `NodemailerMailProvider` and `SESMailProvider` are `IMailProvider` beacuse both of them implements it. I am going to implement `SendWelcomeEmailService` injecting `NodemailerMailProvider`

```ts
const mailProvider = new NodemailerMailProvider();
const sendWelcomeEmail = new SendWelcomeEmailService(mailProvider);
```
Now, if you want to change your mail provider the only thing you will need to do is change the mail provider you inject in your services and you won't need to change anything in the service. 

But we still have a problem. The `IMailProvider` is required in more than one service. Then, we need to write `const mailProvider = new NodemailerMailProvider()` every time we need it. To solve it we can create factory methods. These methods are responsible to create the instances of our dependencies. The first factory we will create is `makeIMailProvider` that, obviously, will be responsible to create the a implementation of `IMailProvider`.

```ts 
export function makeIMailProvider(): IMailProvider {
  return new NodemailerMailProvider()
}
```
and the factories for the services
```ts
// look that the mail provider will be the same for both services and if we have another service needs IMailProvider it would use the same provider too
const mailProvider = makeIMailProvider();

export function makeSendWelcomeEmailService() {
  return new SendWelcomeEmailService(mailProvider)
}

export function makeSendForgotPasswordEmailService() {
  return new SendForgotPasswordEmailService(mailProvider)
}
```

When you need the services you can just 
```ts
const sendWelcomeEmail =  makeSendWelcomeEmailService();
// or
const sendForgotPasswordEmail = makeSendForgotPasswordEmailService();
```

And the best part, if you want to change the implementation of `IMailProvider` you only need to change the factory method
```ts
export function makeIMailProvider(): IMailProvider {
  // return new SendgridMailProvider()
  // return new MailchimpMailProvider()
  return new SESMailProvider()
}
```
Note that the provider returned in the factory must implement the inerface `IMailProvider`.

Robert Matin in his book clean arquitecture says
> The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.

If you want to see the full code check the repository in https://github.com/murilomaiaa/dependency-inversion