Skip to content

Conversation

@unixslayer
Copy link
Member

Why is this change proposed?

Consider following scenario.

  • When product becomes billable (ProductBecameBillable), it should be added to InvoiceOrder identified by Debtor of billable product. InvoiceOrder can be created when this occurs.
  • Debtor of a product can change overtime (ProductDebtorWasChanged). When it happens, following outcome should take place:
    • product should be removed from InvoiceOrde identified by old Debtor
    • product should be added to InvoiceOrder identified by new Debtor. InvoiceOrder of new Debtor may not yet exists, therefore it can be created when this occurs.

Above scenario require aggregate to have multiple factory methods and action channels:

ProductBecameBillable - factory method of InvoiceOrder
ProductDebtorWasChanged - factory method + two action channels, one to remove and second to add product to InvoiceOrder

Proper identifier mapping is defined in EventHandler definition as shown in packages/Ecotone/tests/Modelling/Fixture/MultipleMessageHandlers/InvoiceOrder.php

However, current implementation require that Message Handlers on Aggregate and Saga can be used either for single factory method and single action method together, or for multiple actions methods and forbid that implementation.

Description of Changes

tbd

Pull Request Contribution Terms

  • I have read and agree to the contribution terms outlined in CONTRIBUTING.

@unixslayer unixslayer requested review from dgafka and jlabedo December 5, 2025 17:37
}

#[EventHandler(endpointId: 'createWhenDebtorWasChanged', identifierMapping: ['debtorId' => 'payload.newDebtor'])]
public static function createWhenDebtorWasChanged(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we would go this path, then there should be still guard that there is only one factory:action for given identifier mapping (either payload or headers).
Otherwise it become confusing whatever they do relate to each other or not, and then it's not obvious if we should create instance or redirect to action.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgafka yes, right now Ecotone guards factory:action combination and does not take identifier mapping into consideration. I think adding this into account does not introduce BC as it won't affect cases where there is already one factory:action.


self::assertEquals(['product-1'], $ecotone->getAggregate(InvoiceOrder::class, 'debtor-1')->products());

$ecotone->publishEvent(new ProductDebtorWasChanged(productId: 'product-1', oldDebtor: 'debtor-1', newDebtor: 'debtor-2'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like the design here is that, Product is stating that Debtor was changed, but Debtor itself also hold the list and need to be synchronized (even so it's not source of truth for it, because Product is?)

I wonder if this could be modelled differently?
For example Debtor (debtor-1) would record an event ProductDebtorWasChanged (which would unassign the product), and then there would be single Event Handler on the Debtor, that would be called based on newDebtor id. This way Product itself is not really holding Debtor information and that info is not in need to be synced there.

Do you have other scenarios where we would like to trigger same Aggregate but more than one instance, based on single Event?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgafka Example here is simplified.

In real example, Products Debtor is "calculated" based on changes in Product itself so it is changed for the Product, not for the Debtor. Later we need to assign Product in correct Invoice. That logic is not 100% applicable for Product nor Debtor so we want to introduce InvoiceOrder as something in the middle that would keep that coupling away from both Product and Debtor.

This is a typical problem for us whenever we have to "move" something. When identifier of an "orchestrator" is hard to define we'd introduce technical ACL which would do all the complex logic but this is not that efficient and hard to track.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants