DEV Community

Ricardo Lüders
Ricardo Lüders

Posted on

Your Laravel application with Repository doesn't make any sense

I’ve seen over the years many developers using the Repository pattern with Laravel, attempting to apply clean architecture concepts to their applications, but often misunderstanding the general concept of using a framework like Laravel.

Before I start and have to dodge some stones that I know you folks might throw my way, let me be clear: this is my opinion as a software engineer who has worked with several languages, frameworks, building software from scratch, and maintaining old legacy codebases. My word is not final, and as always, I believe the most acceptable answer to any software engineering question is: “It depends on what you are willing to trade off to solve your problem.”

So, without further ado, let’s get into the subject.

What is Clean architecture?

Clean Architecture is a software design philosophy that aims to create systems that are easy to maintain, test, and understand. It emphasizes the separation of concerns and the creation of boundaries between different parts of the system to ensure that changes in one part do not adversely affect others. This architecture was popularized by Robert C. Martin (Uncle Bob) and is often used to guide the organization of code in a way that is resilient to changes in business rules or technical requirements.

In short, what Bob proposes is that your application should be split into layers that follow key principles to allow your business logic to be decoupled from any external dependencies, giving them mobility and a single responsibility context. But this isn’t going to be a clean architecture article; I just wanted to put us on the same page about it.

How about Laravel and Clean Architecture?

When it comes to frameworks, we’re talking about tight coupling of dependencies. You likely didn’t pick Laravel to use Doctrine with it; you probably chose it to use Eloquent. The same goes for any piece of framework functionality you may find — you are picking the framework for development speed and convenience.

So, my question is: Why would you add an extra complexity layer to something that you picked to be simple?

Wait, don’t hate me for saying this. You can still adapt or even use clean architecture with Laravel, but the most important aspect of any architectural decision is having a clear understanding of what you are trying to achieve and the pros and cons of those choices.

It’s a hard decision, and you can change your mind in the middle of an implementation. Software should not be immutable, right? I have a whole article about software quality where I raise this flag.

Anyway, getting back to Laravel and clean architecture. One of the most annoying approaches, in my humble opinion, is the use of the Repository pattern with Eloquent, and this is what motivated me to write this article.

Repository Pattern

The Repository pattern is a design pattern that mediates between the domain and data mapping layers, acting as an in-memory collection of domain objects. It aims to decouple the domain and data mapping layers.

So, the Repository definition is a domain entity. Why? Because it interacts with the domain and defines how the infrastructure interacts with the domain, acting as a contract between the Domain and the Infrastructure layer of our application.

The Repository Pattern on Laravel

First, let me show you a bad implementation of the Repository pattern that I often see in Laravel applications and then fix it for you:

// app/Repositories/UserRepositoryInterface.php
<?php

namespace App\Repositories;

interface UserRepositoryInterface
{
  // ...methods definitions
}
Enter fullscreen mode Exit fullscreen mode
// app/Repositories/UserRepository.php
<?php

namespace App\Repositories;

class UserRepository implements UserRepositoryInterface
{
   // ...implementation
}
Enter fullscreen mode Exit fullscreen mode

As you can see in the code above, the “contract” with the domain and the infrastructure isn’t defined inside the domain, but inside the infrastructure itself. Yes, the App namespace is part of the Application Layer, and it contains all the non-domain stuff.

Now, let’s see how I would implement the same thing:

// domain/Repositories/UserRepositoryInterface.php
<?php

namespace Domain\Repositories;

interface UserRepositoryInterface
{
  // ...methods definitions
}
Enter fullscreen mode Exit fullscreen mode
// app/Repositories/UserRepository.php
<?php

namespace App\Repositories;

use Domain\Repositories\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
   // ...implementation
}
Enter fullscreen mode Exit fullscreen mode

Notice that now we have a proper domain layer and an application layer. It is quite simple to get, right?

The Repository Pattern with Eloquent

Now, let us think a bit. Since the Repository Interface is now part of the Domain, it means that we can’t simply inject dependencies from other layers into it. Here’s a bad example to explain it:

// app/Repositories/UserRepositoryInterface.php
<?php

namespace App\Repositories;

use App\Models\User;

interface UserRepositoryInterface
{
  public function find(int $id): User|null;
  // ...other methods
}
Enter fullscreen mode Exit fullscreen mode

Not clear yet? Let me move it to the domain layer:

// domain/Repositories/UserRepositoryInterface.php
<?php

namespace Domain\Repositories;

use App\Models\User;

interface UserRepositoryInterface
{
  public function find(int $id): User|null;
  // ...other methods
}
Enter fullscreen mode Exit fullscreen mode

See the problem? We’re injecting an Eloquent model that is part of the infrastructure layer into our domain. This tightly couples our domain to Eloquent, which defeats the purpose of the Repository pattern.

But how do we solve it? Well, it is not as hard as you might think. You probably already heard about Entities, right? So, let’s fix it:

// domain/Repositories/UserRepositoryInterface.php
<?php

namespace Domain\Repositories;

use Domain\Entities\User;

interface UserRepositoryInterface
{
  public function find(int $id): User|null;
  // ...other methods
}
Enter fullscreen mode Exit fullscreen mode
// domain/Entities/User.php
<?php

namespace Domain\Entities;

class User
{
  public function __construct(
    public int $id;
    public string $name;
    public string $email;
  ) {}
  // ...other properties and methods
}
Enter fullscreen mode Exit fullscreen mode

Our domain layer is now clean from external dependencies, and if we want to move the whole domain to another framework, we can do it.

Now, how do we implement our Repository in our Laravel application? Let me show you:

// app/Repositories/EloquentUserRepository.php
<?php

namespace App\Repositories;

use Domain\Repositories\UserRepositoryInterface;
use Domain\Entities\User;
use App\Models\User as EloquentUser;

class EloquentUserRepository implements UserRepositoryInterface
{
    public function find(int $id): ?User {
        $eloquentUser = EloquentUser::find($id);
        return $eloquentUser ? $this->toDomain($eloquentUser): null;
    }

    private function toDomain(EloquentUser $eloquentUser): User
    {
        return new User(
          $eloquentUser->id, 
          $eloquentUser->name, 
          $eloquentUser->email
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have a proper implementation of the Repository pattern in Laravel using Eloquent. You can create any other Repository implementation, such as PDOUserRepository.php, DoctrineUserRepository.php, and many others without injecting any dependency into your Domain layer.

Do You Really Need to Use Repository?

Back to what I said in the subject of this article, I’ll complement that, in my humble opinion, using the Repository Pattern with Laravel is just overengineering, and you need a really good reason to do it.

You are adding an extra layer of complexity to your application that you may not need at all, or even worse, it will not guarantee that your business logic is actually isolated into the Domain layer.

What are you trying to achieve here? Think about it. You might end up missing out on many nice functionalities from Laravel or at least making their usage overly complicated.

Are you not sure if Laravel is the best framework for your application and you might move your application to Symfony in the future? Sure, go for the Domain and the Repository. Do you need to replace your SQL database with a NoSQL database later? Maybe it is a good justification. But do you really need it, or is it just charm?

One common argument is that the Repository helps to put some business logic into a separate layer. This normally gives me the creeps because there are better approaches for splitting your logic. Repositories are just for acting as a middle layer to connect the data layer and domain, nothing else. If you want to split your business logic, you should make use of something else — but this is another subject.

Conclusion

In conclusion, while the Repository pattern and clean architecture principles offer significant benefits in terms of maintainability and separation of concerns, their application in a Laravel context often introduces unnecessary complexity. Laravel is designed for simplicity and rapid development, and adding layers of abstraction can complicate this process.

Before implementing the Repository pattern, it is essential to evaluate your project’s specific needs. If decoupling your domain logic from Laravel’s infrastructure is a genuine necessity due to future migrations or a need for flexibility, then the complexity might be warranted. However, for many Laravel applications, leveraging Eloquent directly aligns better with the framework’s strengths and keeps the codebase simpler and more maintainable.

Ultimately, the decision should be driven by a clear understanding of your project’s requirements, balancing the trade-offs involved. Overengineering can lead to more problems than it solves, so aim to keep your solutions as straightforward as possible while still achieving your design goals. The primary objective of any architecture is to solve problems, not to create new ones.

Top comments (7)

Collapse
 
xwero profile image
david duymelinck

I agree most people, including myself, don't use the repository pattern textbook style.

Reasons why I use it:

  • keeping controllers more readable
  • don't write Flight::where('number', $number)->first() multiple times and then having to rewrite it in all those places if specs change
  • use it as database/api/filesystem wrapper
  • keeping multiple projects code consistent.

Not all the reasons are in service to create the best code. But code good enough will do most of the time.

The complexity the pattern adds isn't a big threshold. And the benefits are reaped fast.

Collapse
 
rluders profile image
Ricardo Lüders • Edited

I get where you're coming from, and you’ve got some solid reasons. Here’s my take:

Keeping controllers more readable: Totally agree. Readability is key. But you can also achieve this with Query Scopes, model methods, or even a Service Layer without going all-in on the repository pattern (in this case you can even include some business logic here - but I'm not a big fan of service layer, 'cause it can become a hell-ish class).

  • Avoiding repeated queries: Yep, no one wants to write Flight::where('number', $number)->first() all over the place. Encapsulating queries in model methods or traits can keep things DRY (Don't Repeat Yourself).

  • Database/API/filesystem wrapper: Repositories do this well, but make sure they’re done right to keep your domain logic separate from infrastructure stuff.

  • Consistency across projects: Consistency is important, especially in teams. If your team is on board with using repositories and it works well, go for it. Just ensure everyone understands the why and how.

The complexity of the repository pattern isn’t always a dealbreaker if it genuinely makes your project more maintainable and clear. However, avoid over-engineering. If you’re getting more benefits than headaches, it’s a good call.

For those not needing the full repository setup, Laravel offers tools like Query Scopes, model methods, and traits that can get you similar results without the extra layer. It’s all about balancing simplicity and maintainability based on what you need.

Ultimately, there’s no one-size-fits-all. If the repository pattern fits your projects and team, awesome. Just make sure it adds value and doesn’t bring unnecessary complexity.

Collapse
 
cnastasi profile image
Christian Nastasi

Sorry but Eloquent use the Active Record pattern, not the Repository.

And no, it's not a waste of time separate the business logic and the framework.

Some projects last years, frameworks changes often, twice a year at least. Keep update the last framework version within your project could not be silly if you didn't apply some abstraction and having your business logic depending on the framework.

The trick Is invert the dependency (dependency inversion principle)

Collapse
 
rluders profile image
Ricardo Lüders

First, @kwnaidoo, just to clarify, Eloquent actually uses the Active Record pattern, not the Repository pattern.

And @cnastasi , I get where you're coming from, but I don't think it's a waste of time to separate business logic from the framework. Think about it—some projects last for years, and frameworks can change pretty often, sometimes even twice a year. Keeping everything updated with the latest framework versions can be a real headache if you haven't applied some level of abstraction.

Also, the repository pattern is meant to keep your database layer decoupled, not to store business logic. Business logic is typically decoupled using UseCases or Services. If you want to use the repository pattern to achieve dependency injection, that makes total sense. My approach in the article isn't to say "don't use the repository pattern," but rather "use it correctly and only when you really need it."

When your business logic is tightly coupled with the framework, it can become a big problem, but that also depends on the size and nature of the project. There’s no silver bullet—each project should be carefully considered based on its own needs and system design. Some projects might benefit from being tightly coupled to a framework, while others might gain more from being decoupled. It all depends.

The key here is to invert the dependency (Dependency Inversion Principle). By doing this, you keep your business logic decoupled from the framework, making maintenance and updates much more manageable in the long run.

Just my two cents!

Thread Thread
 
cnastasi profile image
Christian Nastasi

Basically, you confirmed my point :)

But I totally agree with you that "There are no silver bullets." Everything depends on what you need and how long your product should last.

Collapse
 
sreno77 profile image
Scott Reno

I don't think that the repository pattern is needed in Laravel. Just follow the Laravel standards and you can keep your code base "clean" with making good decisions.

Collapse
 
rluders profile image
Ricardo Lüders

Yes, it is a design choice. You need to balance the pros and cons and use only if you really need, and use it correctly. This is my point. In short terms: do not overengeneering just to look fancy.