-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[8.x] Implement transactional events via marker interface #1
Conversation
Not sure if the big code example within the PR description could be misleading, as this kind of code isn't included (should be in a Also of what I remember, the majority of people longing for this actually want it for Eloquent native events (i.e. specifically the part not considered to be ported from the library), so we might add some useful arguments for that too. |
"Native" Eloquent events can still benefit of this, similar to the Job example: they would just fire another event from within which is tagged with the interface. |
Note: its for Laravel 8 because it requires laravel#32338 (and wouldn't have it for < 8) |
Hi @mfn, I am really glad to see you're pushing it forward! At first glance, I see the dependency between events and database package will cause the most difficulties on merging this into the core. As it creates dependency between those two separated environments, I suspect Laravel folks will say that it should exist as a standalone package or at least be integrated in Laravel application rather than the core itself... I like the approach of the TransactionalJob and it inspired me to add the possibility to specify transaction-aware custom instructions at runtime in a simple way. 👍 I've just subscribed to this topic, so I'm paying attention to this. Thanks in advance! |
I'm subscribing to the topic as well. |
dcb7dc6
to
8cfaf25
Compare
Based on https://github.com/fntneves/laravel-transactional-events and with permission from it's primary Author, Francisco Neves, this brings a basic form of transactional events to Laravel. Any event "tagged" with the interface `\Illuminate\Contracts\Events\TransactionalEvent` will automatically be (internally) queued before being dispatch only in case the surround database transaction finishes successfully. In case of: - no transaction, the event is immediately fired as usual - transaction rollback, all "tagged" events are discarded
I'm still alive and interested in moving forward 😄
As a next step I plan to go through all issues/ideas in Laravel which every talked about such a feature and will ping people there to get a wider feedback. Before doing that, any other feedback from current subscribers to this issue? Thanks! |
Hi @mfn, Great to see it is ongoing and being discussed. I read your PR introduction and I felt it is in the right way. |
Hey guys! Great job @mfn. A while ago I've faced some more issues with transactions and events. I have similar implementation as @mfn provided. But when you don't need transactional events and you have some listeners that shouldn't be queued and some that should and you using SerialiazesModels it will throw exceptions ModelNotFound(if you creating it) or will return you incorrect data if job ran before transaction finish. So here you have 2 solutions, do not queue this listeners or use delay(you can't be sure that transaction will be finished before delay is ended). All this solutions isn't perfect. So if you have ideas how to implement this behavior will be happy to hear. Hope explained everything good, if you have questions feel free to ask. |
@ilirien I don't fully understand
This PR/approach is about optionally enabling event only being dispatched when the database transaction successfully finishes; as such, the serialization part should work as expected. Maybe you can try explain it again or, if you want, can you create a public repo showing the problem with this PR? Thank you :-) |
Not in all cases we need to delay running event in finished transactions, in some cases we need to delay specific listener to run after transaction finished. My main message was that we need something like TransactionalListener for example. Some of the listeners should be ran immediatly some of them should be pended (for example that should be queued) As well it will be great to have method similar to shouldQueue, because same events could be transactional in one case and not in other. |
Those seem very specific use cases, and you can create a specific event for what you call “delayed listeners”. The purpose of delaying events is to guarantee that the event represents what “did” happen and not what “possibly” happened. That is, if a UserCreated event is raised, then the user was actually created an exists. As such, your listeners can handle that state change consistently. In my opinion, if you want to delay a listener, you should delay the event or even dispatch a delayed job inside a listener. |
For all subscribers, it may be interesting to see laravel#35266 |
The above mentioned feature has made it into Laravel (and via some iterations it may seem to be solid), and docs are there: https://laravel.com/docs/8.x/queues#jobs-and-database-transactions https://divinglaravel.com/better-management-of-database-transactions-in-laravel-8 mentions:
My first impression is that it might obsolete this approach here, but I've not had time to verify. In case anyone else is interested to check this out and compare! |
The Laravel changes might obsolete the implementation details of this PR (e.g. allow the implementation to be simplified), but not the feature itself. As far as I have understood Laravel's new implementation, the only way to delay the dispatch of an Event is to wrap the dispatch itself in a We ended up keeping fntneves/laravel-transactional-events because of this, so I'm still rooting for this PR. :) |
Hey everyone 👋 I've decided to abandon to pursue this change becoming officially into Laravel. The new features they added in the last couple months are not a replacement and don't cover the scope what is possible here: however I've doubts they would introduce a similar-yet-different approach on the whole thing, making effectively two competing strategies in Laravel. As long as replacing the dispatcher works, it's "good enough" for me. Thanks everyone for their thoughts and time! |
This is a PoC for fntneves/laravel-transactional-events#32 ; see below for the "official to be" Laravel PR description I intend to use and at the end, some notes regarding this.
What does it solve
A "classical problem" of systems with database transaction and events is that the latter is often handled void of the context of the current transaction.
This means in case of database transaction being rolled back, events are often already fired and thus their outcome might or might not reflect the state of the transaction anymore.
This PR attempts to provide a solution for that.
The implementation
Only events implementing the new
TransactionalEvent
interface are subject to this handling, the existing event behaviour is not changed (ensuring backwards compatibility).This is based on https://github.com/fntneves/laravel-transactional-events with permission by the author Francisco Neves (see fntneves/laravel-transactional-events#32).
This is a MVP (*) implementation focusing on:
These are slimmed down capabilities provided by the original approach because of doubts if automatic handling of e.g. Eloquent events for transaction is a good idea to start with or the additional complexity of pattern matching to perform on event names (please see the original readme).
Why only Events and not e.g. Jobs?
First, why are "Jobs" also interesting to be handled in a transaction-safe way? Two examples:
=> this PR can help solving this
In such cases, it's not uncommon that these jobs see the wrong / outdated information in the database and thus might process stale data.
There are ways to mitigate this, e.g. not passing model IDs but the whole model (Laravel supports serializing them), but this might not always be possible or desired
=> this PR can help solving this
Implementing this only for the event bus is a "basic building block". It allows to make transactional jobs with a bit of glue code: create a transactional job wrapper, tagged by the interface, and wrap the jobs you want to be transactional:
Add a
\App\Events\TransactionalJob.php
:In the
EventServiceProvider.php
:Add a
\App\Listeners\DispatchJob.php
:and then use in your code like:
(*) minimal viable product
Technical considerations:
illuminate/event
has to depend onilluminate/database
to be able to register the event listeners for the transactionsThis is rather a hefty dependency for this, but I didn't see a way without it.
Maybe future consideration: separate events to listen from individual packages and provide a way to separately depend only on them, much like
illuminate/contracts
worksLinks
Some notes
Just a feeling…
Probably "fine" but some might not be happy about this; it's a hefty dependency but we need it makes the events for the transaction to listen on are in this namespace 😢
fntneves/laravel-transactional-events
library) and it worked (it also has a test suite with >7k tests with many making use of transactional event testing).TODO
I checked the coverage and there are a few lines not covered it seems