Categories
Software Architecture Software Development

Single Responsibility: limiting the impact of changes

Get a better understanding of the principle that is not only about separating things, but also bringing them together when they are related. Limit the impact of changes!

Contrary to what seems to be interpreted by most developers, the Single Responsibility principle is not only about separating things, but also about bringing together related concepts.

And I’m not the one saying it, look at this:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

Robert “Uncle Bob” Martin

Very well. But then these questions arise…

When to separate? When to join?

The key to understanding this principle is to first understand that the decision to separate or join is made with a focus on the people who bring the requirements.

In his post on the subject, Uncle Bob gives the example of a class that contains 3 methods, each of which is requested by a different C-Level executive:

  • The COO asks for a method that calculates the payroll
  • The CFO asks for a method that generates a financial report
  • The CTO asks for a method that saves everything to the database

A serious error in any of these methods can result in the resignation of the responsible executive. But, since everything is in a single class, it is easy to spoil what one method does while trying to fix something in another.

The question is how to prevent one executive from being fired because there have been changes in some other executive’s method.

The solution suggests the separation into 3 classes, each with its own method. With this, the limits are better delineated and it is easier to avoid the problem, because this approach prevents the methods from sharing private fields and methods.

But it is important to understand that Uncle Bob is not suggesting that you create a class for each method from now on! It is not how it works.

Understanding Single Responsibility in an e-commerce

Let’s increase the scope of the problem to my favorite example: the e-commerce website. With this, we’ll also be able to understand the Single Responsibility Principle (SRP) in the context of micro services.

The concept of “Product” is usually shared by the entire code base of an e-commerce. I mean, there generally is a single Product entity that is used throughout the whole system, right?

namespace Ecommerce
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
        public Uri MainImage { get; set; }

        // demais propriedades seguem...
    }
}

But if you think about it, a product has different meanings in each “department” of an e-commerce. In the catalog (search), the product is something with a description and pictures. At checkout, it’s something with a name and a price. In stock, it’s something with an EAN and a quantity.

Now, think of Amazon or any medium to large e-commerce. The people or departments that request things for the system are different. Marketing people (catalog) worry about very different things than the checkout people.

How to apply the Single Responsibility principle of so that changes in the catalog are unlikely to break the checkout?

The most interesting thing to do is to create a module for each of these areas. A module can be a namespace, but ideally it would be a DLL (or JAR, or GEM). That is, each module is complete, comprising all components from the user interface to the database. One module is physically separate from the others, and has everything it needs to work in isolation.

namespace Ecommerce.CatalogModule
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public Uri MainImage { get; set; }
    }
}

namespace Ecommerce.CartModule
{
    public class Product
    {
        public Guid Id { get; set; }
        public decimal Price { get; set; }
    }
}

namespace Ecommerce.CheckoutModule
{
    public class Product
    {
        public Guid Id { get; set; }
        public decimal Price { get; set; }
    }
}

That way, you would have several Product classes, one in each module. They all share the same Id, because after all in all of them the product is the same guy. But in each module, the product would have different properties in addition to the Id, and methods, all specific to that module.

Notice something I left on purpose: The Cart and Checkout modules initially present a Product with the same properties. But here we have important things to grasp:

  1. It is not because two things have the same structure that they are the same. As I have been saying, the product has different meanings in each context. Therefore, the demands of each area will push their product definitions in different ways. Understanding this and separating them prevents the product from becoming one of those giant classes (God Object), with 50 properties and several methods with very different business responsibilities.
  2. Here I am using a simplified model that only has data (properties), but the Object Orientation comes precisely to make encapsulation possible, which means that the Product class must have methods that represent the operations that can be done with the product. And here is where we’ll certainly have differences from the beginning.

In addition, each module would have its repositories, DTOs, service classes and everything else. It could even include the module’s own presentation layer!

namespace Ecommerce.CatalogModule
{
    public class Product { ... }
    public interface SearchService { ... }
    public interface ICatalogRepository { ... }
    public interface ICatalogQueries { ... }
    public class SearchResultsDto { ... }
}

namespace Ecommerce.CartModule
{
    public class Cart { ... }
    public class Product { ... }
    public interface ICartRepository { ... }
    public interface ICartQueries { ... }
}

namespace Ecommerce.CheckoutModule
{
    public class Order { ... }
    public class Product { ... }
    public class Customer { ... }
    public interface ICheckoutRepository { ... }
    public interface ICheckoutQueries { ... }
}

The example above is simplistic. Each module would have many more classes than that. But each module encompasses an area of ​​the e-commerce business. The things that change together are together, and the things that change separately are separate.

So here it is clear that the Single Responsibility goes beyond methods and classes: it reaches to the level of the software layers, rippling through the entire architecture.

If this seems crazy, know that it is not, as it is the reality of micro services. Each module is a service that is usually developed by a different team. These modules can even be developed with different languages ​​and technologies! Such is the level of separation.

Another important point to note in this example is that the division of namespaces (or folders in the project) does not occur for technical reasons. Within a module, I do not separate classes into folders like “Database”, “Services”, “Entities”, because these would be separations from the technical perspective. But the idea here is to keep classes that belong to the same business area together, at the same level. If there is separation, it must be because the business presents the same separation in its areas and processes.


If you wanna dive deeper and you already know DDD (Domain-Driven Design), I recommend this excellent talk by Nick Tune:

Single Responsibility limits the impact of changes

This is the essence of the principle. When it is applied well, you draw a clear boundary between the modules of your system, allowing you to focus the changes only on the parts related to the requirement. The change is surgical.

Note that this line of reasoning leads you to break what would be a single class into several classes spread over several modules, but it also leads you to bring together classes related to the same concept, whether they are entities, services or infrastructure. You have a more decoupled and cohesive code.

Thus, when the marketing department requests changes to the product’s page, there is no way to accidentally impact the other modules. They are physically separated.

Likewise, a change or new implementation may require changes in the service and infrastructure classes involved. It usually happens, because you hardly ever have a requirement that only asks you to change just some tiny detail. If you have to add a field, that field will come with business rules and persistence, which possibly trigger side-effects elsewhere. But that’s okay, because now the changes happen in different classes within the same module, physically separated from the others.

The opposite, which we unfortunately see daily in so many projects, would be what Martin Fowler calls Shotgun Surgery. Due to the poor separation of responsibilities, we have projects with a lot of duplicated code, which require more time to develop small features, and the system as a whole is difficult to maintain.

Techniques that help to apply the Single Responsibility

How to apply the Single Responsibility Principle in code? Well, in addition to the tips we’ve already seen above, here are some techniques that go in line with everything we talked about here:

Domain Events

This technique is easy to apply, but it is very efficient in separating responsibilities.

Imagine that an e-commerce customer reached a thousand dollars in purchase, was promoted to Premium, and you need to notify him by email.

In a coupled approach, the code that evaluates the customer’s total spend would be next to the checkout code. Also the code for sending the email.

With Domain Events, you trigger an event after a purchase, something like NewOrderPlaced. A handler captures this event to identify whether the customer should be promoted, promotes the customer and launches a new ClientPromotedToPremium event. Finally, a second handler captures this event and sends the email.

In this approach, the closing of the purchase and the customer promotion handler are part of the domain (business) layer. The handler for sending emails is in another layer, since communication with an SMTP server is infrastructure.

See the detailed solution here:

CQRS

The name of the technique itself has a lot to do with Single Responsibility. CQRS stands for Command and Query Responsibility Segregation, which basically consists of the separation between operations that read data from operations that write data.

Most systems have disproportionately more reads than writes. When opening any page you fire some queries, but you will likely only have a write if the user changes something.

If so, why do we put reads and writes in the same “package”? What makes the most sense is to optimize each case.

We’ll soon have a post about CQRS here in the blog. For now, I recommend reading this here.

Conclusion

The definition of the Single Responsibility Principle states that a class must have only a single reason to change, and that reason is the people for whom we build the code. If you get this and deeply understand needs of the business you are serving, it becomes much easier to create classes with a clear meaning.

Any change in the business requirements for a system will naturally require changes to the code in several places. But, if the system is structured according to the principle of “grouping things that change for the same reasons”, you can minimize the number of classes that must change.

By Phillippe Santana

Passionate about writting code that people can understand, I'm a software developer, a project manager, an entrepreneur, and people/culture enthusiast. Find me on [Linkedin](https://www.linkedin.com/in/phillippesantana/) and on [Medium](https://medium.com/@phillippesantana).