Repository pattern and relationships

Published 4 months ago by xxRockOnxx

Searched the internet for this but even straight googling of this topic does not give anything related.

I would like the answers to be general programming answer rather than language or framework specific.

So in a repository pattern, how do you do relationships? Eager by default? Query if needed (kind of lazy load)? Have extra methods in repo?

Say StudentRepo which I think is the best example... would you

  • findStudentWithGuardian?
  • findStudentById and findGuardianByStudentId (on GuardianRepo)

First one seems dirty to me. What if you needed a nested relationship?

Eager load by default seems dangerous on some cases kind of 50/50 to be used a solid solution.

Those are the only solution solutions can think of. There might be some answers out there. How do you solve this?

xxRockOnxx

Anyone that can share his idea?

shez1983

take a look at this post which deals with relationships

https://medium.com/@smayzes/how-do-you-work-in-laravel-5a763fe5c5a0

xxRockOnxx

Thanks for the article @shez1983 i might not need doctrine anymore with that technique on the article but it did not really answer my question.

Imagine firing an event and you passed in the Student object and on the listener you might want to send an SMS or Email to a guardian.

Putting eloquent aside because it loads relationship when accessing the relationship that has not been loaded yet, doing student->getGuardian() would be null if it is not eager loaded.

How do you solve that? Do findGuardianByStudentId()? Or make a repo method that will join the relationship called findStudentWithGuardian()?

Cronix
Cronix
4 months ago (790,130 XP)

I'd just eager load the relationship on $student, and pass that to the event.

xxRockOnxx

So eager load by default? Thing with repository pattern is you can't eager load on the fly :\

Cronix
Cronix
4 months ago (790,130 XP)

No, not by "default." You specifically mentioned an event, and needing the relationship data in that listener. If you have a method that is passing something to an event, why couldn't you load the relationship there before passing it to the event if you know you need it?

xxRockOnxx

because "repository pattern" a.k.a Doctrine2/Hibernate style.

Cronix
Cronix
4 months ago (790,130 XP)

I'd argue an event doesn't belong in a repository, either. Only database logic.

$user = $this->repository->update(blah);

event(new SomeUserThing($user->load('relationship')));
xxRockOnxx

I dont fire event in repository but rather passing an object returned by the repository to the event.

$student = $this->repository->findById (1)

event (new FooEvent ($student)

In an event there might be multiple listener, and one of the listener operates on the student's relationship.

Repository only return POJOs/POPOs. It doesn't have any actions such as loading of relationship that's why I'm kinda confused and asked what is the best practice for this.

If you do student->getGuardian() and it wasn't eager loaded it'll be null.

If you guardianRepo->getByStudentId(1) then having getGuardian method and guardians property on the Student entity doesn't make sense anymore including the defining of relationship such as HasMany on it.

Cronix
Cronix
4 months ago (790,130 XP)

Yes, I got that, which is why in my example I eager loaded the relationship on $user being passed to the event outside of the repository.

event(new SomeUserThing($user->load('relationship')));
xxRockOnxx

But I can't do that because repo is only returning POPOs.

class User
{
    private $name;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

That does not have a load method.

https://jeremycurny.com/2016/04/07/php-popo-object/

Cronix
Cronix
4 months ago (790,130 XP)

Repository only return POJOs/POPOs

I missed that.

Ok, I really don't have any suggestion then. I use contracts/interfaces for my repos and use an implementation using eloquent. I don't want to give up the power of the ORM. I really don't see any benefit to using POPO as it seems (from your link) it's basically rewriting what the ORM already does natively, but missing tons of really useful features that you'd have to implement yourself.

xxRockOnxx

It is my first time to use doctrine because I'm a long time Eloquent user.

Turns out, Doctrine creates and use proxies of your model to do the relationship stuff.

So doing $student->getGuardians() will trigger a query behind the scene.

martinbean

@xxRockOnxx In a “true” implementation of the Repository pattern, a repository should only return entities. Entities are just plain classes representing something in your application. It has properties, and doesn’t have methods for accessing things like relationships because the class isn’t a “model” in terms of an ORM.

It’s mixing repositories with ORMs (specifically Eloquent) that I’ve seen so many poor implementation of the Repository pattern. People just don’t understand it. People think creating repositories, you create a class corresponding to an Eloquent model, and proxy method calls (i.e. $repository->save() calls $model->save()). And somehow, if Eloquent is “wrapped” in repository classes, you can swap out your ORM (why would you want to?) without touching your repository classes. It’s not. It’s bulls— to be quite honest.

So, going back to the notion that a repository should only return entities, if you wanted related records then you’d need to call another repository to get the related entities. So if you have a StudentRepository and fetch a Student entity, that entity would have some form of identifier; you’d take that identifier and then call a method on a GuardianRepository.

As you may imagine, this can get unwieldy very quickly, the more entities and relationships you’re dealing with. This is when service classes come in. You’d have a class that performs the methods needed to retrieve and compose the data needed for a particular operation.

xxRockOnxx

@martinbean that's a very insightful answer.

I do actually have service classes that uses repositories as their dependencies.

... if you wanted related records then you’d need to call another repository to get the related entities.

Agree. My repository classes now just contains operations related only to that entity. StudentRepository might have:

  • changeLevel(int $level)
  • chageName(string $name)
  • getById(int $id)
  • searchByName(string $name

But I guess Doctrine did a little lift for you for the relationship thing.

Wasn't really sure what I was doing last time, but after playing with it for a while I managed to understand how everything works.

TL;DR Lessons I learned here are:

  • Repository classes should only contain methods related to that entity/domain only.
  • Use Service classes for composing/assembling a data from different repositories although it is not limited to that. Could also be if you need to access filesystem and store something.

Please sign in or create an account to participate in this conversation.