-
-
Notifications
You must be signed in to change notification settings - Fork 21
stateful event sourced workflow with multiple aggregates #485
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
base: main
Are you sure you want to change the base?
stateful event sourced workflow with multiple aggregates #485
Conversation
unixslayer
commented
Jun 8, 2025
- I have read and agree to the contribution terms outlined in CONTRIBUTING.
@dgafka this PR is a follow-up to #481 Scenario here is to build synchronous, stateful workflow with multiple aggregates. Apparently there is an issue with mapping aggregate id when workflow jumps from one aggregate to another as identifier of initial aggregate remains in use. The same happens, when This issue was already solved with #474 although, flow is based on event handler in that one. Anyway, I wonder if this would really be a use case. As models from example can be modified independently from each other. Therefore, so they will probably be in their own BCs so connecting them in synchronous workflow would lead to unexpected behavior. I'd rather don't connect them in the same workflow as we most likely have to deal with eventual consistency here. |
I do think valid use case to connect multiple aggregates within synchronous workflow. In general this can be used for adding extra informations to the Message, to make the decision in last step for example. In general Command or Event Handler can be joined as part of the workflow, however the intention behind them is rather higher level execution, which may begin the flow. So the cavecat here is that we miss one piece to make it actually fully useable, it's making ability to put #[InternalHandler] on the Aggregate and Saga. This way we wouldn't be really exposing an Aggregate method to the public (via Bus), but it would be kept as part of the Workflow. Anyways I do think combing things together via input/output channels is something not really explored much in the area of PHP. Even that Ecotone had under the hood from the beginning, on the surface userland level - it's still relativealy new, as there was no tooling like that before. That being said, I do think we will find more and more use cases for building workflows like this and combining Aggregates as part of the process. I just matter of exploration and easy to use higher level API to achieve that (e.g. missing InternalHandler on Aggregate). |
Hi! I’d like to suggest a feature that would greatly improve developer experience and workflow flexibility. Currently, the outputChannelName option for command or event handlers only supports returning a single message. However, there are many real-world cases where a handler naturally results in multiple follow-up commands or events. For example:
In these scenarios, being able to return an array or iterable from a handler — and having Ecotone automatically route each item to the specified OutputChannel — would simplify the architecture and eliminate boilerplate. |
@dgafka looking at the previous implementation I think that building stateful workflow over ES aggregate was never fully supported. We are one step closer now to cover more complex scenarios with reliable solution. I think that Ecotone would need to track connections in workflow which goes through multiple different aggregates/sagas as those will require additional information added to the message as different aggregate is being loaded/called. I'm a little bit concerned exposing each step of a workflow right now however, knowing this limitation and comparing it with the benefits of splitting complex business process I've decided to go with it for now. Using @lifinsky some time ago as I was working on aggregate flow I came up with an option for aggregate to return described result object that would be handled by Ecotone with decision, e.g. class Aggregate
{
// ...
#[CommandHandler]
public function doStuff(): DoStuffResult
{
// ....
return new DoStuffResult(...);
}
}
#[Result]
class DoStuffResult
{
public function __construct(
#[RecordedEvents] public array $events,
#[ResultedAggregate] public AnotherAggregate $resultedAggregateA,
#[ResultedAggregate] public AnotherAggregate $resultedAggregateB,
#[FollowUpCommand] public DoSomethingElse $followUpCommandA,
#[FollowUpCommand] public DoSomethingElse $followUpCommandB,
) {}
} WDYT? |
Although, that would make |
I like the idea of returning an iterator as output for an input channel better. |