Interfaces and Dependency Inversion
Published at by

When redesigning an existing application, it is not uncommon to encounter the direct use of an external service in the code. The problem is that if we want to change the service, or if the service provider modifies its SDK or API, we have to rewrite a significant portion of the code.
To avoid finding ourselves in this situation in the future, we can adopt a more modular and scalable approach.
Here is a possible solution for an email sending service:
- Creation of a common interface: Create an interface called MailerInterface, for example, which defines a send method (at a minimum). This interface serves as a contract that all messaging services must adhere to.
- Implementation of specific services: Then, simply implement this interface in the classes using an email sending service: MailjetMailer and SendgridMailer, for example. Each class implements MailerInterface and uses a distinct email service provider. They both have a send method, allowing them to be easily used and interchanged in the code.
// Definition of the interface
interface MailerInterface {
public function send($recipient, $subject, $body);
}
// Implementation for Mailjet
class MailjetMailer implements MailerInterface {
public function send($recipient, $subject, $body) {
// Code to send an email via Mailjet
}
}
// Implementation for Sendgrid
class SendgridMailer implements MailerInterface {
public function send($recipient, $subject, $body) {
// Code to send an email via Sendgrid
}
}
// Very simplistic implementation example
class ResetPasswordCommandHandler {
private $mailer;
// We can use the mailer of our choice
public function __construct(MailerInterface $mailer) {
$this->mailer = $mailer;
}
public function __invoke(ResetPasswordCommand $resetPasswordCommand ): void {
// Generate the password reset token (for example)
$resetToken = //random;
// Send the email, regardless of the mailer we call the send method
$this->mailer->send(
$resetPasswordCommand->email,
'Reset your password',
"Use this token to reset your password: $resetToken"
);
}
}
With this architecture, simply change the implementation of MailerInterface used to switch between external services without modifying the rest of the code.
By applying this method, we have not only simplified the management of services in our application, but we have also adopted a better practice for software development.
The advantages of this approach are clear:
- Flexibility : We can easily switch providers or services without touching the main logic of our application.
- Facilitated maintenance : Changes are localized and do not require massive refactoring.
- Extensibility :Adding a new service or provider only requires creating a new class implementing the defined interface.
This method has allowed us to gain agility and responsiveness to technological developments and the changing needs of our application. It is a best practice that I highly recommend to all developers.