-
Notifications
You must be signed in to change notification settings - Fork 125
-
Notifications
You must be signed in to change notification settings - Fork 125
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
Improve module integration by simplifying event listener declaration #80
Comments
We now provide @ApplicationModuleIntegrationListener as shortcut for the combination of @async @transactional @TransactionalEventListener to provide a dedicated annotation for the recommended integration arrangement.
This is now available in the 0.2 snapshots. There's still a naming debate to be had. Pro
Cons
Shall we rename |
I really like the idea to simplify asynchronous event handling. It especially reduces errors introduced by missing out on any of the combined annotations. But why not name it just like that: While this is not shorter, to me it is more intuitive and straight forward, especially when migrating from synchronous event handling. While |
Thanks for the feedback, @sfeldkord! This is probably all because |
Name like |
I like that as an additional option to consider. It points a bit to the fact that |
yep, it sounds too general for a developer, as
If look at this from domain design point of view, if a concrete module treated as a bounded context, this would mean producing of an event - what already done, otherwise such need should be first clarified to avoid overloading the whole concept (in an another issue, I guess).
Assuming a module as a bounded context, then we have crossing of contexts boundaries, what is a classical case of DDD and naming telling this would be obvious for the framework users: PS: I found the Spring Modulith only yesterday evening, it's great initiative but I haven't used it yet, thus my ideas here are only opinionated by architecting and development experience in other frameworks. |
Thanks for chiming in, Peter, especially in such tough times! There are cases in which we find a 1:1 mapping between a module and a Bounded Context, but I'd like to keep the latter out of scope here, as we primarily design the interaction between the modules. Those occasionally mapping to BC relationships is not something I'd want to put in the middle of the terminology for something primarily dealing with modules. I think I'll be going with |
…D however that would reuse the already running committed transaction which is not the correct behavior (spring-projects#80).
The default propagation type of @transactional is Propagation.REQUIRED. That, however would reuse the already running committed transaction which is not the correct behavior. Related ticket: #80.
The default propagation type of @transactional is Propagation.REQUIRED. That, however would reuse the already running committed transaction which is not the correct behavior. Related ticket: #80.
tl;dr
We would like to simplify the event-based module integration and make sure the default transactional setup is correct by introducing a custom
@ModuleIntegrationListener
annotation.Context
Classic Spring applications use bean references and invocations to orchestrate functionality, even if the functionality triggered resides in different application modules. In those cases, the default approach to consistency is a transaction that spans the original business method and all transitive functionality:
We usually recommend replacing these kinds of interactions with letting the
OrderApplicationService
rather publish an event and letting the previously actively invoked components listen to that event:This arrangement elegantly fixes the cyclic dependency between the components, simplifies testability but still keeps the consistency model of the previous approach, with all its pros and cons. However, to further decouple the modules from each other, and to limit the scope of the original business transaction, it might make sense to rather turn the event listeners, into asynchronous, transaction-bound ones.
This declaration will cause the even listener method to be invoked during the cleanup of the original business method's transaction (hence the name
@*Transactional*EventListener
).Problem
One problem with this arrangement is that
@*Transactional*EventListener
might create the impression that the listener itself is transactional, which it – declared like this – is not. Users understanding that will very likely go ahead and tweak the declaration to this:This is quite a buit of low-level demarcation and it's easy to miss a detail about the setup. Developers might not actively consider the asynchronous execution. In that case a transactionally correct setup would actually require the transaction propagation settings to be set to
REQUIRES_NEW
as otherwise, the already running, committed transaction would be reused and the listener running in undefined (likely auto-commit) mode.Solution
To simplify the declaration and prevent users from misconfiguring their transactional arrangement, it makes sense to provide a custom annotation
@ApplicationModuleIntegrationListener
, meta-annotated with the set of annotations shown above.We could also provide verification for a variety of cases:
@AMIL
listening to a non-foreign application module's events (via ArchUnit)REQUIRES_NEW
as propagation (via ArchUnit, at runtime)@AMIL
used in applications that do not have transactions activated at all (runtime)@TransactionalEventListener
registered for an event that's published but while no transaction is runningConsequences / Alternatives
Application code would need to use a Spring Modulith specific annotation in a very fundamental use case. Traditionally, we've tried to avoid that by making the defaults sane and only require Spring Modulith-specific annotations for specialized cases. Also, one would have to inspect the custom annotation for its meta annotations to actually understand what's going on. In other words, we would be introducing an indirection.
The text was updated successfully, but these errors were encountered: