This article is a kind of experience that was acquired as a result of a very unpleasant architectural error that I made during the long development of a project on
Laravel5 .
I will try to tell you how the
Repository pattern was used in the project, what advantages and disadvantages were revealed, how it affected the development as a whole, and what profit was obtained.
Introduction
I just want to warn you that the article is more likely focused on developers who are only familiar with the design patterns, read smart books, and then try to apply the whole thing, so to speak, in production. I will mention the development using frameworks that use ActiveRecord (for example, Yii, Laravel, etc.), because, thanks to ActiveRecord, I continue to step on the rake and learn to solve various problems.
Repository pattern
Literally in a few words I propose to consider what the
Repository is .
A repository is the concept of storing a collection for entities of a particular type.
You can read more about this pattern:
')
In general, there is a lot of information and it is quite easy to understand what a Repository is.
Starting from Repository
If you have developed medium and / or large (not in terms of workload, but rather with a large code base and long-term support) projects, then you most likely have encountered shortcomings and problems that arise when using ActiveRecord. The main ones can be highlighted in a small list:
- Violation of a single responsibility .
- From the first paragraph it follows that your “models” can be quite “fat”.
- Beginners form an incorrect concept of MVC, where M is understood as a model and it is == 1 class, mostly ActiveRecord.
- A very resource-intensive organization of data import / export, if you need for some reason to work with a large number of records at a time.
- It is inconvenient, and sometimes not realistic, to write custom SQL queries if necessary.
ActiveRecord naturally has advantages as well, but we will not mention them, since this is beyond the scope of our article. And at the same time, if in a few words, then: “ActiveRecord is quick, simple and easy.”
So, for several years of working with frameworks based on ActiveRecord, I have most likely come across all its flaws. And somehow having read through clever books and articles, while designing the architecture of a new project, I decided to implement the Repository pattern.
Based on its simple implementation and description, everything is simple: we take interfaces, bind them into classes, which will be our repositories that retrieve data from the “repository”. Everything is fine, at any moment we can bind another Repository, replace the implementation of sampling methods, in general everything is clear.
I went to change the status on
Systems Architect
Is your “repository” really a Repository?
And then there was a moment when I really needed to replace the implementation. I came to the office with a smile and with the thoughts: “As I’m all easy to submenu, I’ll just create another class and change the string during binding”.
However, the task was to replace the selection in the first case from a file, and in another from a third-party API. When I began to dig and understand all this business, I noticed that my "repositories" return models. Yes, that's right, my allegedly the Repository pattern returns all the same models that continue to walk around the entire project.
Yes, thanks to the interface, I was really able to easily replace the implementation, but the format of the returned data has changed. Previously, it was an instance class with ActiveRecord, but now my repository could return an array or collection.
What does it mean? This means that any representative of my team could use the individual features of the “model”. For example, mutators or accessors, or write a method in a model with logic and call it anywhere. Therefore, by replacing the implementation, I changed the data format and now I cannot guarantee that the entire application will work as it worked, just as anything could have happened. Starting from referring to the innocuous method of the model anywhere, and ending with the save () call in the form. No one knows, no one remembers, especially if the project was experienced by several developers who left and were replaced by new ones.
Do not panic, tests
Then I remembered that you can run the tests. I turned to my partner and asked: “did you write the tests?”. He in turn turned to another colleague and clarified the same question. In general, as it turned out, not a very large% of our application was covered with tests.
What we have?
So, we have an additional layer of abstraction, which requires a larger entry threshold and more time to develop with efficiency aspiring at 0, since the models both walked around the project and continue to walk.
Went to change status to
Junior Assistant
In more detail we understand a problem
Understanding the system more deeply, interrupting the examples, I noticed that many developers make such mistakes and even worse.
It may not be good, but I want to present a similar example of a
poor implementation of the Repository pattern .
Looking through the information on the topic, I found the following application on Laravel:
https://github.com/Bottelet/Flarepoint-crm/
Let's look at the example UserRepository:
https://github.com/Bottelet/Flarepoint-crm/blob/develop/app/Repositories/User/UserRepository.php
One of the methods I want to make out here (in case all this disappears):
... public function create($requestData) { $settings = Settings::first(); $password = bcrypt($requestData->password); $role = $requestData->roles; $department = $requestData->departments; $companyname = $settings->company; if ($requestData->hasFile('image_path')) { if (!is_dir(public_path(). '/images/'. $companyname)) { mkdir(public_path(). '/images/'. $companyname, 0777, true); } $settings = Settings::findOrFail(1); $file = $requestData->file('image_path'); $destinationPath = public_path(). '/images/'. $companyname; $filename = str_random(8) . '_' . $file->getClientOriginalName() ; $file->move($destinationPath, $filename); $input = array_replace($requestData->all(), ['image_path'=>"$filename", 'password'=>"$password"]); } else { $input = array_replace($requestData->all(), ['password'=>"$password"]); } $user = User::create($input); $user->roles()->attach($role); $user->department()->attach($department); $user->save(); Session::flash('flash_message', 'User successfully added!');
- Well, firstly, the Repository is an abstract work with the repository. Ie take something or put something. There should be no logic in the Repository.
- Secondly, you cannot use bcrypt and similar things inside the Repository, because if you write the application yourself, you remember this, if you have a command, then there may be a situation where someone will put an encrypted password in the Repository, you will look for an error long.
- Further, the Repository is an abstract repository, so it cannot know about Session, since it may be necessary to save something using a console call.
- Again, the result is a model that uncontrolledly “walks” on the application. No one protects you from using all the magic of ActiveRecord.
Probably all, if you analyze such examples in more detail, you can find many more interesting things.
This is a typical example when people do not themselves reach patterns, but recite smart books and pop their repositories, etc. ...
How to use Repository correctly?
- Well, firstly, you should clearly understand why you need this design pattern.
- Secondly, the Repository assumes the presence of entities that can be driven through the application. Ie, the Repository must both accept and return a single format for storing data. As a rule, this Entity is a class with getters and setters without logic. It turns out it should be like this: if we change the data source, then we should not change the return format.
- Further, if you use frameworks with ActiveRecord, probably in 99% of cases, the Repository will be redundant, since the position of ActiveRecord itself is a certain combination of Repository / Entity / Presenter, and in the case of Yii2, it is also a filter and validation. Accordingly, in order to truly wrap the entire ActiveRecord into the Repository, you will need to build an impressive layer of abstraction and an entire infrastructure.
- If it is still necessary for some reason to make friends with Yii, Laravel (or something similar) with the Repository, Doctrine is probably the best option. For Yii2 and Laravel5, there are exactly extensions, it means that someone is doing something similar.
Implementing a Repository Pattern or Something Like That
I found
an article that describes the implementation of the Repository pattern for Laravel5 (most likely for Yii2 it will be about the same). However, in my personal opinion, it rather describes a structured approach to writing queries using ActiveRecord. On the one hand, it is convenient, duplicate code decreases, models lose weight and the architecture is more elegant. On the other hand, the Repository does not quite fulfill its role as an “abstract repository”, since work is going on with models and full binding to ActiveRecord with all its magic.
The danger may be as follows: when changing the data source (note, it is not necessary to change the database or framework, it is enough to get data from another resource, for example from a third-party API or by making a complex custom query using the query builder), if you worked with models , and the new implementation will return an array or collection, then most likely you will not be able to guarantee the stable operation of your application. So, as you simply do not know (if the project is large and is written not only by you), what methods, accessors / mutators and other delights of the models were used and where.
findings
Having obtained a useful and at the same time bitter experience in designing an application, for myself I can emphasize the following conclusions that I want to share (maybe it will be useful to someone):
- You must clearly understand why you are using the Repository, and indeed any design pattern. It is not enough to know or understand how to implement it, where it is more important to understand why you want to use it and whether it is really necessary.
- Do not practice your newly acquired knowledge on a new commercial project. Practice on cats or home projects.
- Do not attempt to play with Repository in frameworks with ActiveRecord. I repeat: almost always it will be redundant, except for those options when you really know what you are doing and give yourself a full account of the consequences.
- Expand your horizons by viewing other tools. Do not be one-framework-developer
- Tests would be nice.