遵循 SOLID 原则的电子邮件回退
Email fallback following SOLID principles
我想实现一个微服务来使用后备客户端发送电子邮件,所以如果第一个客户端 (SendGrid) 失败,我将调用第二个客户端 (MailJet),下面的代码展示了这个想法.
问题是:有没有一种方法可以使用一些 .net 核心功能来改进 Main 函数而不是初始化新对象?关键是我想遵循 SOLID 原则避免依赖和紧密耦合,所以如果我明天需要一个新的 EmailClient,它应该很容易实现而不会破坏 SOLID 原则。
P.S。欢迎任何改进。
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<IEmailClient> clients = new List<IEmailClient>();
clients.Add(new SendGrid());
clients.Add(new MailJet());
var emailService = new EmailService(clients);
emailService.sendEmail();
}
}
public class EmailService
{
protected List<IEmailClient> clients;
public EmailService(List<IEmailClient> clients)
{
this.clients = clients;
}
public void sendEmail()
{
foreach (IEmailClient client in this.clients)
{
if (client.send()) {
break;
}
}
}
}
public interface IEmailClient
{
bool send();
}
public class SendGrid: IEmailClient
{
public bool send()
{
var error = true;
Console.WriteLine("SendGrid sending email");
if (error) {
Console.WriteLine("Error");
return false;
}
Console.WriteLine("Sendgrid email sent");
return true;
}
}
public class MailJet: IEmailClient
{
public bool send()
{
var error = false;
Console.WriteLine("Mailjet sending email");
if (error) {
Console.WriteLine("Error");
return false;
}
Console.WriteLine("Mailjet email sent");
return true;
}
}
所有应用程序都有一个 Composition Root 连接依赖项的地方。无论您是手动编写还是通过配置 IoC 容器,代码的核心很可能是相同的。
在您设计的示例中,Main
方法是组合根,添加新的 IEmailClient
实现不会影响应用程序的核心 (EmailService
),只会影响根。
现在关于设计,很难说,但也许您可以努力应用 Composite & Decorator 模式来抽象出多个客户端的存在。例如:
这种设计的优点是它允许在不改变 class 的情况下扩展 EmailService
的行为。您可以添加重试、回退、日志记录等,而无需更改现有代码。此外,如果 EmailService
最终只委托给 IEmailClient
,你甚至可能会质疑是否需要 Facade。
另一个优点是它允许您即时创建具有不同行为的客户端。例如,假设您的系统的用户希望能够配置回退、重试等。那么您可以有一个 Factory 来根据他们的配置动态构建一个 IEmailClient
实例。
但是,请记住不要过度设计解决方案。您已经在利用现有的电子邮件网关。如果您不确定自己需要什么,只需努力寻找最简单的解决方案并根据需要进行重构。
我想实现一个微服务来使用后备客户端发送电子邮件,所以如果第一个客户端 (SendGrid) 失败,我将调用第二个客户端 (MailJet),下面的代码展示了这个想法.
问题是:有没有一种方法可以使用一些 .net 核心功能来改进 Main 函数而不是初始化新对象?关键是我想遵循 SOLID 原则避免依赖和紧密耦合,所以如果我明天需要一个新的 EmailClient,它应该很容易实现而不会破坏 SOLID 原则。
P.S。欢迎任何改进。
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<IEmailClient> clients = new List<IEmailClient>();
clients.Add(new SendGrid());
clients.Add(new MailJet());
var emailService = new EmailService(clients);
emailService.sendEmail();
}
}
public class EmailService
{
protected List<IEmailClient> clients;
public EmailService(List<IEmailClient> clients)
{
this.clients = clients;
}
public void sendEmail()
{
foreach (IEmailClient client in this.clients)
{
if (client.send()) {
break;
}
}
}
}
public interface IEmailClient
{
bool send();
}
public class SendGrid: IEmailClient
{
public bool send()
{
var error = true;
Console.WriteLine("SendGrid sending email");
if (error) {
Console.WriteLine("Error");
return false;
}
Console.WriteLine("Sendgrid email sent");
return true;
}
}
public class MailJet: IEmailClient
{
public bool send()
{
var error = false;
Console.WriteLine("Mailjet sending email");
if (error) {
Console.WriteLine("Error");
return false;
}
Console.WriteLine("Mailjet email sent");
return true;
}
}
所有应用程序都有一个 Composition Root 连接依赖项的地方。无论您是手动编写还是通过配置 IoC 容器,代码的核心很可能是相同的。
在您设计的示例中,Main
方法是组合根,添加新的 IEmailClient
实现不会影响应用程序的核心 (EmailService
),只会影响根。
现在关于设计,很难说,但也许您可以努力应用 Composite & Decorator 模式来抽象出多个客户端的存在。例如:
这种设计的优点是它允许在不改变 class 的情况下扩展 EmailService
的行为。您可以添加重试、回退、日志记录等,而无需更改现有代码。此外,如果 EmailService
最终只委托给 IEmailClient
,你甚至可能会质疑是否需要 Facade。
另一个优点是它允许您即时创建具有不同行为的客户端。例如,假设您的系统的用户希望能够配置回退、重试等。那么您可以有一个 Factory 来根据他们的配置动态构建一个 IEmailClient
实例。
但是,请记住不要过度设计解决方案。您已经在利用现有的电子邮件网关。如果您不确定自己需要什么,只需努力寻找最简单的解决方案并根据需要进行重构。