cosmicpython / code

Example application code for the python architecture book
Other
2.14k stars 943 forks source link

[Question] How do you return values (like created ids) in Commands/from the message bus? #50

Open ArcticXWolf opened 2 years ago

ArcticXWolf commented 2 years ago

Hey,

first and foremost: thanks for this awesome book, it is the first time I had to plan an architecture for a bigger project and your book has helped me enormously to create maintainable code.

Question

In Chapter 9 you temporarly show how to return values from the handlers to the entrypoint. You mention that this is still ugly, because it mixes resposibilities (of read and write) and refactor everything according to CQRS in Chapter 12.

However during this you loose the ability ("feature") of returning the batchref in the entrypoint (see here in the code).

I agree that reading and writing are separate responsibilities, but sometimes there is a usecase for returning values even for a command and not a query. Examples:

Possible Solutions

I've thought about two ways to solve this, but why I would choose which approach is more of a gut feeling. Maybe you can help me by providing feedback of why these are good/bad or if there is even a better/dogmatic way.

Just returning values from the handler (like in the ugly workaround section linked above)

This enables you to just use it like already shown in your book. What I dont like about here is that there is no real "interface"/"annotation" of the return value of messagebus.handle. It completely depends on the handler. Furthermore, how should we deal with a chain of commands and events (as one handler/model can create new events). Do we discard them or do we return a list/dict of all return values? Also it just "feels wrong" to return Any.

Leverage the events to return values

As the return values will be mostly stuff like "ObjectCreated(id=1)" or similar, we could just use the available events. Just in the messagebus create a separate copy of all events that are added to self.queue and return this list of events. Then the handle-method just returns the list of thrown events. This solves both problems above: we have an "interface" (List[events.Event]) and also we can return stuff from ALL handlers in a handler chain.

The entrypoint just extracts the events/data it is interested in then.

What do you think about this idea? Or am I wrong and we can also solve this with CQRS? Or some other way completely?

cottet-ju-e3 commented 2 years ago

Same as @ArcticXWolf big thanks for the book I bought last year. I encountered exactly the same issue and choose to return the head of self.queue as the response. I think there could have a better and secured way. I hope this could be answered.

abdulhaq-e commented 1 year ago

Hello @ArcticXWolf and @cottet-ju-e3

While I'm not an expert but the pattern that I've seen to solve this issue is by having the client create the resource identifier. Ideally this identifier would be a GUID. It's definitely mentioned by Udi Dahan here but I think Harry and Bob mentioned it somewhere in the book too.