-
-
Notifications
You must be signed in to change notification settings - Fork 21
Multiple message handlers #573
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| <?php | ||
|
|
||
| namespace Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers; | ||
|
|
||
| use Ecotone\Messaging\Attribute\Parameter\Payload; | ||
| use Ecotone\Modelling\Attribute\EventHandler; | ||
| use Ecotone\Modelling\Attribute\Identifier; | ||
| use Ecotone\Modelling\Attribute\Saga; | ||
|
|
||
| #[Saga] | ||
| class InvoiceOrder | ||
| { | ||
| private array $products; | ||
|
|
||
| public function __construct( | ||
| #[Identifier] private string $debtorId, | ||
| ) { | ||
| $this->products = []; | ||
| } | ||
|
|
||
| public function products(): array | ||
| { | ||
| return $this->products; | ||
| } | ||
|
|
||
| #[EventHandler(endpointId: 'createWhenProductBecameBillable', identifierMapping: ['debtorId' => 'payload.debtor'])] | ||
| public static function createWhenProductBecameBillable( | ||
| #[Payload] ProductBecameBillable $event | ||
| ): self { | ||
| $invoiceOrder = new self($event->debtor); | ||
| $invoiceOrder->products[] = $event->productId; | ||
|
|
||
| return $invoiceOrder; | ||
| } | ||
|
|
||
| #[EventHandler(endpointId: 'createWhenDebtorWasChanged', identifierMapping: ['debtorId' => 'payload.newDebtor'])] | ||
| public static function createWhenDebtorWasChanged( | ||
| #[Payload] ProductDebtorWasChanged $event | ||
| ): self { | ||
| $invoiceOrder = new self($event->newDebtor); | ||
| $invoiceOrder->products[] = $event->productId; | ||
|
|
||
| return $invoiceOrder; | ||
| } | ||
|
|
||
| #[EventHandler(endpointId: 'removeProductWhenDebtorWasChanged', identifierMapping: ['debtorId' => 'payload.oldDebtor'])] | ||
| public function removeProductWhenDebtorWasChanged( | ||
| #[Payload] ProductDebtorWasChanged $event | ||
| ): void { | ||
| unset($this->products[$event->productId]); | ||
| } | ||
|
|
||
| #[EventHandler(endpointId: 'addProductWhenDebtorWasChanged', identifierMapping: ['debtorId' => 'payload.newDebtor'])] | ||
| public function addProductWhenDebtorWasChanged( | ||
| #[Payload] ProductDebtorWasChanged $event | ||
| ): void { | ||
| if (!in_array($event->productId, $this->products, true)) { | ||
| $this->products[] = $event->productId; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?php | ||
|
|
||
| namespace Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers; | ||
|
|
||
| class ProductBecameBillable | ||
| { | ||
| public function __construct( | ||
| public string $productId, | ||
| public string $debtor, | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| namespace Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers; | ||
|
|
||
| class ProductDebtorWasChanged | ||
| { | ||
| public function __construct( | ||
| public string $productId, | ||
| public string $oldDebtor, | ||
| public string $newDebtor, | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| namespace Modelling\Unit; | ||
|
|
||
| use Ecotone\Lite\EcotoneLite; | ||
| use Ecotone\Messaging\Channel\SimpleMessageChannelBuilder; | ||
| use Ecotone\Messaging\Config\ServiceConfiguration; | ||
| use PHPUnit\Framework\TestCase; | ||
| use Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers\InvoiceOrder; | ||
| use Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers\ProductBecameBillable; | ||
| use Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers\ProductDebtorWasChanged; | ||
|
|
||
| class MultipleMessageHandlersTest extends TestCase | ||
| { | ||
| public function test_multiple_message_handlers(): void | ||
| { | ||
| $ecotone = EcotoneLite::bootstrapFlowTesting( | ||
| configuration: ServiceConfiguration::createWithDefaults() | ||
| ->withNamespaces(['Test\Ecotone\Modelling\Fixture\MultipleMessageHandlers']) | ||
| ->withExtensionObjects([ | ||
| SimpleMessageChannelBuilder::createQueueChannel('invoiceOrder') | ||
| ]) | ||
| , | ||
| ); | ||
|
|
||
| $ecotone->publishEvent(new ProductBecameBillable(productId: 'product-1', debtor: 'debtor-1')); | ||
|
|
||
| self::assertEquals(['product-1'], $ecotone->getAggregate(InvoiceOrder::class, 'debtor-1')->products()); | ||
|
|
||
| $ecotone->publishEvent(new ProductDebtorWasChanged(productId: 'product-1', oldDebtor: 'debtor-1', newDebtor: 'debtor-2')); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? Do you have other scenarios where we would like to trigger same Aggregate but more than one instance, based on single Event?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
|
||
| self::assertEquals([], $ecotone->getAggregate(InvoiceOrder::class, 'debtor-1')->products()); | ||
| self::assertEquals(['product-1'], $ecotone->getAggregate(InvoiceOrder::class, 'debtor-1')->products()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.