The Single Responsibility Principle is the most misunderstood of all the SOLID Principles. I've certainly gotten it wrong in the past and almost everybody I talk to gets it wrong in the same way. How can that be when it seems like the simplest principle? Granted, not all people get it wrong, if you got it right, well done! You're in the minority. In this article we'll look at what most people think it is, what it actually is, and cover some Domain Driven Design (DDD) and Bounded Contexts along the way.
Let's start with a quick introduction to the SOLID Principles for anybody who isn't already familiar with them. In 2000, Robert C. Martin, or Uncle Bob, wrote the article Design Principles and Design Patterns which compiled several existing software principles and patterns that Martin found, in his experience, worked well together. Martin didn't necessarily invent anything new here, but he probably saved readers of the article months, or years, of reading and studying the sources that these principles and patterns came from as well as sharing his experience and thoughts on effective software development.
In 2004, Michael Feathers pointed out to Martin that with a little tweaking, 5 of the principles from his article could make up the acronym SOLID and that was the start of the term "SOLID principles":
- S - Single Responsiblity Principle - SRP
- O - Open-closed Principle - OCP
- L - Liskov Substitution Principle - LSP
- I - Interface Segregation Principle - ISP
- D - Dependency Inversion Principle - DIP
Since then, Uncle Bob has made a very comfortable living from talking about the SOLID principles. He has authored a number of books but the 2 main ones that contain more details about the principles and patterns aggregated in his article are Clean Code and Clean Architecture. Martin also has a video channel you can subscribe to containing videos of Clean Code, Clean Architecture and SOLID principles. The videos are informative, but as a word of warning, they can be a little zany with Bob performing some comedy characters that aren't to everybody's taste. I'd recommend reading the 2 books rather than watching the videos.
Single Responsibility Principle - SRP
In this article, we'll focus on the Single Responsibility Principle as it is the most misunderstood of the SOLID principles. The Single Responsibility Principle seems like a simple enough quote...
A class should have only one reason to change
What do people think this means
This is often misquoted and misunderstood as "A class, or function, should be responsible for doing one thing". This isn't a bad thing, it's actually really good advice but it certainly isn't the Single Responsibility Principle. This is actually the "Do One Thing" rule from the book Clean Code and there are memes about it.
What it actually means
To understand what Single Responsibility Principle actually means we need to really consider what Martin means by the words "reason" and "change". To do that we need to consider Domain Driven Design and Bounded Contexts. The reason to change isn't a technical one implied by the 'Do One Thing' rule, where the reason might be that a different framework is being used, or errors need to be logged in a different format. The reason to change here is that a real-world process has changed or needs to change, and the code needs to reflect that. This is where DDD comes in as the code reflects the Domain Model which reflects the Domain, or the real-world process. Changes to the process are updated in the Domain Model and in turn in the code itself. So what do we mean by one reason to change?
In DDD we have the concept of a Ubiquitous Language, which is a language that Domain Experts (who perform or really know the process) and Software Developers (who implement the process in code) use. Ubiquitous Language will be different in different areas of a business, for example people in the warehouse talk differently about their processes to people in sales or purchasing, or people working in a physical store. If we take the example of an organisation selling books, people selling books to customers in a physical store talk about a book in a very different context to people in the warehouse.
People in the warehouse are interested in:
- How heavy the book is
- The physical size of the book
- Where in the warehouse the book is located
- Books that are heavy and sold often might be located closer to the loading bay. Lighter books that aren't sold often might be further away.
For people working in sales though, a book is a very different thing. They could be interested in:
- Is a book a soft or hardback
- Is the book in colour or black and white
- Does the book have photos or illustrations
- Is the text large for people who are visually impaired
- Who the author is
- Which category the book is and the type of person who could be interested in buying that category
We can see from the lists above, whilst a book might have the same identifier, probably an ISBN code, they mean very different things to people working in different contexts of the same organisation and following different processes. Sales and the warehouse here are two different Bounded Contexts. The Ubiquitous Language changes between the two departments.
Martin Fowler's blog on Bounded Contexts contains this excellent diagram depicting how the Customer and Product classes in this Domain Model both exist in 2 different Bounded Contexts:
Responsible TO One Thing
This is where the Single Responsibility Principle comes in... rather than a class being responsible for one thing, the SRP suggests that a class should be responsible to one thing, and that one thing is a department or role. The one reason to change is that the real-world process has changed, or is changing, in that department and code needs to reflect that. Changes to code because the process has changed in one department should have no risk of impacting software used by another department. In the example of the bookseller... if the process changes in the warehouse in a way that requires code changes, those changes should have no risk of affecting software used by sales people.
The way to remove that risk is to separate code used by different departments. This could include separating code into:
- Different classes
- Different components
- Different applications
- Different microservices
In the book selling example, there could be two different implementations of a class called Book. Both would have an ISBN so the systems can be connected if needed, but each implementation of Book would be specific to the department and process that it was used in, with the relevant properties needed by that department. Changes to the Book class used by sales now cannot have any impact of the software used in the warehouse because that will be using its own Bounded Context specific implementation of Book. This can result in what is known as Accidental Duplication, where there is some overlap in the implementation of Book in both sales and the warehouse, but that's fine if we are following the Single Responsibility Principle. We can live with non-infrastructure code duplication in different Bounded Contexts because of the reduction in risk of changes in software for one department affecting software used in another department.
The Single Responsibility Principle states that a class should have one reason to change. That reason is a change to a real-world process used in a single department or by a specific role. Changes in code for one department, or role, should have no risk of impacting software used by another department or role. We can achieve this by separating out code used by different departments into different classes, components, applications, or microservices. This can result in some Accidental Duplication in non-infrastructure code written for different departments but that's OK as it reduces the risk of change for one department impacting another.