How you shouldn’t use Repository pattern
Bài đăng này đã không được cập nhật trong 3 năm
Introduction
This article is reflection of my experience, which I gained as a result of very unpleasant system architecture mistake made during development of Laravel 5 project.
I will try to explain how I used Repository pattern in that project, which pros and cons were revealed, how it affected development process in general and which benefit was gained.
Repository pattern
Repository is a design pattern which introduces concept of storing collections of specific types.
Begin developing with Repository
If you have ever developed middle and/or big projects (not highly loaded, but with hug code base and long-time maintenance), then you definitely have faced disadvantages and troubles, which appear with using ActiveRecord pattern. The main issues are:
- Violation of single responsibility approach.
- From the first goes result that models become quite “fat”.
- Freshers get wrong understanding of MVC, where as M they consider the model as a single ActiveRecord instance class.
- Data import/export is very costly if you need to work with big amount of records at once, and sometimes it is impossible to write custom SQL queries.
Of course ActiveRecord has a lot of advantages, but that is a topic for a different article. In general “ActiveRecord is fast, simple and easy”.
So far, in a few years of working with ActiveRecord-based frameworks, I mostly faced only disadvantages of the pattern. And after I read books and articles, I had decided to implement Repository pattern in the architecture of new project.
Considering it’s easy implementation and description, everything should have been simple: we had to create interfaces and then create classes implementing them, which will be our repositories to get data from storage. Awesome! at any moment we should have been able to replace interface implementation to any other Repository, overwrite querying logic and get benefit.
User changed status to Systems Architect
Is our new “repository” really a Repository?
Everything had been nice until I really needed to replace implementation of the interface. I came to office in a good mood with one thing on my mind — as I would simply create new class and change one line in Dependency Injection binding”.
But the task was to replace querying from DB to querying from external API. When I started to get into the code, I noticed that the “repositories” return model classes anyway, and those models still were being used across all the project.
Yes, thanks to the interface I really could have replaced implementation, but return type had changed. It was an ActiveRecord instance before and after it might have been an array or collection.
What does it mean? It means, that any team member could have used model-specific features. Mutators and accessors, for example. Or even implement own model method and use it somewhere. So when I replaced implementation, I couldn’t guarantee that all the application continued to work as before and everything bad might have happened. Starting from some call to model method in a controller and up to call to save()
method in any view. Nobody knows, nobody remembers, especially if the project has been developed by several developers, which were replaced by new ones.
No panic! We have got tests
Then I realized that we can run the tests. I faced to my colleague and asked if he wrote tests to cover such cases. And he faced to other colleague and asked the same question. It turned out that not big part of our code was really covered by tests. Yes, we had behavior tests to check if API returns correct values and basic pages work as needed. BUT we didn’t have a lot of tests to cover really important internal logic of application.
What is the result?
By implementing Repository pattern we just created an enormously huge abstraction layer, which required higher experience threshold and increased development costs a lot, while we had not got get any efficiency from it, because models were still used across the project without any change.
User changed status to Junior Assistant
A bit more details
After sorting out the issues and studying examples, I noticed that many developers make the same mistakes and even worse.
Maybe it is not good, but I want to show one very bad Repository pattern implementation.
I found on application based on Laravel: https://github.com/Bottelet/Flarepoint-crm/
Let’s take a look at UserRepository example: https://github.com/Bottelet/Flarepoint-crm/blob/develop/app/Repositories/User/UserRepository.php
WARNING Very bad code below!
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!'); //Snippet in Master.blade.php
return $user;
}
- Well, firstly Repository is an abstract data storage connector. It means only get or store something. It must not contain any logic.
- Secondly, you must not use
bcrypt
or any other similar things in the Repository implementation and model implementation too, because if you create the app on your own, you remember that, but if you work with a team, then any situation might happen, like somebody might try to put encrypted password to the storage and you will spend a lot of time to find the mistake. - Next, Repository is an abstract storage, so it cannot know anything about Session, because it may be needed to store something from console usage.
- And finally, the return type is still a model, which is used across the application without any control.
This is a typical example when people don’t really understand some pattern themselves, but read some smart books or articles and start to use patters everywhere without any common sense.
How to use Repository correctly?
Firstly, you must understand, why exactly you need to use this kind of design.
Secondly, Repository pattern assumes you have some instances, which you may use across the application. It means that all Repositories must return the same data format (or return type). In general it should be a Entity class with getters and setters only, but not any logic. Why? Because if we change the data source, we must not get any data format changes.
Next, if you use any ActiveRecord-based framework, then with possibility of 99% using the Repository pattern is overabundant.
ActiveRecord is actually some kind of combination of Repository/Entity/Presenter and in case of Yii2 even filters and validators. And of course, if you would like to wrap all ActiveRecord inside a Repository, you will have to create an imposing abstraction layer and the whole infrastructure should be changed.
Anyway. If there is any case when you really want to use Repositories in Laravel, Yii or some other framework, then the best option is to use Doctrine instead of bundled database packages. There are definitely some extensions for Yii 2 and Laravel 5 to implement that.
Conclusion
I have got very useful and bitter experience in system architecture. And for myself I made the next conclusion: Don’t try to play Repositories game with ActiveRecord frameworks! It would be excess cost in most cases. But if you really want to use this pattern, you should be 100% sure and responsible of what you want to get and why you want to use it.
And one more. Write good unit tests.
Good luck!
All rights reserved