Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Messenger] Passing stamps to message Handler #31075

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

Closed
enumag opened this issue Apr 11, 2019 · 15 comments Β· Fixed by #45418
Closed

[Messenger] Passing stamps to message Handler #31075

enumag opened this issue Apr 11, 2019 · 15 comments Β· Fixed by #45418

Comments

@enumag
Copy link
Contributor

enumag commented Apr 11, 2019

Description

When working with symfony/messenger I sometimes find myself implementing a message handler where I would need some of the stamps that are attached to the message. Currently the HandleMiddleware only passes the message itself but there is no way for the handler to get the stamps. This is forcing me to rewrite such handlers to middlewares.

Example

I'm trying to use Messenger for CQRS + Event Sourcing. So I add stamps like event id, aggregate type, some data about the user who initiated the action etc. In some cases the CommandBus or EventBus handlers need the data about the user or aggregate.

Solution

In my opinion the handler should receive an array of stamps or possibly better the Envelope class as second argument. What do you think?

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 11, 2019

This has been proposed several times, but right now we always figured out that such info should be inside the message, if they are needed in a handler. Does that make sense? If not, why?

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

It does make some sense but ultimately these things are still sort of metadata and not a direct part of the Command or Event. Moreover they are usually only added to the message later and not right away. Adding such metadata as a Stamp makes more sense then creating a new instance of Command/Event (because those are obviously immutable). I did store them in a $metadata array inside the message until now but using Stamps for such metadata makes much better sense to me. So ultimately I would only end up with having a second layer of stamps inside the message and the message would become a second envelope for the real command or event. When adding a new stamp later it would become difficult to figure out which layer it belongs to.


For example in the regards of Event Sourcing I found out that I very much dislike the concept of CommandHandler classes that load the aggregate identified by the command from EventStore, call a method on that aggregate to generate new events and save those events to EventStore again (such as this example from Prooph). This was exactly the same useless code over and over in all CommandHandlers which also became difficult to test as a result. So I got rid of CommandHandlers completely and have one handler that handles all commands.

Now when refactoring the application to symfony/messenger it would make sense to refactor this command handler to a Middleware and have the aggregates themselves become command handlers. The middleware would load the current aggregate state, add it as a Stamp and send it to the next Middleware (HandleMiddleware) and after the inner middleware is done it would save the events from HandledStamp to EventStore. Does this make sense?

The catch of course is that the handler called by HandleMiddleware would need to have access to the AggregateStateStamp - which is currently impossible.

Note: In my case AggregateState is a value object separate from the class with the Aggregate's logic. This was not the case in many EventSourcing examples but it works really well for me. AggregateState has an apply method for each Event while Aggregate has methods that receive command + AggregateState and yield events.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

The catch of course is that the handler called by HandleMiddleware would need to have access to the AggregateStateStamp - which is currently impossible.

why not transfer stamps from the envelop to its message? Or fill pure message properties with it?

I tend to believe a message handler should remain scoped to a message.

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

why not transfer stamps from the envelop to its message? Or fill pure message properties with it?

I believe I already answered that with this:

Adding such metadata as a Stamp makes more sense then creating a new instance of Command/Event (because those are obviously immutable). I did store them in a $metadata array inside the message until now but using Stamps for such metadata makes much better sense to me. So ultimately I would only end up with having a second layer of stamps inside the message and the message would become a second envelope for the real command or event.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

hm, maybe i misunderstood

but using Stamps for such metadata makes much better sense to me

that's exactly what i proposed; handle the message using e.g. $command->withStamps($envelop->stamps()).

At least i think the user should be responsible for coupling domain code with infrastructure, or it should be possible to opt-out, if we make a move here.

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

that's exactly what i proposed; handle the message using e.g.
$command->withStamps($envelop->stamps()).

Yes but then the $command needs to have a withStamps method, effectively making it something more than a simple command. Which is exactly what I'd like to avoid:

So ultimately I would only end up with having a second layer of stamps inside the message and the message would become a second envelope for the real command or event.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

We could pass the envelop along i guess, maybe using a EnvelopeAwareInterface to op-tin, but im curious what's considered best practice regarding coupling to infrastructure in a message handler.

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

but im curious what's considered best practice regarding coupling to infrastructure in a message handler.

Yeah, I'm interested in that too. I do understand the reasons against passing the envelope and stamps to some extent so I think I'll use my own HandleMiddleware to pass the envelope for now. I do dislike the coupling between messenger and my own code. Maybe I'll come up with something else later. No need to pollute symfony for now.

Thanks for your thoughts.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

i disliked MessageHandlerInterface already :D but it keeps the handler fairly portable (i.e. you can always remove this marker interface without touching other business logic)

when either the message or handler is coupled to a stamp, it makes it much harder to port. Hence i suggested pure message properties / or keep a specific infrastructural middleware.

Of course the porting is very hypothetical, but im not sure it legitimates Symfony to couple handlers by default.

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

Yeah, that makes sense. Thanks for your input. I'll try to think more about how to decouple things on my side. Will reopen this if I figure out something.

@enumag enumag closed this as completed Apr 11, 2019
@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

@ro0NL An idea just occurred to me. After reevaluating my use-cases it seems that I don't actually need different stamps in different handlers. All my cases like that make more sense as middlewares. For handlers I just need to pass the message itself + an application-specific context which will be present in some stamp but not the message itself. So a possible solution would be for the HandleMiddleware to have an optional callback that would receive the envelope and return the handler arguments. That way the handler can receive an additional context but without any symfony specific wrappers like envelopes or stamps. This way it would not be coupled at all.

I think I'll experiment with this next week. Thoughts?

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

i see :) that could be worth exploring ... it's a bit like controller action arguments IIUC.

However i think handle(Known $message) is a very valuable and clear semantic. Introducing more state might cause diversion in implementations.

Would wrapping your message, in another message of yours, be more clean?

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

However i think handle(Known $message) is a very valuable and clear semantic. Introducing more state might cause diversion in implementations.

Since the diversity would be caused by custom, bus-specific callback, I don't think it matters too much.

Would wrapping your message, in another message of yours, be more clean?

From the purely academical point of view, possibly. From practical point of view, definitely not because I'd have to unpack the message in every handler and annotate / assert the objects for IDE to know the classes etc.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 11, 2019

or unpack from a middleware, which brings us to your proposal i guess :)

i agree this creates custom (local) message handlers that follow a different semantic; but by choice πŸ‘

@enumag
Copy link
Contributor Author

enumag commented Apr 11, 2019

or unpack from a middleware, which brings us to your proposal i guess :)

So adding an optional HandlerArgumentsStamp which the HandlerMiddleware would try to use before falling back to simply pass $envelope->getMessage()? Yeah that's an improvement to my proposal. πŸ‘

fabpot added a commit that referenced this issue Aug 14, 2022
This PR was merged into the 6.2 branch.

Discussion
----------

[Messenger] Add HandlerArgumentsStamp

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #31075
| License       | MIT
| Doc PR        | symfony/symfony-docs#17174

As discussed in #31075 sometimes it's desirable for the messenger handler to receive additional argument than just the message itself. I understand the voiced concerns about passing the entire envelope but instead of that we could use the approach from this PR which doesn't add any additional arguments by default but is actually even more powerful since it gives the user full control what should be sent to the handler if desired.

This is just a prototype of course. With #45377 in mind I used a readonly property from PHP 8.1 but of course such details can be easily adjusted.

Let me know if such feature is wanted in Symfony. If yes then I'll add tests and a doc PR.

Commits
-------

d081267 Add HandlerArgumentsStamp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants