odan / slim4-tutorial

Slim 4 Tutorial - Source Code
98 stars 25 forks source link

Inheritance for Repositories #37

Closed hochleitner closed 3 years ago

hochleitner commented 3 years ago

First off, thanks for the excellent slim4-skeleton as well as your e-book. It's been a tremendous help in building a RESTful API for a research project. There's one thing though that I didn't find covered or that I maybe just misunderstood in the whole ADR architecture.

We have multiple Domains: User (very similar to the skeleton), Task, Session and a few more. There are of course the associated actions and also repositories to it. What we soon discovered is that down in the repositories, many things become very similar and start to violate the DRY principle. Meaning: a UserCreateAction has to perform an insert of a new user in its UserRepository the same way as a TaskCreateAction will insert a new task in the TaskRepository. This seems fine at first to me because the domains are different but the DB queries with the repositories are pretty similar. insertUser(UserData $user) does the same as insertTask(TaskData $task) in terms of the underlying query.

How do you suggest handling this? Is it wise to create, say an AbstractRepository class, that contains just an insert(AbstractData $data) method and UserRepository, as well as TaskRepository, will then just call this to perform the actual DB operation? This in turn would then also need an AbstactData class where all the specified domain data classes inherit from.

Does this make sense, or this something that is absolutely not desirable in an ADR approach? If not, how would you approach this issue?

odan commented 3 years ago

Hi @hochleitner Thank you for your feedback.

...maybe just misunderstood in the whole ADR architecture.

The book covers ADR and a simplified Clean Architecture. ADR just says, that the Action delegates all the non HTTP-specific tasks to the Domain layer and the Responder builds the Response. ADR itself does not define how to structure your domain layer.

...many things become very similar and start to violate the DRY principle

I have heard this question many times in the context of repositories that use the DataMapper pattern.

There is no DRY violation because you always have a different bounded context, different interfaces, different queries, different tables, and different columns and different data to insert / update. Every use case is different. Therefore, I do not see any problem here that could be solved.

How do you suggest handling this? Is it wise to create, say an AbstractRepository

The Repository pattern is a well-documented way of working with a data source. In my example I use persistence-oriented Repositories because I don't need the Unit of Work pattern.

Today I would never use and recommend inheritance anymore. That's why I use final by default. There is also no need to reinvent the ORM wheel again ;-)

When you still need a more generic solution, you may try the collection-oriented Repository pattern.

hochleitner commented 3 years ago

Thanks for the clarification. I'll definitely have to read up more on my design patterns.

About inheritance: Is this a personal choice or why wouldn't you recommend it in these cases? When looking at slimphp\Slim-Skeleton I can see that they're using an abstract Action class. Or would you say that it's different for Actions compared to Repositories because of the underlying pattern?

odan commented 3 years ago

Inheritance is "bad" from a theoretical and practical perspective.

To build reusable classes, I prefer Composition over Inheritance because of the Dependency inversion principle from SOLID.

In practice, I have found that inheritance is increasingly difficult to maintain. A tiny change in a base class can break a lot of code. Huge class hierarchies are also hard to maintain and refactor.

hochleitner commented 3 years ago

Thank you, that helps a lot with some of the upcoming decisions.

Also, thanks again for the excellent skeleton and the ebook!