Skip to content

Commit b12000b

Browse files
committed
feat: service map convention improvements
1 parent 88f52e0 commit b12000b

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

packages/Ecotone/src/Modelling/Api/Distribution/DistributedServiceMap.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ final class DistributedServiceMap implements DefinedObject
2525
{
2626
/**
2727
* @param array<string, string> $commandMapping - service name -> channel name (for command routing)
28-
* @param array<string, array{keys: ?array<string>, exclude: array<string>}> $eventSubscriptions - channel name -> ['keys' => [...] or null, 'exclude' => [...]]
28+
* @param array<string, array{keys: ?array<string>, exclude: array<string>, include: array<string>}> $eventSubscriptions - channel name -> ['keys' => [...] or null, 'exclude' => [...], 'include' => [...]]
2929
* @param array<object> $distributedBusAnnotations
3030
* @param bool|null $legacyMode - null = not set, true = legacy (withServiceMapping), false = new API (withCommandMapping/withEventMapping)
3131
*/
@@ -58,6 +58,7 @@ public function withServiceMapping(string $serviceName, string $channelName, ?ar
5858
$self->eventSubscriptions[$channelName] = [
5959
'keys' => $subscriptionRoutingKeys,
6060
'exclude' => [],
61+
'include' => [],
6162
];
6263

6364
return $self;
@@ -83,17 +84,26 @@ public function withCommandMapping(string $targetServiceName, string $channelNam
8384
*
8485
* @param string $channelName Target channel to send events to
8586
* @param array<string> $subscriptionKeys Routing key patterns to match
86-
* @param array<string> $excludeEventsFromServices Service names whose events should NOT be sent to this channel
87+
* @param array<string> $excludePublishingServices Service names whose events should NOT be sent to this channel
88+
* @param array<string> $includePublishingServices Service names whose events should ONLY be sent to this channel (whitelist)
8789
*/
88-
public function withEventMapping(string $channelName, array $subscriptionKeys, array $excludeEventsFromServices = []): self
90+
public function withEventMapping(string $channelName, array $subscriptionKeys, array $excludePublishingServices = [], array $includePublishingServices = []): self
8991
{
92+
if ($excludePublishingServices !== [] && $includePublishingServices !== []) {
93+
throw ConfigurationException::create(
94+
"Cannot use both 'excludePublishingServices' and 'includePublishingServices' in the same event mapping for channel '{$channelName}'. " .
95+
'These parameters are mutually exclusive - use either exclude (blacklist) or include (whitelist), not both.'
96+
);
97+
}
98+
9099
$self = clone $this;
91100
$self->assertNotInLegacyMode('withEventMapping');
92101
$self->legacyMode = false;
93102

94103
$self->eventSubscriptions[$channelName] = [
95104
'keys' => $subscriptionKeys,
96-
'exclude' => $excludeEventsFromServices,
105+
'exclude' => $excludePublishingServices,
106+
'include' => $includePublishingServices,
97107
];
98108

99109
return $self;
@@ -153,7 +163,7 @@ public function getAllChannelNamesBesides(string $serviceName, string $routingKe
153163

154164
/**
155165
* NEW MODE ONLY - Get all subscription channels for an event.
156-
* Uses explicit exclude list from eventSubscriptions config.
166+
* Uses explicit exclude/include list from eventSubscriptions config.
157167
*
158168
* @param string $sourceServiceName The service publishing the event
159169
* @param string $routingKey The event routing key
@@ -166,11 +176,16 @@ public function getAllSubscriptionChannels(string $sourceServiceName, string $ro
166176
foreach ($this->eventSubscriptions as $channel => $config) {
167177
$keys = $config['keys'];
168178
$exclude = $config['exclude'];
179+
$include = $config['include'];
169180

170181
if (in_array($sourceServiceName, $exclude, true)) {
171182
continue;
172183
}
173184

185+
if ($include !== [] && ! \in_array($sourceServiceName, $include, true)) {
186+
continue;
187+
}
188+
174189
foreach ($keys as $subscriptionEventFilter) {
175190
if (BusRoutingMap::globMatch($subscriptionEventFilter, $routingKey)) {
176191
$filteredChannels[] = $channel;

packages/Ecotone/tests/Messaging/Unit/Distributed/DistributedBusWithExplicitServiceMapTest.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public function test_it_does_not_publish_event_to_publishing_service_when_exclud
290290
->withCommandMapping(targetServiceName: TestServiceName::TICKET_SERVICE, channelName: $ticketChannelName)
291291
->withCommandMapping(targetServiceName: TestServiceName::USER_SERVICE, channelName: $userChannelName)
292292
->withEventMapping(channelName: $ticketChannelName, subscriptionKeys: ['*'])
293-
->withEventMapping(channelName: $userChannelName, subscriptionKeys: ['*'], excludeEventsFromServices: [TestServiceName::USER_SERVICE])
293+
->withEventMapping(channelName: $userChannelName, subscriptionKeys: ['*'], excludePublishingServices: [TestServiceName::USER_SERVICE])
294294
);
295295
$ticketService = $this->bootstrapEcotone(TestServiceName::TICKET_SERVICE, ['Test\Ecotone\Messaging\Fixture\Distributed\DistributedEventBus\ReceiverTicket'], [new \Test\Ecotone\Messaging\Fixture\Distributed\DistributedEventBus\ReceiverTicket\TicketServiceReceiver()], $distributedTicketQueue);
296296

@@ -851,4 +851,40 @@ public function test_cannot_use_with_event_mapping_after_legacy_with_service_map
851851
->withServiceMapping('service1', 'channel1')
852852
->withEventMapping('channel2', ['*']);
853853
}
854+
855+
public function test_it_publishes_event_only_to_channel_when_source_service_is_in_include_list(): void
856+
{
857+
$distributedTicketQueue = SimpleMessageChannelBuilder::createQueueChannel($ticketChannelName = 'distributed_ticket_channel');
858+
$distributedUserQueue = SimpleMessageChannelBuilder::createQueueChannel($userChannelName = 'distributed_user_channel');
859+
$userService = $this->bootstrapEcotone(
860+
TestServiceName::USER_SERVICE,
861+
[],
862+
[],
863+
[$distributedTicketQueue, $distributedUserQueue],
864+
DistributedServiceMap::initialize()
865+
->withCommandMapping(targetServiceName: TestServiceName::TICKET_SERVICE, channelName: $ticketChannelName)
866+
->withCommandMapping(targetServiceName: TestServiceName::USER_SERVICE, channelName: $userChannelName)
867+
->withEventMapping(channelName: $ticketChannelName, subscriptionKeys: ['*'], includePublishingServices: [TestServiceName::TICKET_SERVICE])
868+
->withEventMapping(channelName: $userChannelName, subscriptionKeys: ['*'])
869+
);
870+
$ticketService = $this->bootstrapEcotone(TestServiceName::TICKET_SERVICE, ['Test\Ecotone\Messaging\Fixture\Distributed\DistributedEventBus\ReceiverTicket'], [new \Test\Ecotone\Messaging\Fixture\Distributed\DistributedEventBus\ReceiverTicket\TicketServiceReceiver()], $distributedTicketQueue);
871+
872+
$userService->getDistributedBus()->publishEvent(
873+
'userService.billing.DetailsWereChanged',
874+
'User changed billing address',
875+
metadata: ['token' => '123']
876+
);
877+
878+
self::assertNull($ticketService->getMessageChannel($ticketChannelName)->receive());
879+
self::assertNotNull($userService->getMessageChannel($userChannelName)->receive());
880+
}
881+
882+
public function test_cannot_use_both_exclude_and_include_publishing_services(): void
883+
{
884+
$this->expectException(ConfigurationException::class);
885+
$this->expectExceptionMessage("Cannot use both 'excludePublishingServices' and 'includePublishingServices' in the same event mapping for channel 'channel1'. These parameters are mutually exclusive - use either exclude (blacklist) or include (whitelist), not both.");
886+
887+
DistributedServiceMap::initialize()
888+
->withEventMapping('channel1', ['*'], excludePublishingServices: ['service1'], includePublishingServices: ['service2']);
889+
}
854890
}

0 commit comments

Comments
 (0)