0

I'd like to implement a microservice to send emails using a fallback client, so in case of failure in the first client (SendGrid) I'll call the second client (MailJet), the code below shows the idea.

The questions is: Is there a way to improve the Main function using some .net core feature instead of initialize new objects? The point is that I'd like to follow SOLID principles avoiding dependencies and tight couplings, so if I need a new EmailClient tomorrow it should be easy to implement without break SOLID principles.

P.S. Any improvement is welcome.

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;        
    }
}

dotnet fiddle

Leandro Brito
  • 334
  • 2
  • 13
  • 1
    Intentionally implementing design patterns [is a pitfall](https://medium.com/the-coding-matrix/https-medium-com-the-coding-matrix-dont-use-design-patterns-35bcff59dbb5) for new developers. Design patterns are descriptive, not prescriptive. They are not how we learn how to write or recognize good code any more than [diagramming sentences](https://www.google.com/search?q=diagramming+sentences) is how we learn natural language grammar. – Kevin Krumwiede Apr 23 '21 at 17:46
  • Regarding improvements, terms like manager and execute doesn't tell me anything about what it actually does. the purpose/Intent is not clear. Also, is the ordering of the list of any importance? How to tell what is the primary system and what the fallback? How about duplicate entries? Or zero? Or 50.. – Peter Bons Apr 23 '21 at 18:36
  • How about some [dependency injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0)? – Peter Bons Apr 23 '21 at 18:38
  • @PeterBons it's a microservice that will send transactional emails and should have a fallback client in case of the default client fails. My main question is regarding the clients.add(new Client()) part, in PHP/Laravel we can set a Provider to instantiate this and I think .net core might have something like this. Thanks for the dependency injection link, it'll be useful – Leandro Brito Apr 23 '21 at 21:36
  • @KevinKrumwiede, [principles and patterns](https://stackoverflow.com/q/31317141/1371329) are two very different things. They are opposites in some sense. – jaco0646 Apr 24 '21 at 04:04
  • @jaco0646 I'd say the same admonition applies to principles. For example, there's a lot of contention even among experienced programmers about exactly what the Single Responsibility Principle means. Whenever you see SOLID mentioned in a commit message, it's always a newbie developer and it's a huge red flag. – Kevin Krumwiede Apr 24 '21 at 06:47
  • @KevinKrumwiede, the pitfall article linked above makes no mention of principles, so I don't think it applies to this topic. Principles are very much prescriptive, and they _are_ how we learn to recognize good code. Cautioning against principles requires a different argument. For example, something like, "principles are too complex for junior developers to implement without guidance," sounds sensible. Conflating patterns with principles because both are misunderstood and abused only confuses the issue (which increases the likelyhood of misunderstanding and abuse). – jaco0646 Apr 24 '21 at 13:46
  • @jaco0646 It's my opinion that the same admonition applies, not the article's. – Kevin Krumwiede Apr 24 '21 at 15:58

1 Answers1

1

All applications have a Composition Root where the dependencies gets wired. Whether you write this manually or by configuring an IoC container the core of your code will most likely be identical.

In your contrived example the Main method is the composition root and adding new IEmailClient implementations wouldn't impact the core of the application (EmailService), only the root.

Now regarding the design, it's hard to tell, but perhaps you could have strived to apply the Composite & Decorator patterns to abstract away the existence of multiple clients. For instance:

enter image description here

The advantage of such design is that it allows to extend the behavior of EmailService without changing the class. You could add retrys, fallback, logging, etc. all without changing existing code. Furthermore, if EmailService ends up only delegating to IEmailClient you may even question whether that Facade is needed or not.

Another advantage is that it lets you create clients with different behaviors on the fly. For instance, imagine the users of your system wants to be able to configure the fallbacks, retries, etc. then you could have a Factory that builds an IEmailClient instance dynamically according to their configuration.

However, keep in mind not to overengineer the solution. You are already leveraging existing email gateways. If you aren't sure what you are going to need yet just strive for the simplest solution and refactor as needed.

plalx
  • 42,889
  • 6
  • 74
  • 90