Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
c5d7f03
feat(messaging): dump the application's message flow graph as Mermaid…
dgafka Oct 17, 2026
e4548c2
feat(messaging): show the distributed bus in the message flow graph
dgafka Oct 18, 2026
ce1572a
perf(messaging): build the message flow graph in linear time
dgafka Oct 18, 2026
2c5b6e1
perf(messaging): filter and classify source tokens by id in one pass
dgafka Oct 18, 2026
eb8066f
fix(messaging): infer events created by aggregate helpers
dgafka Oct 18, 2026
bab7221
fix(messaging): retain publication through local event variables
dgafka Oct 18, 2026
558f5ee
test(messaging): pin event recording on existing aggregate instances
dgafka Oct 18, 2026
eb806da
perf(messaging): reuse file tokens and method facts across helper scans
dgafka Oct 18, 2026
3dff359
test(messaging): cover event bus collaborator type variations
dgafka Oct 18, 2026
e9b2810
fix(messaging): infer constant and enum message routing keys
dgafka Oct 18, 2026
b9d1299
fix(messaging): connect named event aliases to their subscribers
dgafka Oct 18, 2026
587f445
fix(messaging): classify class-routed query emissions correctly
dgafka Oct 18, 2026
7cfb72e
test(messaging): cover event-sourced emission shapes
dgafka Oct 18, 2026
b25b7ca
fix(messaging): show events returned by decision handlers
dgafka Oct 18, 2026
6803ff6
test(messaging): cover saga command and event flows
dgafka Oct 18, 2026
a83e582
fix(messaging): connect inferred events to polymorphic subscribers
dgafka Oct 18, 2026
b749139
fix(messaging): preserve repeated and mixed handler registrations
dgafka Oct 18, 2026
ef37745
test(messaging): pin dotted and wildcard event routing
dgafka Oct 18, 2026
5ddce90
test(messaging): cover aggregate query and upsert routes
dgafka Oct 18, 2026
9a76a5f
test(messaging): cover channel chains and optional routers
dgafka Oct 18, 2026
7209ae1
test(messaging): cover asynchronous channel combinations and overrides
dgafka Oct 18, 2026
fb48758
test(messaging): cover inherited and header-driven business gateways
dgafka Oct 18, 2026
3086309
test(messaging): cover scheduled polling and required reply chains
dgafka Oct 18, 2026
bd2c719
fix(messaging): retain distributed calls with dynamic destinations
dgafka Oct 18, 2026
90506b3
fix(messaging): expose polling and streaming projection inputs
dgafka Oct 18, 2026
422f154
test(messaging): cover handler source ownership and nested syntax
dgafka Oct 18, 2026
4141486
test(messaging): pin source name resolution and dynamic class limits
dgafka Oct 18, 2026
3a5d319
fix(messaging): preserve declared flows when source files are unavail…
dgafka Oct 18, 2026
84e4fa4
test(messaging): pin framework exclusions from application graphs
dgafka Oct 18, 2026
790a626
fix(messaging): preserve graph edge deduplication through public copies
dgafka Oct 18, 2026
7eaa607
test(messaging): cover empty and machine-readable flow dumps
dgafka Oct 18, 2026
8556ea9
fix(messaging): resolve source imports in their lexical namespace
dgafka Oct 18, 2026
6274c48
fix(messaging): preserve PHP routing string literal semantics
dgafka Oct 18, 2026
0c08640
fix(messaging): distinguish inherited self from late-bound static
dgafka Oct 18, 2026
6f25106
fix(messaging): infer emissions from scheduled endpoints
dgafka Oct 18, 2026
4326d58
fix(messaging): include configured router destination channels
dgafka Oct 18, 2026
0f025ac
fix(messaging): show a named event as one node reached by its class a…
dgafka Oct 18, 2026
408a514
refactor(messaging): deduplicate flow edges once, when the graph is c…
dgafka Oct 18, 2026
c399d3c
fix(messaging): leave configured channels nothing connects to out of …
dgafka Oct 18, 2026
92cc872
perf(messaging): resolve event routes through each event's own class …
dgafka Oct 18, 2026
43f2e6b
perf(messaging): classify per-token scanner checks by token id
dgafka Oct 18, 2026
4e3554a
docs(messaging): record coordinator flow graph review increments
dgafka Oct 18, 2026
28348bb
docs(messaging): summarize completed flow graph challenge
dgafka Oct 18, 2026
9c0c17f
docs(messaging): drop the flow graph challenge ledger from the branch
dgafka Oct 18, 2026
d945ca8
feat(messaging): make the message flow graph an Enterprise feature
dgafka Oct 18, 2026
17e609e
Merge remote-tracking branch 'origin/main' into dgafka/full-flow-dump
dgafka Oct 19, 2026
bcbbc63
refactor(messaging): register the message flow graph only for an Ente…
dgafka Oct 19, 2026
7290cc5
feat(tracing): bus spans name the message they carry as messaging.des…
dgafka Oct 19, 2026
b005b52
test(tracing): a shared catalogue of flow scenarios, written as expec…
dgafka Oct 19, 2026
aa6df76
test(messaging): every recorded step of a catalogued flow is a path i…
dgafka Oct 19, 2026
a1ac038
test(open-telemetry): every catalogued flow is exported as its expect…
dgafka Oct 19, 2026
ec5095d
test(dbal): every catalogued flow is stored as its expected spans and…
dgafka Oct 19, 2026
73affbd
feat(messaging): the flow graph shows splitters, orchestrators and th…
dgafka Oct 19, 2026
256dd6a
test(tracing): the flow catalogue covers synchronous, event, asynchro…
dgafka Oct 19, 2026
c3c82e7
test(tracing): drop the flow-shape tests the shared catalogue now runs
dgafka Oct 19, 2026
ea5831a
test(open-telemetry): drop the flow-shape tests the shared catalogue …
dgafka Oct 19, 2026
c6dbfb4
feat(messaging): the flow graph shows interceptors, the handlers thei…
dgafka Oct 19, 2026
0c721fa
test(tracing): the flow catalogue covers aggregates, sagas and interc…
dgafka Oct 19, 2026
01d316e
test(tracing): the flow catalogue covers failing handlers, and flow t…
dgafka Oct 19, 2026
e7c1706
test(tracing): the flow catalogue covers channel and interceptor fail…
dgafka Oct 19, 2026
117d9cf
test(tracing): the flow catalogue covers separate traces, user header…
dgafka Oct 19, 2026
4e45a1e
test(tracing): drop the tracing and OpenTelemetry tests and fixtures …
dgafka Oct 19, 2026
4e1a374
test(tracing): the conformance suites name the modules they run, so a…
dgafka Oct 19, 2026
5ac187e
fix(tracing): a send span is recorded only for a message that reached…
dgafka Oct 19, 2026
1df44e2
feat(messaging): the flow graph shows transformers, each orchestrator…
dgafka Oct 19, 2026
541181e
test(tracing): the flow catalogue covers delays, retries, dead letter…
dgafka Oct 19, 2026
8b36a58
test(tracing): the flow catalogue covers routers with several destina…
dgafka Oct 19, 2026
601c21a
test(tracing): the flow catalogue covers polling and partitioned proj…
dgafka Oct 19, 2026
32b5c87
refactor(tracing): decision model loads and conditional appends are s…
dgafka Oct 19, 2026
2c3d6ee
test(tracing): the flow catalogue covers the dynamic consistency boun…
dgafka Oct 19, 2026
f731ce2
test(tracing): the flow graph, exported span and stored flow checks b…
dgafka Oct 19, 2026
e13a335
test(tracing): a flow counts as finished after the stage that leaves …
dgafka Oct 19, 2026
163cc40
test(tracing): a cross-module flow catalogue checks database channels…
dgafka Oct 19, 2026
84bb71f
fix(tracing): projection initialize, trigger, reset, backfill, rebuil…
dgafka Oct 19, 2026
ca7bf3f
fix(tracing): a scheduled endpoint that sends nowhere is traced as a …
dgafka Oct 19, 2026
90899d0
fix(tracing): a batch relayed from the outbox continues the trace of …
dgafka Oct 19, 2026
ae7c4bc
test(tracing): the cross-module catalogue covers projections on the d…
dgafka Oct 19, 2026
6324b54
test(tracing): the cross-module catalogue covers commands of two tena…
dgafka Oct 19, 2026
75c9c63
fix(flow-tracking): a message replayed from the dead letter or forwar…
dgafka Oct 19, 2026
c6eb465
fix(flow): services sharing a transport channel each keep their route…
dgafka Oct 19, 2026
9ce3181
fix(flow): the flow graph draws distributed event publishing only to …
dgafka Oct 19, 2026
817a759
fix(tracing): each send relayed in a batch keeps the carried and untr…
dgafka Oct 19, 2026
70a1c83
fix(tracing): a send delivered by a send retry is traced as succeeded…
dgafka Oct 19, 2026
c3b943e
fix(tracing): a route whose message the message collector holds back …
dgafka Oct 19, 2026
a566343
fix(flow): the Mermaid dump quotes edge labels, so a label with paren…
dgafka Oct 19, 2026
1961f02
feat(flow): the Mermaid dump labels a pollable channel as a queue and…
dgafka Oct 19, 2026
ffb966c
feat(flow): the Mermaid dump labels every channel as a channel
dgafka Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Monorepo\CrossModuleTests\Fixture\Flow;

use Ecotone\Test\Flow\FlowScenario;

/**
* licence Apache-2.0
*/
final class CrossModuleFlowScenarioCatalogue
{
/**
* @return iterable<string, array{FlowScenario}>
*/
public static function dataProvider(): iterable
{
foreach (self::all() as $scenario) {
yield $scenario->name => [$scenario];
}
}

/**
* @return list<FlowScenario>
*/
public static function all(): array
{
return [
...DbalFlowScenarios::all(),
...EventSourcingFlowScenarios::all(),
...MultiTenantFlowScenarios::all(),
];
}
}
300 changes: 300 additions & 0 deletions Monorepo/CrossModuleTests/Fixture/Flow/DbalFlowScenarios.php

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions Monorepo/CrossModuleTests/Fixture/Flow/EventSourcingFlowScenarios.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

namespace Monorepo\CrossModuleTests\Fixture\Flow;

use Ecotone\Api\Attribute\EventHandler;
use Ecotone\Api\Dbal\ExtensionObject\DbalBackedMessageChannelBuilder;
use Ecotone\Api\ExtensionObject\ExecutionPollingMetadata;
use Ecotone\Api\Lite\Test\FlowTestSupport;
use Ecotone\Api\Projecting\FromAggregateStream;
use Ecotone\Api\Projecting\Partitioned;
use Ecotone\Api\Projecting\Projection;
use Ecotone\Api\Projecting\ProjectionRebuild;
use Ecotone\Api\Tracing\SpanAttribute;
use Ecotone\Api\Tracing\SpanKind;
use Ecotone\Test\Flow\ExpectedSpan;
use Ecotone\Test\Flow\FlowScenario;
use Ecotone\Test\Flow\FlowStage;
use Ecotone\Test\Flow\Fixture\FlowOrder;
use Ecotone\Test\Flow\Fixture\FlowOrderPlaced;

/**
* licence Apache-2.0
*/
final class EventSourcingFlowScenarios
{
/**
* @return list<FlowScenario>
*/
public static function all(): array
{
return [
self::projectionOnTheDatabaseEventStoreRebuilt(),
self::partitionedProjectionRebuiltThroughADatabaseChannel(),
self::projectionDeletedInitializedAndBackfilledFromTheDatabaseEventStore(),
];
}

private static function projectionOnTheDatabaseEventStoreRebuilt(): FlowScenario
{
$projection = new #[Projection('order_list'), FromAggregateStream(FlowOrder::class)] class () {
#[EventHandler(endpointId: 'order_list.placed')]
public function whenPlaced(FlowOrderPlaced $event): void
{
}
};

return new FlowScenario('projection on the database event store rebuilt from the console', [FlowOrder::class, FlowOrderPlaced::class, $projection::class], [$projection], [], [
self::orderPlaced(),
FlowStage::when(fn (FlowTestSupport $ecotone) => $ecotone->runConsoleCommand('ecotone:projection:rebuild', ['name' => 'order_list']))
->expect(self::operation('rebuild')->containing(self::projectedOrder())),
]);
}

private static function partitionedProjectionRebuiltThroughADatabaseChannel(): FlowScenario
{
$projection = new #[Projection('order_list'), Partitioned, FromAggregateStream(FlowOrder::class), ProjectionRebuild(asyncChannelName: 'order_list_rebuild')] class () {
#[EventHandler(endpointId: 'order_list.placed')]
public function whenPlaced(FlowOrderPlaced $event): void
{
}
};

return new FlowScenario('partitioned projection rebuilt through a database channel', [FlowOrder::class, FlowOrderPlaced::class, $projection::class], [$projection], [DbalBackedMessageChannelBuilder::create('order_list_rebuild')], [
self::orderPlaced(),
FlowStage::when(fn (FlowTestSupport $ecotone) => $ecotone->runConsoleCommand('ecotone:projection:rebuild', ['name' => 'order_list']))
->expect(self::operation('rebuild')->containing(ExpectedSpan::named('send order_list_rebuild')->ofKind(SpanKind::Producer)))
->expectNoSpanNamed('receive order_list_rebuild')
->leavingWorkPending(),
FlowStage::when(fn (FlowTestSupport $ecotone) => $ecotone->run('order_list_rebuild', ExecutionPollingMetadata::createWithTestingSetup()))
->expect(
self::operation('rebuild')->containing(
ExpectedSpan::named('send order_list_rebuild')->containing(
ExpectedSpan::named('receive order_list_rebuild')->ofKind(SpanKind::Consumer)->containing(self::projectedOrder()),
),
),
),
]);
}

private static function projectionDeletedInitializedAndBackfilledFromTheDatabaseEventStore(): FlowScenario
{
$projection = new #[Projection('order_list'), FromAggregateStream(FlowOrder::class)] class () {
#[EventHandler(endpointId: 'order_list.placed')]
public function whenPlaced(FlowOrderPlaced $event): void
{
}
};

return new FlowScenario('projection deleted, initialized and backfilled from the database event store', [FlowOrder::class, FlowOrderPlaced::class, $projection::class], [$projection], [], [
self::orderPlaced(),
FlowStage::when(function (FlowTestSupport $ecotone): void {
$ecotone->deleteProjection('order_list');
$ecotone->initializeProjection('order_list');
$ecotone->runConsoleCommand('ecotone:projection:backfill', ['name' => 'order_list']);
})
->expect(self::operation('delete'), self::operation('initialize'), self::operation('backfill')->containing(self::projectedOrder())),
]);
}

private static function orderPlaced(): FlowStage
{
return FlowStage::when(fn (FlowTestSupport $ecotone, array $metadata) => $ecotone->sendCommandWithRouting('flow_order.place', ['orderId' => 'order-1'], metadata: $metadata))
->expect(
ExpectedSpan::named('send command')->containing(
ExpectedSpan::named('process FlowOrder::place')->withAttributes([SpanAttribute::ENDPOINT_ID => 'flow_order.placing']),
ExpectedSpan::named('send event')->containing(
ExpectedSpan::named('route order_list')->containing(
ExpectedSpan::named('process order_list')->containing(self::projectedOrder()),
),
),
),
);
}

private static function operation(string $operation): ExpectedSpan
{
return ExpectedSpan::named($operation . ' order_list')->asRoot()->ofKind(SpanKind::Server)->withAttributes([SpanAttribute::PROJECTION_NAME => 'order_list', SpanAttribute::PROJECTION_OPERATION => $operation]);
}

private static function projectedOrder(): ExpectedSpan
{
return ExpectedSpan::named('process class@anonymous::whenPlaced')->withAttributes([SpanAttribute::ENDPOINT_ID => 'order_list.placed']);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Monorepo\CrossModuleTests\Fixture\Flow;

use Ecotone\Api\Attribute\Asynchronous;
use Ecotone\Api\Attribute\CommandHandler;
use Ecotone\Api\Dbal\DbalConnectionFactory;
use Ecotone\Api\Dbal\ExtensionObject\DbalBackedMessageChannelBuilder;
use Ecotone\Api\Dbal\ExtensionObject\MultiTenantConfiguration;
use Ecotone\Api\ExtensionObject\ExecutionPollingMetadata;
use Ecotone\Api\Lite\Test\FlowTestSupport;
use Ecotone\Api\Tracing\SpanAttribute;
use Ecotone\Api\Tracing\SpanKind;
use Ecotone\Test\Flow\ExpectedSpan;
use Ecotone\Test\Flow\FlowScenario;
use Ecotone\Test\Flow\FlowStage;
use Test\Ecotone\EventSourcing\SecondaryTestDatabaseConnectionFactory;

/**
* licence Apache-2.0
*/
final class MultiTenantFlowScenarios
{
/**
* @return list<FlowScenario>
*/
public static function all(): array
{
return [
self::commandsOfTwoTenantsConsumedFromTheirOwnDatabases(),
];
}

private static function commandsOfTwoTenantsConsumedFromTheirOwnDatabases(): FlowScenario
{
$orders = new class () {
#[Asynchronous('orders')]
#[CommandHandler('order.place', endpointId: 'order.placing')]
public function place(string $orderId): void
{
}
};
$sentFor = fn (string $tenant) => ExpectedSpan::named('send command')->asRoot()->withAttributes(['tenant' => $tenant])->containing(
ExpectedSpan::named('send orders')->ofKind(SpanKind::Producer),
);
$handledFor = fn (string $tenant) => ExpectedSpan::named('send command')->asRoot()->withAttributes(['tenant' => $tenant])->containing(
ExpectedSpan::named('send orders')->containing(
ExpectedSpan::named('receive orders')->ofKind(SpanKind::Consumer)->containing(
ExpectedSpan::named('process class@anonymous::place')->withAttributes([SpanAttribute::ENDPOINT_ID => 'order.placing']),
),
),
);

return new FlowScenario('commands of two tenants consumed from their own databases', [$orders::class], [
$orders,
'tenant_a_connection' => new DbalConnectionFactory(getenv('DATABASE_DSN') ?: 'pgsql://ecotone:secret@127.0.0.1:5432/ecotone'),
'tenant_b_connection' => new DbalConnectionFactory(SecondaryTestDatabaseConnectionFactory::resolveDsn()),
], [
MultiTenantConfiguration::create('tenant', ['tenant_a' => 'tenant_a_connection', 'tenant_b' => 'tenant_b_connection']),
DbalBackedMessageChannelBuilder::create('orders'),
], [
FlowStage::when(fn (FlowTestSupport $ecotone, array $metadata) => $ecotone->sendCommandWithRouting('order.place', '1', metadata: [...$metadata, 'tenant' => 'tenant_a']))
->expect($sentFor('tenant_a'))
->leavingWorkPending(),
FlowStage::when(fn (FlowTestSupport $ecotone, array $metadata) => $ecotone->sendCommandWithRouting('order.place', '2', metadata: [...$metadata, 'tenant' => 'tenant_b']))
->expect($sentFor('tenant_b'))
->leavingWorkPending(),
FlowStage::when(fn (FlowTestSupport $ecotone) => $ecotone->run('orders', ExecutionPollingMetadata::createWithTestingSetup()))
->expect($handledFor('tenant_a'), $handledFor('tenant_b')),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Monorepo\CrossModuleTests\Tests\Tracing;

use Ecotone\Api\Dbal\DbalConnectionFactory;
use Ecotone\Api\Dbal\Tracing\FlowTrackingConfiguration;
use Ecotone\Api\Dbal\Tracing\TracingGateway;
use Ecotone\Api\ExtensionObject\ModulePackageList;
use Ecotone\Api\Lite\EcotoneLite;
use Ecotone\Api\Tracing\InMemorySpanRecorder;
use Ecotone\Api\Tracing\TracingConfiguration;
use Ecotone\Dbal\Test\StoredFlowVerifier;
use Ecotone\OpenTelemetry\Test\ExportedSpans;
use Ecotone\Test\Flow\FlowScenario;
use Ecotone\Test\Flow\MessageFlowGraphVerifier;
use Ecotone\Test\LicenceTesting;
use Monorepo\CrossModuleTests\Fixture\Flow\CrossModuleFlowScenarioCatalogue;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use PHPUnit\Framework\Attributes\DataProviderExternal;
use PHPUnit\Framework\TestCase;
use Test\Ecotone\Dbal\DbalMessagingTestCase;
use Test\Ecotone\EventSourcing\EventSourcingMessagingTestCase;
use Test\Ecotone\EventSourcing\SecondaryTestDatabaseConnectionFactory;

/**
* licence Apache-2.0
* @internal
*/
final class CrossModuleFlowConformanceTest extends TestCase
{
protected function setUp(): void
{
EventSourcingMessagingTestCase::clearDataTables(DbalMessagingTestCase::prepareConnection()->createContext()->getDbalConnection());
EventSourcingMessagingTestCase::clearDataTables((new DbalConnectionFactory(SecondaryTestDatabaseConnectionFactory::resolveDsn()))->createContext()->getDbalConnection());
}

#[DataProviderExternal(CrossModuleFlowScenarioCatalogue::class, 'dataProvider')]
public function test_every_cross_module_flow_is_traced_exported_stored_and_drawn_alike(FlowScenario $scenario): void
{
$recorder = new InMemorySpanRecorder();
$exporter = new InMemoryExporter();
$ecotone = EcotoneLite::bootstrapFlowTesting(
$scenario->classesToResolve,
[
...$scenario->services,
InMemorySpanRecorder::class => $recorder,
TracerProviderInterface::class => new TracerProvider(new SimpleSpanProcessor($exporter)),
DbalConnectionFactory::class => new DbalConnectionFactory(getenv('DATABASE_DSN') ?: 'pgsql://ecotone:secret@127.0.0.1:5432/ecotone'),
],
[
FlowTrackingConfiguration::createWithDefaults(),
TracingConfiguration::createWithDefaults()->withSpanRecorder(InMemorySpanRecorder::class),
...$scenario->extensionObjects,
],
enabledModules: [ModulePackageList::DBAL_PACKAGE, ModulePackageList::EVENT_SOURCING_PACKAGE, ModulePackageList::JMS_CONVERTER_PACKAGE, ModulePackageList::TRACING_PACKAGE],
licenceKey: LicenceTesting::VALID_LICENCE,
)->setupDatabaseTables();
$tracing = $ecotone->getGateway(TracingGateway::class);

foreach (array_keys($scenario->stages) as $index) {
self::assertNull($scenario->runStage($index, $ecotone));

self::assertNull($scenario->mismatchAfterStage($index, $recorder->recordedSpans()));
self::assertNull($scenario->mismatchAfterStage($index, ExportedSpans::of($exporter)));
self::assertNull(StoredFlowVerifier::mismatchAfterStage($scenario, $index, $tracing, $recorder->recordedSpans()));
}
self::assertNull(MessageFlowGraphVerifier::mismatchIn($ecotone->getMessageFlowGraph(), $recorder->recordedSpans()));
}
}
2 changes: 2 additions & 0 deletions packages/Dbal/Api/ExtensionObject/DbalDeadLetterBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Ecotone\Api\Messaging\MessageHeaders;
use Ecotone\Dbal\Recoverability\DeadLetterHandler;
use Ecotone\Dbal\Recoverability\DeadLetterStore;
use Ecotone\Dbal\Tracing\FlowTrackingScope;
use Ecotone\Messaging\Config\Container\Definition;
use Ecotone\Messaging\Config\Container\InterfaceToCallReference;
use Ecotone\Messaging\Config\Container\MessagingContainerBuilder;
Expand Down Expand Up @@ -158,6 +159,7 @@ public function compile(MessagingContainerBuilder $builder): Definition
$builder->register($deadLetterHandlerReference, new Definition(DeadLetterHandler::class, [
Reference::to(self::storeReferenceFor($this->referenceName)),
Reference::to(MessageBuilder::class),
Reference::to(FlowTrackingScope::class),
]));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Ecotone\Api\ExtensionObject\ModulePackageList;
use Ecotone\Api\Gateway\EcotoneClockInterface;
use Ecotone\Dbal\DbalReconnectableConnectionFactory;
use Ecotone\Dbal\Tracing\FlowTrackingScope;
use Ecotone\Enqueue\CachedConnectionFactory;
use Ecotone\Messaging\Config\Annotation\AnnotationModule;
use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver;
Expand Down Expand Up @@ -134,6 +135,7 @@ private function createOutboxPublisherDefinition(DbalBackedMessageChannelBuilder
$outboxForwardingChannel->getMaxForwardingBatchSize(),
$outboxForwardingChannel->getFinalFailureStrategy() ?? $inboundChannelAdapter->getFinalFailureStrategy(),
new Reference(MessageBuilder::class),
new Reference(FlowTrackingScope::class),
]);
}
}
15 changes: 12 additions & 3 deletions packages/Dbal/src/BatchForwarding/DbalOutboxPublisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Ecotone\Api\Messaging\MessageHeaders;
use Ecotone\Dbal\Connection\DbalContext;
use Ecotone\Dbal\Connection\DbalType;
use Ecotone\Dbal\Tracing\FlowTrackingScope;
use Ecotone\Enqueue\CachedConnectionFactory;
use Ecotone\Messaging\Channel\BatchSupportingMessageChannel;
use Ecotone\Messaging\Channel\MessageChannelInterceptorAdapter;
Expand Down Expand Up @@ -59,10 +60,16 @@ public function __construct(
private int $maxBatchSize,
private FinalFailureStrategy $finalFailureStrategy,
private MessageBuilder $messageBuilder,
private FlowTrackingScope $flowTracking,
) {
}

public function publishBatch(): int
{
return $this->flowTracking->publishingStepsAfterCommit(fn (): int => $this->forwardClaimedBatch());
}

private function forwardClaimedBatch(): int
{
/** @var DbalContext $context */
$context = $this->connectionFactory->createContext();
Expand All @@ -83,9 +90,11 @@ public function publishBatch(): int

$deliveredRowIds = [];
$releasedRowIds = [];
foreach ($this->groupByTargetChannel($drainedRecords, $releasedRowIds) as $groupTargetChannelName => $groupedRecords) {
$this->forwardGroup((string) $groupTargetChannelName, $groupedRecords, $deliveredRowIds, $releasedRowIds);
}
$this->flowTracking->storingSpansOf(function () use ($drainedRecords, &$deliveredRowIds, &$releasedRowIds): void {
foreach ($this->groupByTargetChannel($drainedRecords, $releasedRowIds) as $groupTargetChannelName => $groupedRecords) {
$this->forwardGroup((string) $groupTargetChannelName, $groupedRecords, $deliveredRowIds, $releasedRowIds);
}
});

$this->deleteRows($context, $connection, $deliveredRowIds);
$this->releaseRows($context, $connection, $releasedRowIds);
Expand Down
Loading
Loading