Search code examples
laravel-5repository-patternddd-repositories

Laravel Repository pattern and many to many relation


In our new project we decided to use hexagonal architecture. We decided to use repository pattern to gain more data access abstraction. We are using command bus pattern as service layer. In our dashboard page we need a lot of data and because of that we should use 3 level many to many relations (user -> projects -> skills -> review) and also skills should be active(status=1).

The problem rises here, where should i put this?

  1. $userRepository->getDashboardData($userId).

2.$userRepository->getUser($userId)->withProjects()->withActiveSkills()->withReviews();

3.$user = $userRepository->getById(); $projects = $projectRepository->getByUserId($user->id); $skills = $skillRepository->getActiveSkillsByProjectsIds($projectIds);

In this case, I couldn't find the benefits of repository pattern except coding to interface which can be achived with model interfac. I think solution 3 is prefect but it adds a lot of work.


Solution

  • You have to decide (for example) from an object-oriented perspective if a "User" returned is one that has a collection of skills within it. If so, your returned user will already have those objects.

    In the case of using regular objects, try to avoid child entities unless it makes good sense. Like, for example.. The 'User' entity is responsible for ensuring that the child entities play by the business rules. Prefer to use a different repository to select the other types of entities based on whatever other criteria.

    Talking about a "relationship" in this way makes me feel like you're using ActiveRecord because otherwise they'd just be child objects. The "relationship" exists in the relational database. It only creeps into your objects if you're mixing database record / object like with AR.

    In the case of using ActiveRecord objects, you might consider having specific methods on the repository to load the correctly configured member objects. $members->allIncludingSkills() or something perhaps. This is because you have to solve for N+1 when returning multiple entities. Then, you need to use eager-loading for the result set and you don't want to use the same eager loading configuration for every request.. Therefore, you need a way to delineate configurations per request.. One way to do this is to call different methods on the repository for different requests.

    However, for me.. I'd prefer not to have a bunch of objects with just.. infinite reach.. For example.. You can have a $member->posts[0]->author->posts[0]->author->posts[0]->author->posts[0].

    I prefer to keep things as 'flat' as possible.

    $member = $members->withId($id);
    $posts = $posts->writtenBy($member->id);
    

    Or something like that. (just typing off the top of my head).

    Nobody likes tons of nested arrays and ActiveRecord can be abused to the point where its objects are essentially arrays with methods and the potential for infinite nesting. So, while it can be a convenient way to work with data. I would work to prevent abusing relationships as a concept and keep your structures as flat as possible.

    It's not only very possible to code without ORM 'relationship' functionality.. It's often easier.. You can tell that this functionality adds a ton of trouble because of just how many features the ORM has to provide in order to try to mitigate the pain.

    And really, what's the point? It just keeps you from having to use the ID of a specific Member to do the lookup? Maybe it's easier to loop over a ton of different things I guess?

    Repositories are really only particularly useful in the ActiveRecord case if you want to be able to test your code in isolation. Otherwise, you can create scopes and whatnot using Laravel's built-in functionality to prevent the need for redundant (and consequently brittle) query logic everywhere.

    It's also perfectly reasonable to create models that exist SPECIFICALLY for the UI. You can have more than one ActiveRecord model that uses the same database table, for example, that you use just for a specific user-interface use-case. Dashboard for example. If you have a new use-case.. You just create a new model.

    This, to me.. Is core to designing systems. Asking ourselves.. Ok, when we have a new use-case what will we have to do? If the answer is, sure our architecture is such that we just do this and this and we don't really have to mess with the rest.. then great! Otherwise, the answer is probably more like.. I have no idea.. I guess modify everything and hope it works.

    There's many ways to approach this stuff. But, I would propose to avoid using a lot of complex tooling in exchange for simpler approaches / solutions. Repository is a great way to abstract away data persistence to allow for testing in isolation. If you want to test in isolation, use it. But, I'm not sure that I'm sold much on how ORM relationships work with an object model.

    For example, do we have some massive Member object that contains the following?

    • All comments ever left by that member
    • All skills the member has
    • All recommendations that the member has made
    • All friend invites the member has sent
    • All friends that the member has established

    I don't like the idea of these massive objects that are designed to just be containers for absolutely everything. I prefer to break objects into bits that are specifically designed for use-cases.

    But, I'm rambling. In short..

    1. Don't abuse ORM relationship functionality.
    2. It's better to have multiple small objects that are specifically designed for a use-case than a few large ones that do everything.

    Just my 2 cents.