Skip to content

Commit c90ce2b

Browse files
committed
disallow multi-stream partitioned projections
1 parent c966b48 commit c90ce2b

File tree

2 files changed

+21
-3
lines changed

2 files changed

+21
-3
lines changed

packages/PdoEventSourcing/src/Config/ProophProjectingModule.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ private static function resolveConfigs(
108108
$projectionName = $projectionAttribute->name;
109109
$isPartitioned = $partitionedAttribute !== null;
110110

111+
// @todo: Partitioned projections cannot be declared with multiple streams because the current partition provider cannot merge partitions from multiple streams.
112+
if ($isPartitioned && count($streamAttributes) > 1) {
113+
throw ConfigurationException::create(
114+
"Partitioned projection {$projectionName} cannot declare multiple streams. Use a single aggregate stream or remove #[Partitioned]."
115+
);
116+
}
117+
111118
$sources = [];
112119
foreach ($streamAttributes as $streamAttribute) {
113120
if ($isPartitioned && ! $streamAttribute->aggregateType) {

packages/PdoEventSourcing/tests/Projecting/Global/MultiStreamProjectionTest.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Ecotone\Lite\Test\FlowTestSupport;
1515
use Ecotone\Messaging\Config\ModulePackageList;
1616
use Ecotone\Messaging\Config\ServiceConfiguration;
17+
use Ecotone\Messaging\Config\ConfigurationException;
1718
use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata;
1819
use Ecotone\Messaging\MessageHeaders;
1920
use Ecotone\Modelling\Attribute\EventHandler;
@@ -51,7 +52,6 @@ public function test_building_multi_stream_synchronous_projection(): void
5152
$this->expectExceptionMessage('Calendar with id cal-build-1 not found');
5253
$ecotone->sendQueryWithRouting('getCalendar', 'cal-build-1');
5354

54-
// create calendar and schedule meeting to drive projection entries
5555
$calendarId = 'cal-build-1';
5656
$meetingId = 'm-build-1';
5757
$ecotone->sendCommand(new CreateCalendar($calendarId));
@@ -150,10 +150,21 @@ public function test_reset_and_delete_on_polling_multi_stream_projection(): void
150150
$ecotone->sendQueryWithRouting('getCalendar', 'cal-poll-reset');
151151
}
152152

153+
public function test_declaring_partitioned_multi_stream_projection_throws_exception(): void
154+
{
155+
$projection = new #[ProjectionV2(self::NAME), Partitioned(MessageHeaders::EVENT_AGGREGATE_ID), FromStream(CalendarWithInternalRecorder::class), FromStream(MeetingWithEventSourcing::class)] class () {
156+
public const NAME = 'calendar_multi_stream_projection';
157+
};
158+
159+
$this->expectException(ConfigurationException::class);
160+
$this->expectExceptionMessage('Partitioned projection calendar_multi_stream_projection cannot declare multiple streams');
161+
162+
// Bootstrapping should fail due to invalid configuration
163+
$this->bootstrapEcotone([$projection::class], [$projection]);
164+
}
165+
153166
private function createMultiStreamProjection(): object
154167
{
155-
// Configure FromStream with multiple streams: Calendar/Meeting aggregates
156-
// Real-world usage: projection reacts to Calendar/Meeting events to generate a read model
157168
return new #[ProjectionV2(self::NAME), FromStream(CalendarWithInternalRecorder::class), FromStream(MeetingWithEventSourcing::class)] class () {
158169
public const NAME = 'calendar_multi_stream_projection';
159170

0 commit comments

Comments
 (0)