How To Partition A System (Part 4 – Customer Notifier)
Today’s instalment is also available on YouTube.
Important Note: I cannot more highly recommend reading this series. Knowing how to partition a system into logical modules that comply with the SOLID Principles, are easy to understand, are tolerant to change and are pluggable, is the closest thing to a software developer Superpower. I am serious. I wish someone had explained this to me in 4 easily digestible blog posts. Please learn the information in this series inside out, digest it, reflect on it, comprehend it, internalise it. And of course, share it with others.
Here are the links to the previous articles and videos:
- Part 1 – Introduction / YouTube
- Part 2 – The 3 Buckets / YouTube
- Part 3 – General Emailer / YouTube
This is the final post in the series. Today we’ll make our high-level business logic more generic.
Let’s re-examine what we had in Bucket 1. Here is the image of Bucket 1 for our example of sending customers a friendly welcome email after we have registered them in the system:
At this highest level of abstraction, the stated requirement is to send the customer a welcome email. That’s what the business has requested.
Not so fast. Isn’t emailing a mechanism?
Good point. The answer is Yes, emailing is a mechanism. So how do we make our general workflow independent of the emailing notification mechanism?
Let’s try crossing out the word ’email’ in the Bucket 1 image and see how it reads:
OK, so our responsibility now reads: “We want to send the new customer a Welcome message.“. Nice. It still makes sense. Phrasing the sentence like this opens the door for us to send a welcome message using other mechanisms. The customer could be welcomed via SMS/txt or in-app notification.
It would be helpful if we could easily swap the different notification mechanisms for one another. Say unplug the email customer notification mechanism and replace it with an SMS/txt one. Writing an SMS/txt customer notification module may not take much effort either. If one already had a generic SMS/txt message sender, then one would merely need to write the module to format a welcome SMS/txt message and ready it for sending. The required effort would be relatively small.
In programming, meaningful names are crucial. A recognisable name for the interface to plug in customer notification mechanisms might be ICustomerNotifier. How should it be defined?
How about this?
public interface ICustomerNotifier { void SendWelcomeMessage(Customer customer); }
This definition identifies WHAT we are doing – i.e. we are sending a welcome message to the customer. No details or mechanisms needed yet.
How would this affect our Register() method?
public Customer Register(CustomerRegistration registration) { Validate(registration); var customer = registration.ToCustomer(); Repository.SaveCustomer(customer); CustomerNotifier.SendWelcomeMessage(customer); return customer; }
Isn’t that better?
For customer emails, we would have a CustomerEmailer:
public class CustomerEmailer : ICustomerNotifier { private IEmailer Emailer { get; } public CustomerEmailer(IEmailer emailer) { Emailer = emailer; } public void SendWelcomeMessage(Customer customer) { 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); } }
Please note that the general IEmailer interface is now not used by the RegisterNewCustomerUseCase class since it no longer engages in emailing. Instead, it notifies. On the other hand, CustomerEmailer does use IEmailer. This class has retained its pluggability with regards to different general emailers.
If we wanted to notify our newly registered customer using SMS/txt we could create a CustomerSmsSender. This new class would implement the ICustomerNotifier interface:
public class CustomerSmsSender : ICustomerNotifier { private ISmsSender SmsSender { get; } public CustomerSmsSender(ISmsSender smsSender) { SmsSender = smsSender; } public void SendWelcomeMessage(Customer customer) { // Format the SMS/txt welcome message. // Send the message to the customer using the SmsSender. } }
That’s it for today. Please take another look at how short the Register() method has become. There are only five lines of code. Each one of them is eminently understandable. When we program like this, we are not only developing software that is easy and cheap to maintain. We are creating something beautiful and artful too.
Leave a Reply
Want to join the discussion?Feel free to contribute!