Beware Has Many Relationships

There is an inherient pitfall lurking in each hasMany/belongsToMany relationship. This goes beyond the famous n+1 problem. Allow me to elaborate.

Image you built a blogging platform using laravel and people can comment on each post. We might have a Post model like

class Post extends Eloquent
{
    public function comments()
    {
        return $this->hasMany('Comment');
    }
}

Later you might call

@foreach ($post->comments as $comment)
    <div>{{ $comment->text }} by {{ $comment->user_name }}</div>
@endforeach

But what happens after a post goes viral? Maybe it gets a million comments on it? This page takes forever to load and the response payload is much larger. You could remedy this by doing something like

@foreach ($post->comments()->paginate(15) as $comment)
    <div>{{ $comment->text }} by {{ $comment->user_name }}</div>
@endforeach

But this hides the other 999,985 comments. What typically happens here is that you break this into nested resources and some front end magic code. Maybe some Vue.js, React, Svelte or whatever favor j-soup you’re into. You’ll likely end up making an ajax call to dynamically fetch more comments as the user scrolls down on the page.

// user scrolls down
axios.get(`/posts/${postId}/comments`, {++page})

So this kind of scaling problem is not uncommon. I’ve worked on many Laravel applications, some large, some small. It’s been my experience that hasMany relationships end up scaling horribly, needing to be filtered or broken down into paginated lists anyway.

So the question I ask is, why store relationships in your model? Why not just form it in a controller?

// no need to define a hasMany relationship
class PostCommentsController extends Controller {
    public function index(Request $request, $postId) {
        return Comment::where('post_id', $postId)->paginate();
    }
}

This isn’t strictly a Laravel/Eloquent issue. As I work with data mappers and entities I’ve seen this same issue come knocking like an old fr-enemy. Beware ye hasMany relationships and consider alternatives like nested resource controllers. These are clean, effecient and simple to implement.

Should we abandon all hasMany relationships? No. Only when they don’t scale. Take for example, where we model real-world invoices and give each invoice many line items. This should work well because we are only going to find a few dozen line items on a single invoice at worst case.

What are your thoughts?

post by K.D. on 05/06/2020