Single Responsibility Principle

Vlad Ungureanu
5 min readJul 8, 2019

--

The Single Responsibility Principle (part of SOLID, by Robert C. Martin 2000) states that a class needs to model a single, consistent concept. All properties and all methods of the class should be directly part of the encapsulated concept. Methods should also do a single thing in terms of the logical actions performed (not the actual instructions or operations).

A class that does many things will most likely be used in different other pieces of our code, leading to a tightly coupled system. Changes to that class may affect multiple functionalities at once leading to changes across our application and the need to re-test all classes that suffered changes. By adhering to the Single Responsibility Principle, we ensure a loosely couple system since our classes will only be used where they are actually needed and changes to a class have a minimal impact over the whole system.

Single Responsibility Principle (LearnStuff.io)

The most common example of Single Responsibility Principle is the N-Layer Architecture. Consider a situation where we want to model the functionality for saving a “Product” entity using a REST API. We would start with a controller, which, in practice, can incorporate all the logic up to saving the actual entity into the database, but that would break the Single Responsibility Principle.

Following the logical flow of data, we start with the Controller type of classes. Their purpose is the respond to requests made to a specific URL path, process the payload, URL parameters and headers, forward the data to the service layers and when processing is completed to return a specific result or error message with the appropriate status code.

This purpose limits the type of logical operations that can be performed in a Controller class. First of all, these types of classes should not contain business logic, database operation or 3rd party connectivity. Usually, the Controller defines a request contract. If business logic or persistence change then the contract should change, which means that all consumers or clients need to change their code as well. Decoupling these operations from the Controller allows the contract to remain the same even if business logic suffers changes. According to Model-View-Controller design pattern, controllers are also responsible for decoupling representation from logic and as such this type of classes should use Data Transfer Objects which decouple contract from business representation of the data.

Finally, due to the fact that Controllers are responsible for calling the service layer it is recommended that they also encapsulate input data validation. The purpose of this approach is to fail fast, instead of calling multiple methods and processes that we know will fail due to the fact that input data is not correct. To maintain Single Responsibility Principle, we encapsulate validation in a Validation Service and call it before we go forward to the business logic layer. For example, let us say a customer wants to use a coupon or a promotional code on a purchase. It is more efficient to validate the coupon or promotional code before we go to the business layer and return a message to the user that the code is invalid, instead of trying to do the payment and then validating the price only to discover the code is not correct. The same goes for size, type or input values for all fields, which may lead to errors when saving them into the database. On a similar note and approach, security is usually decoupled from the Controller and is trigger before the request actually reaches the controller body in order to prevent unsecured calls.

Service classes are used to encapsulate the business logic of the application. Based on the Dependency Inversion Principle all business logic classes should implement a fine-grained interface in order to allow for multiple implementations. Since the purpose is to encapsulate the business logic, these classes would have more dependencies then regular classes, would encapsulate repetitive or conditional control structures and would be responsible for calling any 3rd party applications. Service classes should not have direct interactions with the database, should not format data and should not build or convert objects directly. While all these operations may be required from a business point of view, they need to be delegate to other classes, as they do not represent the business logic but just helper functionalities to facilitate business needs.

Note that service classes may appear to break the Single Responsibility Principle because they may call multiple classes from different layers and packages in a single method but this is because they model the logical flow of data and as such, this is a correct practice.

Repositories should not contain any business or validation logic. Their only purpose is to interact with the database. Each repository needs to be specific to a single entity. Repositories do not convert or process data, however they are responsible for transaction management and in some extremely rare cases for manual rollback.

Another special, and less used, specialized class is the Direct Access Object or DAO which should act as interfaces over the database or persistence mechanism. While repositories are confined to working with entities, the DAO classes can also use Data Transfer Objects directly if the business flow requires it. Having access only to partial data from the consumers, DAO classes may perform direct database operation while working directly with DTO’s. From a Clean Code point of view there is no reason why DAO classes should not also operate with entities.

As with repositories, conversion to the required entity or DTO, needs to be delegated to specialized helper classes. DAO classes, like any other class should comply to all Development Principles and Clean Code rules. In practice, you usually have a DAO and a Repository for each entity, if the nature of the application requires it.

By separating each concern of the saving process between specific classes we ensure that each class will have a single and specific responsibility, unit testing can be easily done and changes to any of the classes will not affect any of the others.

Another very common application of the Single Responsibility Principle is the utility classes that we define to help business logic. Java, for example, is packed with many such classes like Math, Collections and Files which group operations related to a single concept in order to facilitate usage within our code.

Keep in mind that utility classes are not meant to contain things that do not really belong anywhere else and in the development community they are sometimes frowned upon by some OOP enthusiasts, so use them with care.

There are a lot of principles, rules, standards and laws in software development. From a practical point of view, the Single Responsibility Principle, is the second most important one after proper Naming Conventions. Correctly applying this to principle creates robust, maintainable, easy to change, easy to understand and easy to test systems, leading to cost effective delivery.

Want to learn more about Development Principles? See the new Practical Clean Code Course: https://learnstuffacademy.thinkific.com/courses/practical-clean-code

For exclusive discounts on all Learn Stuff Academy courses join the Facebook group:

https://www.facebook.com/groups/learnstuffacademy

For more information and 100+ free courses visit:

http://learnstuff.io

--

--

Vlad Ungureanu

Software Developer, Trainer, Personal Development Enthusiast.