⬆️ ⬇️

Repository pattern. Basics and explanations

Repository commonly refers to a storage location, often for safety or preservation.

- Wikipedia



This is how Wikipedia describes the repository. It just so happens that unlike some other slang terms that we are dealing with, this term perfectly conveys its essence. A repository is the concept of storing a collection for entities of a particular type.



Repository as a collection



Probably the most important difference between repositories is that they are collections of objects. They do not describe storing in databases or caching or solving any other technical problem. Repositories are collections. How you store these collections is simply an implementation detail.



I want to clarify this issue. A repository is a collection. A collection that contains entities and can filter and return the result back depending on the requirements of your application. Where and how it stores these objects is an IMPLEMENTATION DETAIL.

')

In the PHP world, we are used to the request / response cycle, which ends in the death of a process. All that has come from the outside and has not survived is gone forever, at this point. So, not all platforms work that way.



A good way to understand how repositories work is to have your application running all the time, in which case all objects remain in memory. The probability of critical failures and the reaction to them in this experiment can be neglected. Imagine that you have a singleton instance of the repository for the entities Member , MemberRepository .



Then create a new Member object and add it to the repository. Later, you will request from the repository all the items stored in it, so you will get a collection that contains this object inside. You may want to get some specific object by its ID, it is also possible. It is very easy to imagine that inside the repository these objects are stored in an array or, even better, in a collection object.



Simply put, a repository is a special kind of trusted collection that you will use again and again to store and filter entities.



Repository Interaction



Imagine that we are creating a Member entity. We bring the object to the required state, then the request ends and the object disappears. The user is trying to log in to our application and can not. Obviously, we need to make this object available to other parts of the application.



 $member = Member::register($email, $password); $memberRepository->save($member); 


Now we can access the object later. Like that:



 $member = $memberRepository->findByEmail($email); // or $members = $memberRepository->getAll(); 


We can store objects in one part of our application, and then retrieve them from another.



Should repositories create entities?



You can meet such examples:



 $member = $memberRepository->create($email, $password); 


I have seen many arguments in favor of this, but I am not at all interested in such an approach.



First of all, repositories are collections. I'm not sure why the collection should be a collection and a factory. I heard arguments like “if it’s more convenient to apply this way, why not hang up the handler for such actions” ?



In my opinion, this is an anti-pattern. Why not allow the Member class to have its own understanding of how and why an object is created, or why not make a separate factory to create more complex objects?



If we treat our repositories as simple collections, it means that we don’t need to load them with unnecessary functionality. I don't want classes of collections that behave like factories.



What are the benefits of using repositories?



The main advantage of repositories is the abstract storage mechanism for collections of entities.



By providing the MemberRepository interface MemberRepository we untie the hands of a developer who already decides how and where to store data.



 interface MemberRepository { public function save(Member $member); public function getAll(); public function findById(MemberId $memberId); } 




 class ArrayMemberRepository implements MemberRepository { private $members = []; public function save(Member $member) { $this->members[(string)$member->getId()] = $member; } public function getAll() { return $this->members; } public function findById(MemberId $memberId) { if (isset($this->members[(string)$memberId])) { return $this->members[(string)$memberId]; } } } 




 class RedisMemberRepository implements MemberRepository { public function save(Member $member) { // ... } // you get the point } 


Thus, most of our applications know only the abstract notion of MemberRepository and its use can be separated from the actual implementation. It is very liberating.



What are the repositories: Domain or Application Service Layer?



So, here is an interesting question. First, let's determine that the Application Service Layer is a multi-layered architecture that is responsible for the specific details of the implementation of the application, such as database integrity, and various implementations of working with Internet protocols (sending email, API), etc.



We define the term Domain Layer as a layer of a multi-tier architecture that is responsible for business rules and business logic.



Where does the repository go with this approach?



Let's look at our example. Here is the code written earlier.



 class ArrayMemberRepository implements MemberRepository { private $members = []; public function save(Member $member) { $this->members[(string) $member->getId()] = $member; } public function getAll() { return $this->members; } public function findById(MemberId $memberId) { if (isset($this->members[(string)$memberId])) { return $this->members[(string)$memberId]; } } } 


In this example, I see a lot of implementation details. They should certainly be included in the application layer.



Now let's remove all the implementation details from this class ...



 class ArrayMemberRepository implements MemberRepository { public function save(Member $member) { } public function getAll() { } public function findById(MemberId $memberId) { } } 


Hmm ... it starts to look familiar ... What have we forgotten?



Perhaps the resulting code reminds you of this?



 interface MemberRepository { public function save(Member $member); public function getAll(); public function findById(MemberId $memberId); } 


This means that the interface is on the boundary of the layers. and in fact may contain domain-specific concepts, but the implementation itself should not do that.



The interfaces of the repositories belong to the domain layer. The implementation refers to the application layer. This means that we are free to build architecture at the domain level without having to depend on the service layer.



Freedom to change data storage



Whenever you hear someone talk about the concept of object-oriented design, you could probably hear something like "... and you have the opportunity to change one storage implementation to another in the future ..."



In my opinion, this is not entirely true ... I would even say that this is a very bad argument. The biggest problem in explaining the concept of repositories is that the question “do you really want to do this?” Immediately arises. I do not want such questions to influence the use of the repository pattern.



Any sufficiently well-designed object-oriented application automatically fits the above succession. The central concept of OOP is encapsulation. You can provide access to the API and hide the implementation.



After all, you really will not switch from one ORM to another and vice versa. But even if you want to do this, then at least you will be able to do it. However, replacing the implementation of the repository will be a huge plus when testing.



Testing using the repository pattern



Well, everything is simple. Let's assume that you have an object that handles something like the registration of participants ...



 class RegisterMemberHandler { private $members; public function __construct(MemberRepository $members) { $this->members = $members; } public function handle(RegisterMember $command) { $member = Member::register($command->email, $command->password); $this->members->save($member); } } 


During the next operation, I can take an instance of DoctrineMemberRepository . However, during testing it is easy to replace it with an ArrayMemberRepository instance. They both implement the same interface.



A simplified test example might look something like this ...



 $repo = new ArrayMemberRepository; $handler = new RegisterMemberHandler($repo); $request = $this->createRequest(['email' => 'bob@bob.com', 'password' => 'angelofdestruction']); $handler->handle(RegisterMember::usingForm($request)); AssertCount(1, $repo->findByEmail('bob@bob.com')); 


In this example, we are testing a handler. We do not need to verify the correctness of storing repository data in the database (or else where). We test the specific behavior of this object: register the user based on the form data, and then pass them to the repository.



Collection or Condition



In the book Implementing Domain-Driven Design, Vaughn Vernon makes a distinction between repository types. The idea of ​​a collection-oriented repository (orig. - collection-oriented repository) is that work with the repository is in memory, as with an array. A repository focused on the storage of states (orig. - persistence-oriented repository) contains the idea that there will be some kind of deeper and more thoughtful storage system. In fact, the differences are only in the names.



 // collection-oriented $memberRepository->add($member); // vs persistence-oriented $memberRepository->save($member); 


I note that this is only my opinion and so far I adhere to it in terms of using repositories. However, I would like to warn you that I can probably change my mind. In the end, I focus on them as collections of objects with the same responsibilities as any other collection object.



Additional Information



everzet has created a repository project on Github that is definitely worth a look. Inside you will find examples of working with storage in memory and files.



Results



I think that…

  1. ... it is important to give the repositories a singular task to function as a collection of objects.
  2. ... we should not use repositories to create new instances of objects.
  3. ... we should avoid using repositories as a way to move from one technology to another, since they have so many advantages that are difficult to refuse.


In the future, I plan to write a few more articles on repositories, such as caching results using the decorator, querying using the criteria pattern, the role of the repository in processing batch operations on a large number of objects.



If you have questions or if your opinion is different from mine, please write comments below.



As always, I intend to update the article in order to synchronize it with my current opinion.

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



All Articles