Skip to content

The repository pattern #64

@webdevilopers

Description

@webdevilopers

The initial discussion can be found on twitter:
https://twitter.com/webdevilopers/status/1519600542544281602

My tweet was based on the following thread regarding the "repository pattern" opened by @kainiklas :
https://twitter.com/kniklas/status/1519270161429286914

I am a domain modeller. I solve business problems. Me and my model do not (want) to know what a database is. Maybe a different layer should take care of persistence. Not me!

The main thought behind this quote focused on the active record pattern. It allows an object to "save itself" and have awareness of other objects of the same class.
I first came in contact with active record many years ago when I started with Ruby on Rails. Those days I found the approach very convenient. Everything in one object, a graph with all related objects and even custom SQL.

After some years I discovered Doctrine ORM which introduced the repository pattern. Our object had annotations that showed the structure of the database and still you got the object graph with all relations. But the custom SQL was gone. Instead you got a Query Builder in your repository to add conditions for instance.

Projects grow - objects graphs grow. The N+1 issue occurs - more database memory and lazy loading is introduced. But in order to handle business logic you still need the data for instance for calculations.
Instead of using the related objects inside the object graph we moved calculations to repository methods with custom SQL.

And again things seemed to work fine.

But as more and more features were required it was harder to maintain the business logic of the main objects (Entites).
Those days I discovered Domain-Driven Design. And it solved a lot of problems!
Instead of an object graph holding all relations via lazy loading we created separated Entities and used IDs only for references.
The Entities were completely decoupled from the database. Event the inside class mapping went to an XML file.
The focus was now on the business logic of the Entity which was now an POPO - a "plain old PHP object".
A database is just an implementation detail inside the infrastructure layer. Thanks to - among other things - repositories.
This would not have been possible with the active record pattern.

But at that point of the lifetime of a project it was required! - if you are not at that point, you maybe don't NEED it.

A different point was reusability. As @kainiklas states:

15 years ago I wrote SQL statements and put them into methods so that I could reuse them whenever I need and write the SQL only once. You would call that pattern repo nowadays.

With eloquent I don’t need that approach in most cases.

Some people like to reuse code e.g. short snippets for SQL conditions - independent of raw SQL or query builder parts.
In Laravel I think these extra classes are called "Scopes". I guess they work similar to traits.

Personally I'm a not a fan of DRY if business logic e.g. "WHERE customer_status = foo" is used by multiple contexts. If one changes this condition it actually changes multiple usages. At least without tests I would not recommend this.

Anyways...

There is a pattern know as the "Specification Pattern" that tries to solve this. The SQL or query builder part is in a centralized class. But the business logic is made explicit by naming the class for instance "CustomerStatusSpecification".

A final thought on repositories:
A repository can extend a base class of the ORM e.g. EntityRepository in Doctrine ORM. By extending you gain default methods e.g. "findBy", "findAll" etc.. Personally I prefer to only implement methods that I really need. Instead of opening access to multiple ORM specific methods that could be mis-used by another developer or which could one disappear because you switch the database or ORM.
At least I would always write an Interface that ensures the methods required. Instead of re-using the "findby" method with magic parameters I would prefer "findById", "findByTenantId" etc..

Possibly related:

Maybe @akomm, @deeky666, @ScreamingDev and @J7mbo would like to add their thoughts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions