在代码中切换电子邮件提供商的设计模式

design pattern for switching email providers in the code

我们需要在 php 应用程序中发送电子邮件(谁不需要)。最初当我们的应用程序处于婴儿期时,我们只使用 linux sendmail。 向前一点,我们切换到我们自己的 SMTP 服务器。这意味着每个具有电子邮件相关功能的文件中的代码更改。 一年后,我们转向 AWS,不得不再次更改代码以开始使用 AWS 电子邮件服务。 现在我们转移到 Google 云,再次更改电子邮件代码以使用某些第三方提供商。

电子邮件配置遍布各处,更改一个提供商意味着需要更新数百个文件,如果您错过一个,客户可能无法收到一部分的电子邮件,但可以收到另一部分的电子邮件.

我只是退后一步,意识到我们正在更改的代码没有任何意义,只是因为我们的电子邮件提供商已更改。

但我这辈子都想不出解决这个问题的方法。

我需要做的就是从我的代码中删除电子邮件并重构它,使我的应用程序代码可以独立于邮件服务提供商运行。

她是我对此的看法

class EmailGateway()
{
  private $emailer;
  public function __construct($someEmailProvider)
  {
     $this->emailer = $someEmailProvider;
  }

  public function send($from, $to, $subject, $bodyText, $bodyHtml="")
  {

    this->emailer->send($from, $to, $subject, $bodyText, $bodyHtml="");
  }
}

然后在我的代码中,我只需要像

那样调用它
# Gmail?
$mailGateway = new EmailGateway(new GmailEmailer("username", "password"));

# local?
$mailGateway = new EmailGateway(new SendMailer());

# SMTP?
$mailGateway = new EmailGateway(new MyMailServerMailer("192.168.0.3", "no_user", "secret_password"));

在我的应用程序代码中,我什至可以更进一步调用工厂来获取当前的默认电子邮件提供商

class EmailService()
{
   public static function currentProvider(): EmailProviderInterface
   {

      return new MyMailServerMailer("192.168.0.3", "no_user", "secret_password");

   }
}

这将使我上面的调用程序代码更加简单

# SMTP?/GMAIL?/Local?/Whatever
$mailGateway = new EmailGateway(EmailService::currentProvider());

所以每当我需要更改提供程序时,我要做的就是更改 currentProvider() 的内容,我很高兴。

我做的对吗? 这是一个合适的策略模式吗? 只要能解决我的问题,我还需要关心它是什么模式吗?

有没有更好的方法让自己摆脱这个日益严重的混​​乱局面?

是的,基本上您做对了 -- 鉴于您 objective 在需要更改邮件提供商时可以轻松修改代码。

但是,您可以使您的设计更简单更好。

查看EmailGateway class:它没有做任何重要的事情。它与 EmailProvider 具有相同的接口,只是将 send 任务委托给 EmailProvider.

所以你可以有一个名为 EmailProvider 的接口——我正在使用 Java 但它应该很容易转换为 PHP:

interface EmailProvider {
    void send(String from, String to, String subject, String bodyText, String bodyHtml);
}

然后是几个实现:

class GoogleEmailProvider implements EmailProvider {
    public GoogleEmailProvider (String username, String password) {
        ...
    }
    public void send(String from, String to, String subject, String bodyText, String bodyHtml) {
        ...
    }
}
// and so on ...

在您的应用程序的 CompositionRoot 处(如 main 方法),您只需创建 one 实例 EmailProvider需要:

EmailProvider emailProvider = new GoogleEmailProvider("username", "password");

然后您可以将该实例传递到任何需要发送电子邮件的地方:

Foo foo = new Foo(emailProvider);

这种设计有一些好处。首先,像 Foo 这样的 class 单元测试更容易。你总是可以写一个 MockEmailProvider 并将它传递给 Foo。其次,像 Foo 这样的 classes 的用户应该很容易意识到 Foo 可以通过查看其签名来发送电子邮件。发邮件,做IO/Database/Network……都是很重要的事情,应该时刻注意。

希望对您有所帮助。