DDSameera's avatar

What's Your Opinion on the Service + Repository Pattern?

Hello I don't want to make any complexity of laravel controller class. so i m planning to create Service classes with Repository pattern. Anyway, I know façade architectural concept in laravel. which one is best ?

This project is medium E-learning management system.

I read this article https://dev.to/asperbrothers/laravel-repository-pattern-how-to-use-why-it-matters-1g9d

I m looking forward your genius recommendations

0 likes
12 replies
martinbean's avatar
Level 80

@ddsameera The Repository pattern is by far the worst offender I see in Laravel applications. It’s never implemented properly, people use it to avoid using Eloquent in controllers but guess what? They just try and re-implement Eloquent models in repositories any way. But Eloquent is a massive library, so these repositories are just full of repetitive methods that never live up to the usefulness of Eloquent itself.

For example, developer wants to fetch posts in their application, so they create a PostRepository with an all method. Cool. But now they realise they need paginated posts, so they add a getPaginatedList method. But then they realise the admin panel should list all posts, whereas the front-end should only list published posts, so end up with a getPaginatedList method and a getPublishedPaginatedList. And the repository then just gets stuffed with more and more edge cases as they crop up: the need to filter, the need to sort, eager-load relations and counts (say number of comments on posts), and so on.

So yeah, avoid the Repository pattern. They just introduce more problems than they apparently solve.

Personally, I just use Eloquent in my controllers. It just works. I’ve worked on dozens and dozens of Laravel projects for around 8 years now, and it’s worked fine in projects of all sizes, from small sites to large ones. If you find there’s logic you need to use in multiple places then by all means extract that logic to a dedicated service class or action class.

However, it is worth to think what if the client proposes to change the data structure and instead of in MySQL/Postgresql from now on we are going to keep the data somewhere else, in the data engine which is not supported by Eloquent? When we write such a code, such a change may turn out to be very difficult to implement, or even impossible!

This is the most annoying straw man argument ever for repositories and boils my piss. “But what if we change our database?!” In nearly 15 years of programming, not once have I worked on a project where as a team we’ve decided, “You know what? Let’s change our database.” And not once have I worked on a Laravel project and gone, “You know what? Let’s use a database that isn’t supported by Eloquent.”

If you’re using Laravel, then there’s 99.9% change you’re going to be using Eloquent and a supported database.

17 likes
DDSameera's avatar

@martinbean Its amazing comprehensive explanation . thanks a lot. Now i m planning to use Service class . Hope that is fine for me. i m not heard about the action class.

Also i noticed there is no php artisan command to create service class + binding

jeroensijbom's avatar

@martinbean Your explanation makes sense. Im wondering what you think of the use of query scopes or other methods on the model that involve eloquent queries. When your applications becomes bigger and more complex. You tend to reach for the model to add a lot these but it makes the model too big sometimes. Where would you extract this to?

martinbean's avatar

@jeroensijbom I don’t have a problem with query scopes. I’d rather have a number of query scopes I can use to compose the query I want, rather than some long, convoluted repository method name that is only useful in one scenario.

Compare:

$videos = Video::query()->published()->transcoded()->paginate();

And:

$videos = Video::query()->published()->transcoded()->take(10)->get();

Versus:

$videos = $repository->getPublishedTranscodedPaginatedList();

And:

$videos = $repository->getLatestPublishedTranscodedVideos(take: 10);

Those repository methods are just going to have lots of duplication and wholly inflexible. What if I want to add an orderBy clause? That means I’m going to have to create yet another repository method to handle that scenario:

$videos = $repository->getLatestPublishedTranscodedVideosOrderByRecordingDate(take: 10);

Whereas with Eloquent, I can just go:

$videos = Video::query()->published()->transcoded()->orderBy('recording_date')->take(10)->get();
4 likes
johnmichael's avatar

@martinbean I'm working at a company that uses this pattern and I'm so annoyed with how much work I have to do for simple things. Boilerplate code everywhere.

1 like
tommypria's avatar

@martinbean what a make sense explanation. Currently, I working on my project and wondering what kind of laravel design pattern I should use. I found that there is a repository pattern and I want to implement it. But after reading your explanation, I came to conclusion that better to avoid it and use service pattern instead.

adityadees's avatar

@martinbean Ai'm agree if that's only simple query like ::find(x) , in sometimes and some reasons it's waste a time if we do repository for that, but Just thinking if you have complex code like this example $videos = Video::query()->published()->transcoded()->orderBy('recording_date')->take(10)->get(); and you use that query on many controller, then, you need to add somecode or update, so instead of just change the one, you need change all of that code everywhere right? that's was the issues, yes you can simply just find all then do replce all, but how if you missing some changes?

martinbean's avatar

@adityadees What’s the alternative? A method in a VideoRepository called getLatestPublishedAndTranscodedOrderedByRecordedDate?

Repositories are classes that should be used with criteria in order to retrieve objects matching that criteria, i.e.

$videos = $videos->matching(
    PublishedCriteria::class,
    TranscodedCriteria::class,
);

But then even if you did this, you’d still have to supply the criteria each and every time you wanted that subset of videos.

krisi_gjika's avatar

@adityadees depending on the complexity you can combine scopes in "child" scopes, ex: Video::query()->available()->orderBy('recording_date')->take(10)->get() where available would just chain the parent scopes you want, but I would say do this only after you see lots of duplication. Also if your models get too big you can always make your own VideoEloquentBuilder to extract your scopes, or move them to a VideoScopes trait

Please or to participate in this conversation.