📜 ⬆️ ⬇️

Near-architecture arguments or the results of a single dispute

One fine ordinary Thursday, a single development team disagreed on some architectural solutions, the implementation of which was approved by an order from above, and was not born in the course of a reasoned dispute. After some time, a similar dispute arose again, already on a new project. The discussion was getting hotter, and to clarify the situation and achieve enlightenment, even external higher forces were involved. The latter was not achieved, and yet, by the same strong-willed decision, one of the options for the development of being was adopted. But the wise Kaa, one of the participants in the discussion decided not to leave unresolved disputes in the team - the ground for future conflicts, split of the team and other fights .
In this document, I was asked to describe the advantages and disadvantages of the two approaches, reach a consensus and establish peace and justice in the Universe.
Below, I will try to do it to the best of my intellectual abilities (therefore I will use very simple words and phrases) and bring to the court the bloody butchers of the venerable public.


Willing path leading to the abyss of achievement of nirvana

We have a set of services that deal with data processing, use repositories for data storage. Since Since repositories are often slow sources, some services use temporary storage such as Session and Cache. And since services are used not only from web applications, these temporary storages cannot be used directly (for example, a console application does not have a session at all), therefore services also work with them. (That is, there is no clear concept of what a service is in our architecture and its highest purpose is not indicated. In essence, with this approach, service is a class that does something)
')


Fig.1 Hierarchy of classes of the first approach
An example of the implementation of all this happiness can be downloaded on Git Hub

What I don't like here:

Firstly, I do not like the lack of clear agreements on the delegation of powers: both the service and the other, but they are engaged in completely different things. One is engaged in data processing, the other is their temporary storage.

Secondly, I do not like the SessionDataServiceBase class, namely the T Get method (string key, Func getData), which not only works with the session, but also deals with data processing. It is assumed by the name of the ISessionDataService (and indeed as originally intended) that it is only a wrapper over the Session and the maximum that it can, so it returns the default value. If you turn to the history of the development of this object, then the method T Get (string key, Func getData) (or its equivalent) was not described in the interface. It was implemented in every class that used the implementation of this interface (and on the one hand, in my opinion, this is correct). This approach had a minus - it is the repeatability of the code. I noticed this when I decided to add session usage to one of the services. After a brief study, a large amount of duplicate code was discovered, more precisely, this duplication was in ALL services. This violated my integrity of the perception of the universe and did not allow me to live in harmony with it. Delegating to the session service some additional work with the data seemed to me wrong, and it was decided to put it in some base class in order to remove this dissonance from my soul (details in the description of the second path to the truth).

The third problem that was revealed to me by the Lord our God, His Holiness's binary code, is the keys. They were assigned values ​​(I’m not afraid to admit, and sin was for me at one time) in the style of “who is in the forest, who is of the wood”. And the void Clear method (string [] sessionKeyPrefix) sometimes gave completely unexpected results. There were other problems, but we will not touch them, the solution of these problems also gave a competent inheritance.

The fourth problem is testing. After being translated by an volitional decision on this structure, all unit tests of methods using T Get (string key, Func getData) under the hood dropped. I could not get them wet quickly, even with the help of our unit testing guru, and I was asked to score on them in general, which is not good in my opinion.

And now about the advantages - we cannot say anything about them, that is, in the sense of me, because I am for the second way, filled with piety and perfection, leading to the most perfect nirvana, in which the code is converted directly into binary codes, bypassing the humiliating and boring conversion process to CIL.

Path ridiculed by contemporaries, like all truly great things

This approach is based primarily on agreements and restrictions. It is assumed that the service is a class that processes the received data and stores the results of this processing in permanent repositories, with which it works through the repository, and it has one repository, and the service does not know how it stores data. He doesn’t care, database, file, third-party service on the Internet, etc.
In an amicable way, there would be the right way to make some kind of base class or interface, which would show that this class is responsible for processing and sending data between the repository and the end user. But, unfortunately, this is not done, and, as we see in the first approach, we have a class (HttpContextBasedSessionDataService), called the service, but without a repository, and does not process the data. Therefore, we will assume that we still have a basic entity for services, it will be an IService interface

Now about the HttpContextBasedSessionDataService and similar classes. It appeared due to the fact that repositories are slow data sources, firstly, and they should be used as little as possible, since this is always a bottleneck, secondly. Therefore, it would not be bad to keep some data at hand - and here a new type of classes appears, they do not process data, do not have a repository, they only provide access to temporary storage. In principle, it is closer to repositories than to services, only temporary repositories, such as, for example, Application, Cache, Session. Let's call the base entity IShorttermStore and accept that the names of these classes will not mention the word Service.

And now we will try to build a hierarchy of classes based on these calculations, which will eliminate the shortcomings of the previous implementation, and we will move some of the functionality according to the logic of working with temporary repositories into the base service class, namely, working with keys and lazy initialization logic.
And now let's take a look at the class diagram constructed according to this concept:



Fig.2 Class hierarchy of the second approach
An example of the implementation of all this happiness can be downloaded on Git Hub

Here deficiencies with which, in my opinion, the first approach suffers are eliminated. Unit tests work without any extra mocks.
Now about the imaginary flaws.
If we recall the arguments of the disputants, then there is a drawback that if the class also wants to use the Sache as well as Cache, then problems arise. But in fact, there are no problems with this: if such a need arises, then it may be worth thinking about SOLID, in particular, the letter I in this abbreviation (Interface segregation principle) and not making monsters capable of “embroidering and typing too”.
I can’t say anything more about the shortcomings, as the memory is selective and only remembers the bright and the good, and not at all the criticism. Please start breaking me in the dust, and then something is very good at everything.

Mikhailichenko Alexey, Software .Net Developer, Tech Lead

Source: https://habr.com/ru/post/277077/


All Articles