-
Notifications
You must be signed in to change notification settings - Fork 15
Repositories and table gateways #6
Comments
I hardly use ZendDB, mostly Doctrine. Doctrine is using only the repository for custom SQL. So in that case the repository knows exactly what the data source is. What I'm missing is a service class. That class shouldn't know about the data source of the repository. But just pass the Album object to the repository. Preferably use a AlbumRepositoryInterface and you can easily swap between ZendDbAlbumRepository, DoctrineAlbumRepository or ExternalApiAlbumRepository. |
One of the good read is http://shawnmc.cool/the-repository-pattern . I think the class works as expected. One thing missing is only the interface . So you can have doctrine-dbal implementation or any implementation. |
awesome. Getting rid of this A full DDD approach is of course not needed for the album tutorial, so skipping the application service would be fine, but hiding database stuff behind an interface should be part of every application. |
Thanks for the feedback so far. Then this implementation might be a good way to continue. |
Also in line what I was thinking. I will extract |
BTW, which one should I use? interface AlbumRepositoryInterface or interface AlbumRepository I have a mixed feeling about this - for my projects I tend to add "Interface" suffix, but I'm not sure which is better. |
I personally would always prefer
with
In the second example you know it is an interface at the first glance. In the first example you need to know or you need to look it up. |
I lot of projects I have seen are using |
Using the interface suffix makes it easier for beginners, but I like the approach not to use the interface suffix. +1 for |
Done - PR created. |
I'd also like to make repository more strict:
This will simplify all action classes using this methods. Thoughts? |
@mtymek +1 for |
I understand this part. There is an error and thus an exception when it can't be saved. If there is no exception, it worked.
Why should this throw an exception? Why not just return Just saying this in case you have intentions in adding a DoctrineAlbumRepository. If you do a find($id) it returns null, no exception. It's been a long time since I used ZendDb, but what I remember is that it didn't return an exception if it didn't find an object. I might be wrong though. |
I agree with @xtreamwayz here. I have recently noticed some article that talks about throwing exception is not good. ( Don't remember the link ) . It seems to me as a personal preference though. |
To be honest, I am still not convinced about the interface name without the So, please keep your focus on the beginners. |
As per Ralf's request, I changed |
My understanding is that trying to fetch non-existent entry is not expected in application - it should never happen unless user is typing something weird in the URL. Unexpected situation = exception. |
Thanks for the change of the interface name. Could you provide an example how you would handle the Not Found Exception in the Error handler? |
Here's how it could work in Expressive: namespace App;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Domain404Middleware
{
public function __invoke($err, $request, $response, callable $next)
{
if (!$err instanceof EntityNotFoundException) {
// different type of error - not our concern,
// pass it to the next error handler
return $next($request, $response, $err);
}
// Trick: passing empty response with no error information will
// force FinalHandler to show default 404 page.
// Alternatively - you can do something else here, like rendering
// non-default 404 view script.
return $next($request, $response);
}
} You have to register it as an error middleware: return [
'middleware_pipeline' => [
'post_routing' => [
[
'middleware' => Domain404Middleware::class,
'error' => true,
]
]
]; Having this in place means you don't need to worry about 404 inside your action classes - you can focus on what's important. As I wrote, attempting to show non-existent entity is not a normal situation, so IMO handling this case can be driven by exceptions. Having said that, it may be too difficult to understand for the beginners, blurring information in your tutorial. Your call if you want to have it 😄 |
That's using exceptions to implement the logic flow, which is an anti-pattern. Fetching a non-existent entry is expected, particularly when you have a route in the form of I see four options:
$album = $repository->fetchAlbum($identifier);
if ($album->isNotFound()) {
// 404 condition!
}
if (! $repository->hasAlbum($identifier)) {
// 404 condition!
}
$album = $repository->fetchAlbum($identifier); The last two would be trivially easy to accomplish, and still be easy for beginners to understand. No additional middleware would be necessary to accomplish them, either. |
When passing multiple return types , the possible values can be |
I had a bad feeling about throwing an exception if an entity is not found. Good to know that it is even an anti-pattern. Currently we have the second option that @weierophinney suggested. Personally, I would go for the fourth option because I think an entity should never have an invalid state. @mtymek How should we proceed? Should I change the code base to implement the results of this discussion or would you want to amend your PR? |
Anyway @weierophinney what do you think about the repository <-> table gateway connection?
|
@RalfEggert my latest PR doesn't throw exception if |
Indeed, returning null or entity is not perfect, because you have to branch your code. This is, however, most commonly seen solution, anyone will understand it. So I suggest we stick with it in the tutorial. I know this can get off topic, but it's also pretty interesting to exchange ideas. Thanks for sharing your thoughts @weierophinney! Still, let me defend my approach. Other solutions are not perfect either:
As for exceptions - my line of thinking is that if you don't show URLs with invalid entity IDs, then you don't expect your users to request them. If they "follow the rules", they should never be fetching non-existent entity. If they don't follow the rules (they edit the URL manually) - it is an exceptional situation. I know it may be a bit exaggerated explanation, but it works pretty well in real life - code is both readable and bullet-proof. |
@mtymek Ok, I will try to proceed on that. But unfortunately not today.
No, like @weierophinney already said it should be the job of the repository not to send two DB queries to fetch a single object. After on entity is read it can be added to an identity map and read from there for any subsequent fetching for the same id. |
I don't think you can expect every request to be valid. There are also situations where urls used to be valid and users did not mess around and changed id's. Just to name a few 404 situations, but not exceptions:
|
Not sure if I'm convinced, but thanks for sharing your arguments anyway - I will have something to think of when implementing this pattern next time. @RalfEggert - can you merge what we already have? Based on that I can submit PRs for next parts of the tutorial. |
Well, I implemented some changes now. Please note that depending on issue #8 another couple of changes have been implemented. WarningI was very resistant to advice and selfish because I added an additional table gateway class which encapsulates all SQL generating stuff and keeps it out of the repository. I know that most of you had a different opinion on that subject. But I just wanted to demonstrate how I would handle the repository <-> table gateway connection in this case. I think it is much clearer if the repository doesn't contain any SQL logic because it is part of the model layer and not of the infrastructure layer. To cut a long story short, now the repository is part of the model layer and the table gateway is part of the infrastructure layer. If you have really good arguments that the repo should contain SQL logic, please convince me to change it back. |
I prefer to define the repository contract in the model layer |
Sounds reasonable. But it is another step into a real DDD application design which is not the focus of the tutorial. The focus are the beginners who want to learn There might be no need to split SQL from the repository. But I still see no reason not to keep the SQL logic where it belongs to. And it belongs to the table gateway, otherwise there would be no reason to attach the But maybe I am on the wrong trail and I don't know it. |
@RalfEggert I would not say you are on the wrong trail. But we should better define our audience. You brought DDD into play. Let's do some DDD and ask some questions 😄 :
|
So again, in which part is the current model design really, really bad and should be improved? Any other suggestions for improvemen can always be part of the textual part of the tutorial... |
Thanks @RalfEggert for your detailed answer. Much easier now to understand your goal of the tutorial. Let me answer the same questions from my point of view:
Conclusion Having all that said, I would drop |
Hey Ralf, If you want to go for something easy to learn, then do something simpler - my PR was just like that:
Your latest version adds one more step:
@codeliner take into account that this is supposed to be learning material for Expressive, not DDD. While I agree with you that we should show best practices to newcomers, introducing too many layers will only confuse them. We should show good practices (SOLID, decent test coverage...) - they won't distract readers. I have a feeling that nice DDD would require too many things to be explained. |
@mtymek I NEVER said we should use DDD! IMHO the tutorial should use |
First to clarify, when I wrote "No, Second, @codeliner you did bring up the DDD stuff by posting your DDD examples. Yes, you didn't say "use DDD" directly and you made a limitation of not using application services for simplification. But that was at least a bit ambiguous because it could imply to do DDD only without application services. So I guess there was a little bit of misunderstanding here. I have seen a couple of projects which almost failed because unexperienced users started huge projects based on Doctrine. Doctrine can be a beast when it gets complicated and you don't have the know how to handle this. Of course the same can happen when using I guess we might never agree on this. So why not do both? The Zend Framework was always lacking tutorials. I guess it wouldn't harm anyone to write a tutorial with the same database structure and features by using a lot of external dependencies (Doctrine, Plates, PimpleDI, ...). Then we could have one tutorial which uses mostly ZF components and one which is open for all of the nice other components out there. Having two (or even more) tutorials for What about this idea? Who would like to write such a more open tutorial? |
@RalfEggert Yeah, seems like my comments are a bit ambiguous. I'm sorry for that.
I've linked the cargo sample because it implements the design we are discussing here:
I agree. That is the reason why I suggest to only use
We (prooph) can do that next year ;), but only if it becomes part of the official tutorial. We've already open sourced two example applications using zend-expressive together with different infrastructure components. |
@codeliner sorry, I misunderstood you then. |
I think it might be helpful for a better understanding and structure, when I tweek the
I am still not sure how to name the subdir and the interface, though. I thought of Then the What do you think about that? |
@mtymek thx, np. @RalfEggert Sounds very good. Regarding ns and name: |
Done |
I merged the changes from @mtymek now. I like the addition of the repository but I think the way it is implemented is still suboptimal.
From my point of view the repository uses the table gateway or another data source and requests data or sends data to the persistence layer. The repository shouldn't really know how the data source / table gateway is using it. If it is a database, a web service or the file system.
In the current state, we have a mixture of SQL knowledge in both table gateway and repository.
I would prefer both. An
AlbumTable
that extendsTableGateway
and provides the generation of the SQL objects. And anAlbumRepository
that uses theAlbumTable
for persistence by calling its methods and adding logic like the decision for an insert or an update.Thoughts?
The text was updated successfully, but these errors were encountered: