-
Notifications
You must be signed in to change notification settings - Fork 30
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
Add TransactionListener interface #71
Conversation
@@ -187,9 +228,16 @@ object DbApi { | |||
connection: java.sql.Connection, | |||
config: Config, | |||
dialect: DialectConfig, | |||
autoCommit: Boolean, | |||
rollBack0: () => Unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed the rollBack0
argument since it was only used to handle the difference between autoCommit
true/false behavior.
With the new interface, client code can now hook into the rollback process if needed.
scalasql/core/src/DbApi.scala
Outdated
) extends DbApi.Txn { | ||
val listeners = collection.mutable.ArrayDeque.empty[TransactionListener] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've considered making listeners
thread-safe, but the safepoints
member isn't so it seems more consistent this way. Client code that requires thread-safety bears the responsibility of synchronizing around mutations to listeners (and safepoints, as before)
@aboisvert could you write a bit about your use case in the PR description? that will help contextualize what this feature is meant for |
@lihaoyi I updated the PR description with more detail on the use-case(s). I also put the PR in draft state since I intend to add/change a few things. I will notify you when this is ready to review + merge. |
This is now ready for review and feedback. I'm still doing some integration in our app to see if it fulfills all our needs, along with some real-world testing. No rush on merging. I wanted to get it out earlier for feedback. |
Perhaps the biggest question for me is whether this mutable Have you considered other APIs to inject these hooks into the transaction lifecycle? If so, why did you pick this one? |
Mutation isn't great but I think it's the most user-friendly approach given the current API design.
I think there's a spectrum of possible solutions, in broad strokes:
a) Only allow passing in one or more listeners at DbClient/Connection-creation time . For users, this would avoid having to wrap the API and still provide basic functionality. The trade-off is that it's not very flexible / user-friendly. I believe it's pretty common that people want to add post-commit work, such as dispatching events/notifications after the transaction commits, and most often it is dependent on what happens during the transaction. If users can't add post-commit work dynamically, they'll end up re-creating the proposed API on their own. b) Some variation of subclassing (as you mentioned above). This is still somewhat a "roll-up your sleeves and do the work" option. Except that it's more fragile IMO, as it binds user's code more tightly into scalasql's class hierarchy. A solution based on composition rather than inheritance seems more appealing from a forward-compatibility standpoint, so I'd rate this as strictly worse than 2a.
Are there other options you think we should explore? I'm open to ideas. Also, if you're not ready to decide on this and prefer to wait, that's fine too. In such case, we'll wrap the transaction API, move on, and probably not look back. |
Actually after double-checking, I just realized JDBC connections are not thread-safe -- although some drivers (e.g., PostgreSQL) do enforce thread safety; so the current savepoint and the proposed listener APIs are technically correct, although they may still benefit from some added safety. (I misremembered since I also used XA transactions before with a JTA transaction manager where multiple connections can participate in the same transaction, albeit they still shouldn't be shared across threads. Anyway, digressing...) |
Overall I think your reasoning makes sense. For
Otherwise I think it looks reasonable to me |
Sounds good. I'll update the PR with a non-mutable version of |
Change implemented in 375e8a3 I decided against putting Instead, I only added |
@aboisvert looks good to me, if you're done with this PR I'll merge it and cut a numbered release |
Yes, all done. Ready to merge. |
Pushed a tag for 0.1.16, should become available in an hour or two once it makes it through CI and publishing |
Add TransactionListener interface to support notification and conditional action based on {before, after} {commit, rollback} events.
Use-Case
My present need is to dispatch events conditional on the commit of the current transaction. Using a post-commit hook allows for sending a message (e.g. on a AWS SQS queue) only after the transaction commits. This prevents race conditions where the message is picked up for processing before changes have been effected in the database and are visible to other parties. (Concretely, sending a "User Created" event only after the user record has been persisted.)
Of course this is possible without post-commit hooks, however hooks provide a modular abstraction that decouples the code (e.g. send message) from the specific commit point. Transactional event listeners are a common pattern, and can be seen in other libraries such as the Spring Framework (see https://docs.spring.io/spring-framework/reference/data-access/transaction/event.html)
Other Use-Cases
Hooks provide modularity to support decoupling in several other cases.
Before-Commit Hook
After Commit Hook
Before Rollback Hook
After Rollback Hook