In theory, everything is clear, but in practice there are always difficulties. They are connected with the fact that it is not clear on what scale this very "Only Responsibility" should be considered. Here I need to work with the database. If I create a DataBaseInteracting class, can we assume that it will have one responsibility? But then he writes the data to the database and reads them from there and updates it. Can it be better to create 3 classes: DataBaseWriter , DataBaseReader and DataBaseUpdater ?
4 answers
The principle of a single duty applies to virtually any scale: method, class, module, subsystem, etc. When choosing a duty, as always, there should be some rationality. Moreover, it is sometimes possible to deviate from all principles. It will come with experience.
In your example, I would say that there is no need to split the DataBaseInteracting class according to the actions. The duty of "providing data storage" is quite atomic, in my opinion, and it is not necessary to divide it into read / write / update. However, it makes sense to share in other planes. For example, if in this class there is code for building SQL queries, then it should be moved to a separate class. Moreover, if you need support for multiple databases, there should be several such classes.
So if there is a need for some high-level change in “providing data storage,” you will change the DataBaseInteracting class. If you find a bug with building a non-optimal query, you will change the class that builds the queries and will not even touch DataBaseInteracting . If you need to support a new database, you simply add a new class. Do you understand?
At the same time, all these classes should be in the DAL project. And this project should be based only on what relates to the logic of interaction with the repository (the database in your case). So you support the SRP at the project level.
- And if I write in java, instead of splitting into projects, you can divide them into packages (I did)? - Alexander Elizarov
- @ AleksandrElizarov yes, that’s the way to do it. - andreycha
- thank! Can I clarify something else? For example, I will create a class SQLQuerysCreator. Is it okay that in my project there are a lot of such classes that do not correspond to objects of the real world? (because in my opinion I already have more of them than those that correspond to real-world objects :)) - Alexander Elizarov
- @Alexander Elizarov is fine. Who told you that there must be a match? There is a bunch of software in which no class corresponds to a real-world object. For example, mathematical :). - andreycha
The so-called "Principle of Responsibility" (SRP) actually has nothing to do with the number of features that the entity implements.
The principles of SOLID are aimed at solving specific problems that arise when working with code, and in the case of SRP, this is a problem of multiple code changes when a technical task is changed (TOR).
If the code responsible for the execution of the TK item is scattered across many sources, then changing this TK item will have to edit the source code in many places. Therefore, the SRP recommends that we collect all the code responsible for one or another item of the TK in one place. So that when the TK changes, it would be necessary to edit only one place in the code (one module, or one file, or one class, or one function, or one line).
From this it follows that if several TK items can be made in one piece of code, then this is normal, but the opposite situation — when one TZ item needs many pieces of code — this is already bad. Responsibility is smeared on the source, SRP is not respected, colleagues cry from commits of 40 files.
Therefore, if DataBaseInteracting is one small class, then from the point of view of SRP, there is no point in splitting it into a DataBaseWriter , a DataBaseReader and a DataBaseUpdater . Another thing is that there are other principles, such as OCP (replacing some pieces of code with others), or the problem of isolating pieces of code when testing.
- oneBut FIG understand where the truth! Who says that the object should have one duty. And who says that the object should have one reason to change. You say that there should be one point to change the object. And each is right in its own way, not even in its own way, but really right, because all this should be. Only here it is really interesting what is hidden under (SRP). - user220409
The problem arises from the fact that large systems consist of several layers, and the level of detail on each layer is different.
Therefore, the only responsibility is logical to determine for the degree of detail characteristic of the layer.
Since the DatabaseInteracting class seems too abstract to me, I’ll suggest replacing the well-known Repository pattern instead. There are several modifications of it, and in order not to be confused, let's take as a basis the modification proposed by Evans in the book on DDD.
Evans writes that the Vault provides long-term storage of domain-domain objects - this is his responsibility. It is implemented through read, update and delete operations (here you can add and create, but this is different from the canon DDD).
Moving to the level below (examining operations in detail), we understand that to speed up work with the database, you can apply the master-slave model, where one database is considered to be the main one and a few more subordinate ones. Subordinate databases constantly synchronize their content with the main database, so that all servers have the same data.
Writing always takes place in the main database, and you can read from anywhere. With a large number of readings, such an organization of the application gives a significant increase in speed.
At this level of detail, we understand that our connections to the database server will be of two types: for reading and for writing-reading. Accordingly, the read operation will create the connection of the first kind, and the update and delete operations of the second.
You will have the ReadOnlyConnection and ReadWriteConnection classes, but at the level of subject logic you will operate only with the Store .
From my own experience I can say that the cause of obscure code is very often the confusion of detail levels. Here should help group discussion of the code or the sighting of leading programmers.
- oneA person with SRP is confused, and you explain to him with the example of DDD. Shtaaa? :) - andreycha
- @MarkShevchenko, thanks for the detailed answer! But I apparently did not quite clearly outlined the picture. I am writing this project alone, from scratch, for myself. Therefore, I have no one to conduct group discussions. And how can I define the only one, guided by the detailing characteristic of the layer? I just have the problem that I don’t know what kind of detailing to set for the layer. - Alexander Elizarov
- The layer of detailing rather has to be set, not detected. Personally, “pronunciation” helps me: it usually allows to select entities (objects) and operations (functions). Usually the difference between the layers goes along the border between what and how. As long as your suggestions concern what the program does on this layer, this is the top layer. As soon as you start talking about how it works, you descend to the bottom layer. - Mark Shevchenko
Firstly, this principle is very controversial and therefore almost not applicable in practice. Almost never TK, which is written in a software product. And if there is a detailed TZ, it does not change. In addition, it is clear that for any TK, you can easily come up with such an edit, because of which you will have to radically rewrite the program. After all, different parts of the program are based on the same ideas from TK, which may change.
Secondly, this principle contradicts the principle of KISS (keep it simple stupid / the simpler the better), one of the few that really works in the design of systems. If you break an object into parts, then you complicate the program, instead of one class you get 2, 3, 4 and more. As a result, often, if the original program could be easily understood, the “enhanced” is already difficult, because there are many objects in it and they all do not fit into consciousness.
- SRP does not contradict KISS, because, for example, reduces the complexity of making changes to the program. "Many classes"! = Complex system. Well, your statement about the violation of KISS, to put it mildly, is devoid of objective arguments. - Dmitriy Simushev