SOLID Series: The Single Responsibility Principle

    I am starting a series about the SOLID Principles of Object-Oriented Design. These are five principles that you should keep in mind when designing an object-oriented program. They are coined by different authors in different moments in time. You can say that they are hard learned lessons from the past.

    Today I am going to write about the first of the five principles - The Single Responsibility Principle. It was coined by Robert C. Martin, a.k.a. Uncle Bob. The narrative usually goes along the lines of: "The SRP means that a class should do just one thing". I want you to think very carefully about this. I want you to think about it because it's a lie!

    Imagine what a program made of classes which do just one thing would look like! I've actually seen such a program. The claim was that it conforms to the SRP, but nothing could be further from the truth! I'll spare you the details and I'll show you only the code that illustrates the point. I'll skip the non-essential parts for clarity. The program in question was a web app using an MVC framework. Each class in the program was a verb and it had exactly one method called Execute. Essentially every class in the program was a Command. I'll show you an example in C#.

Consider these six classes: LoginController, LoginModel, ProcessLogin, HashPassword, ComparePassword, SendEmail.

    public class LoginController : Controller
    {
        public IActionResult Execute(LoginModel loginModel)
        {
            if (ModelState.IsValid == false)
            {
                return View(loginModel);
            }

            ProcessLogin processLogin = new ProcessLogin();
            processLogin.Execute(loginModel.Username, loginModel.Password);

            return RedirectToAction("Index", "Home");
        }
    }

    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public class ProcessLogin
    {
        public void Execute(string username, string password)
        {
            var dbPassword = "";
            // Code that gets the password from the database.

            HashPassword hashPassword = new HashPassword();
            hashPassword.Execute(username, password, dbPassword);
        }
    }

    public class HashPassword
    {
        public void Execute(string username, string userProvidedPassword, string dbPassword)
        {
            string hashedPassword = "";
            // Code that hashes the password.

            ComparePassword comparePassword = new ComparePassword();
            comparePassword.Execute(username, hashedPassword, dbPassword);
        }
    }

    public class ComparePassword
    {
        public void Execute(string username, string hashedPassword, string dbPassword)
        {
            if (hashedPassword.Equals(dbPassword))
            {
                SendEmail sendEmail = new SendEmail();
                sendEmail.Execute(username);
            }
        }
    }

    public class SendEmail
    {
        public void Execute(string username)
        {
            // Code that sends email.
        }
    }

    We start in the LoginController. The Execute method validates the LoginModel and passes the username and the password to processLogin.Execute(). The ProcessLogin command fetches the user's password from the database and calls another command - hashPassword.Execute(). The HashPassword command hashes the user provided password and calls another command to compare the two passwords - comparePassword.Execute(). The ComparePassword command, compares the password from the database with the hash of the user provided password and calls another command - sendEmail.Execute(). The SendEmail command emails the user notifying them about their login activity.

    This obviously is a simplified example. The point is that each class "does" one thing and passes the control to another class in the command chain. In each link of the chain, data is transformed and passed down. I don't have to tell you that chasing the tail of the dragon in large chains, with 50+ chained commands, leads to a cognitive overload. I won't even touch the fact that the name of the classes does not describe well enough what they actually do. All that I'll say is that this code violates the Single Responsibility Principle!

    The Single Responsibility Principle states that a class should have one and only one reason to change! It does not talk about the number of things that a class does or doesn't do. So what's a reason to change? Let's define it.

    A reason to change is a person, a role (sometimes the same person can be in multiple roles), or a group of people in your organization. There are multiple people software brings value to, and they might want the software to change in different ways and in different points in time. Sometimes their requirements might even conflict with one another. A change from one party should not affect the other party. You could also say that the SRP is a special case of separation of concerns. Gather together the things that change for the same reason. Separate the things that change for different reasons.

    Let's change the above example to conform to The Single Responsibility Principle. I'll show you one of many ways in which you can square that circle. I'll again skip some details for the sake of clarity. Let's create seven classes: AccountController, LoginModel, AccountService, UserRepository, PasswordHasher, PasswordComparer and an EmailSender.

    public class AccountController : Controller
    {
        public IActionResult Login(LoginModel loginModel)
        {
            if (ModelState.IsValid == false)
            {
                return View(loginModel);
            }

            var accountService = new AccountService();
            accountService.Login(loginModel.Username, loginModel.Password);

            return RedirectToAction("Index", "Home");
        }
    }

    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public class AccountService
    {
        public void Login(string username, string userProvidedPassword)
        {
            var userRepository = new UserRepository();
            var dbPassword = userRepository.GetPassword(username);

            var passwordHasher = new PasswordHasher();
            var hashedPassword = passwordHasher.HashData(userProvidedPassword);

            var passwordComparer = new PasswordComparer();
            
            if (passwordComparer.ArePasswordsEqual(hashedPassword, dbPassword))
            {
                var emailSender = new EmailSender();
                emailSender.SendLoginNotification(username);
            }
        }
    }

    public class UserRepository
    {
        public string GetPassword(string username)
        {
            var password = "";
            // Code that gets the password from the database.

            return password;
        }
    }

    public class PasswordHasher
    {
        public string HashData(string password)
        {
            var hashedData = "";
            // Code that hashes the password.

            return hashedData;
        }
    }

    public class PasswordComparer
    {
        public bool ArePasswordsEqual(string hashedPassword, string dbPassword)
        {
            var arePasswordsEqual = true;
            // Code that compares the passwords.

            return arePasswordsEqual;
        }
    }

    public class EmailSender
    {
        public void SendLoginNotification(string username)
        {
            // Code that sends login notification.
        }
    }

The AccountController has a Login method, which validates the LoginModel and calls an AccountService.Login(). The AccountService.Login method does four distinct actions:

  • Fetches the user's password from the database
  • Hashes the password provided by the user
  • Compares the two passwords
  • Sends an email notifying the user about their login activity

    To fetch the user's password, the LoginService calls a method from the UserRepository. The UserRepository is the only class with knowledge of the database. There's a distinct group of people in an IT organization concerned with the database - the DBAs, so we put the code in a separate class.

    To hash the password the service calls a method from the PasswordHasher class. To compare the password, the service calls a method from the PasswordComparer class, and then it calls a method from the EmailSender to notify the user about their login activity. What group of people is concerned with the security of the application? What group of people in the organization can enforce rules about the password strength, hashing algorithm or whether to send emails with the login activity or not? The IT Security team! By grouping together the things that change for the same reason, and separating the things that change for different reasons, the code now conforms to the SRP, even though there's a class that does four things.

    The Single Responsibility Principle is not about the number of things that a class does. It's about the reasons for a class to change. You can think about it as cohesion and coupling. Increase the cohesion of things that change for the same reasons and decrease the coupling between the things that change for different things. It's also a form of separation of concerns. Bear in mind, however, that it's all about people, and people have different reasons for change.

Popular posts from this blog

The Database is a Detail

The Technical Debt Metaphor