How To Partition A System (Part 3 – General Emailer)

 

Today’s instalment is also available on YouTube.

Previously in this series on partitioning systems, we had set the scene and subsequently introduced the 3-Buckets technique. Today we’ll look at partitioning off the Bucket 3, general mechanism responsibility code. In our example, this means the lines belonging to the ‘general emailer’ functionality. 

Our task today is to replace the SMTP email sending with a more generic emailer. Then we can use other emailing solutions like Amazon Web Services (AWS) Simple Email Service (SES) emailing.

Let’s get started. Firstly we create an IEmailer interface. This interface will be our connector for sending emails:

  public interface IEmailer
  {
     void Send(MailMessage email);
  }

Now we write our SmtpEmailer. It will implement the IEmailer interface. We’ll copy the SmtpClient code from the Register() method:

  public class SmtpEmailer : IEmailer
  {
     public void Send(MailMessage email)
     {
        var smtpHost = ConfigurationManager.AppSettings["SmtpHost"];
        SmtpClient smtpClient = new SmtpClient(smtpHost);
        smtpClient.Send(email);
     }
  }

It’s not the most efficient implementation[1], but it will work. We have merely rehoused the same three lines.  

Let’s create an AwsSesEmailer implementation:

  public class AwsSesEmailer : IEmailer
  {
     public void Send(MailMessage email)
     {
        // TODO: Implement AWS SES emailer functionality.
     }
  }

Admittedly, that is a very light and cheeky implementation. ;-)

However, the point stands – we’ll be able to plug in either SmtpEmailer or AwsSesEmailer into RegisterNewCustomerUseCase once we have modified its constructor to take an IEmailer parameter:

  public class RegisterNewCustomerUseCase
  {
     public ICustomerRepository Repository { get; }
     private IEmailer Emailer { get; }

     public RegisterNewCustomerUseCase(ICustomerRepository repository, IEmailer emailer)
     {
        Repository = repository;
        Emailer = emailer;
     }

Finally, we replace the SmptClient code in the use case’s Register() method with 

  Emailer.Send(email);

Register() reduces to

  public Customer Register(CustomerRegistration registration)
  {
     Validate(registration);
     var customer = registration.ToCustomer();
     Repository.SaveCustomer(customer);

     // *** START - Send the new customer a Welcome email ***
     string welcomeEmailBody = BuildWelcomeEmailBody(customer);
     string welcomeEmailSubject = "Welcome to ABC Finance!";
     MailMessage email = new MailMessage("info@abcfinance.example.com", 
                                         customer.EmailAddress,
                                         welcomeEmailSubject,
                                         welcomeEmailBody);

     Emailer.Send(email);
     // *** END - Send the new customer a Welcome email ***

     return customer;
  }

We have managed to make the general emailing capability pluggable. That is awesome! We can now create an instance[2] of RegisterNewCustomerUseCase using either a SmtpEmailer

  var useCase = new RegisterNewCustomerUseCase(repository, new SmtpEmailer());

or an AwsSesEmailer.

  var useCase = new RegisterNewCustomerUseCase(repository, new AwsSesEmailer());

If we want to use a different general emailer implementation, say a Mailchimp one, we can now do so without having to modify RegisterNewCustomerUseCase.Register(). The Open-Closed Principle is giving us the thumbs up.

Today we made the generic emailer capability pluggable. Next time we will look at whether we can extract and partition off the sending of specific customer Welcome emails. Why would we want to do this? What’s the point? We’ll discover this next time.

 

Continue with How To Partition A System (Part 4 – Customer Notifier)

Footnotes:

  1. The .NET framework’s SmtpClient implements IDisposable. We should ensure we call Dispose() after use to clean up unmanaged resources. An effective way to do so is to instantiate in a using scope.
  2. In production code, we would likely do this by configuring and using an IoC container.
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply