I have written the UserService
class(in logic layer, not persistance layer) about the user and it contains these methods.
Does this class with these methods violate the SRP?
python
class UserService:
repository: Repository
def create(...):
self.repository.save(...)
def patch(...):
self.repository.patch(...)
def delete(...):
self.repository.delete(...)
def get_one(...):
return self.repository.get(...)[0]
def get_list(...):
return self.repository.save(...)
If this has many responsibilities how can I distribute class?
The Single responsibility principle is a tricky one.
Let's start with defining what is a responsibility in the context of a software system and the scope of that responsibility.
Responsibilities come in many different forms. Everything that you think can be a responsibility and everything in your code can have responsibility.
Your modules have responsibilities. If you have a Billing module this module has the responsibility of handling Billing.
Now if you dig deeper, you Billing module can contain multiple layers. Each layer has a specific responsibility. Presentation, Business logic etc.
Digging deeper narrowing the scope, we get that each layer is composed of different classes and/or functions. Each one of them can have a responsibilities.
Now we get to functions and the code inside. In there you can have multiple statements if, for, = etc. Each statement is doing something that is a responsibility. If you analyze your statements you will notice how many responsibilities a function/method has.
If you have UserRepository
and the only thing it does is communicate with the DB or mediate between you app and the ORM, then it has this responsibility. That doesn't mean that you Repository
will have a single method. In order to adhere to SRM, your UserRepository
should have only methods that deal with DB communication related to users
. It should not have any business logic in it.
If you have a UserService
and it only has operations related to users then this service adheres to the SRP because it's responsibility is to have operations related to Users
.
Now here's an extremely tricky part of the SRP.
In order for your UserService
to do it's job it need to call the UserRepository
. If this service creates users and then adds them to the database, doesn't that mean that the UserService
has the responsibility of saving new users?
We see that we have two distinctive responsibilities here.
The responsibility of knowing how to do a thing. The UserRepository
knows how to communicate with the ORM or DB and save new users.
The responsibility of saying when the thing needs to be done. The UserService
knows when the User
should be saved not how or where it should be saved.
You have two distinct responsibilities. If you persistence changes, because you will change how and/or where, you will only change the Repository implementation but the Service wont be affected. If your business logic changes you change when, so the Service will change but not the Repository.
Another thing about it is when an interface of an object gets large.
People start to wonder if this large interface adheres to the single responsibility of not?
If all the methods are cohesive, it does. Which means that the size of something doesn't mean that it violates the SRP. It may violate other principles like the Interface Segregation Principle, but that doesn't mean that it violates the SRP. It's just hard to use, read, modify etc. In this case you can break it down to multiple smaller things.
Here's an example. Let's say you store settings for your app. You can design a single interface ISettingsProvider
that has properties for all the settings and will lead to an interface with 50 methods. If we define the responsibility as saving settings, this interface doesn't violate the SRP. If we define the responsibility as saving settings for specific part of the application, then this interface will violate it.
The above example has the purpose of showing that sometimes the SRP can be subjective and that granularity matters. If you define your responsibilities with smaller scope, then in order to adhere to the SRP, you will need to design smaller interfaces, functions of classes.
Thing about it as a tree structure. The top scope it broader and is composed of more fine grained scopes and so on. Depending on where you look, your components/objects/modules may adhere to the SRP.
If you have a Billing module in one huge class, from the point of view of the system, the module fits the SRP perfectly. From the point of view of responsibilities inside the module, the class the implements the module will have business logic, DB communication code, query building etc. and this class will violate the SRP.