diff --git a/Monorepo/Benchmark/AsyncPublishingBenchmark.php b/Monorepo/Benchmark/HighThroughputPublishingBenchmark.php similarity index 88% rename from Monorepo/Benchmark/AsyncPublishingBenchmark.php rename to Monorepo/Benchmark/HighThroughputPublishingBenchmark.php index 4814fc83b..2ad81cea6 100644 --- a/Monorepo/Benchmark/AsyncPublishingBenchmark.php +++ b/Monorepo/Benchmark/HighThroughputPublishingBenchmark.php @@ -42,7 +42,7 @@ * and command reply respectively), so the asynchronous scenarios are not supported for them and are not benchmarked. */ #[Warmup(0), Revs(1), Iterations(10)] -class AsyncPublishingBenchmark +class HighThroughputPublishingBenchmark { private const AMOUNT_OF_PUBLISHED_MESSAGES = 1000; @@ -58,13 +58,13 @@ class AsyncPublishingBenchmark public function setUpAmqpSynchronousPublishing(): void { - $this->publisher = $this->bootstrapAmqpPublisher(asyncPublishing: false); + $this->publisher = $this->bootstrapAmqpPublisher(highThroughputPublishing: false); $this->warmUpPublisher(); } - public function setUpAmqpAsyncPublishing(): void + public function setUpAmqpHighThroughputPublishing(): void { - $this->publisher = $this->bootstrapAmqpPublisher(asyncPublishing: true); + $this->publisher = $this->bootstrapAmqpPublisher(highThroughputPublishing: true); $this->warmUpPublisher(); } @@ -80,13 +80,13 @@ public function setUpAmqpBatchChannel(): void public function setUpKafkaSynchronousPublishing(): void { - $this->publisher = $this->bootstrapKafkaPublisher(asyncPublishing: false); + $this->publisher = $this->bootstrapKafkaPublisher(highThroughputPublishing: false); $this->warmUpPublisher(); } - public function setUpKafkaAsyncPublishing(): void + public function setUpKafkaHighThroughputPublishing(): void { - $this->publisher = $this->bootstrapKafkaPublisher(asyncPublishing: true); + $this->publisher = $this->bootstrapKafkaPublisher(highThroughputPublishing: true); $this->warmUpPublisher(); } @@ -135,13 +135,13 @@ public function setUpRedisBatchChannel(): void public function setUpSqsSynchronousPublishing(): void { - $this->publisher = $this->bootstrapSqsPublisher(asyncPublishing: false); + $this->publisher = $this->bootstrapSqsPublisher(highThroughputPublishing: false); $this->warmUpPublisher(); } - public function setUpSqsAsyncPublishing(): void + public function setUpSqsHighThroughputPublishing(): void { - $this->publisher = $this->bootstrapSqsPublisher(asyncPublishing: true); + $this->publisher = $this->bootstrapSqsPublisher(highThroughputPublishing: true); $this->warmUpPublisher(); } @@ -161,7 +161,7 @@ public function bench_amqp_single_message_synchronous(): void $this->publishSynchronouslyOneByOne(); } - #[BeforeMethods('setUpAmqpAsyncPublishing')] + #[BeforeMethods('setUpAmqpHighThroughputPublishing')] public function bench_amqp_single_message_asynchronous(): void { $this->publishAsynchronouslyOneByOne(); @@ -173,7 +173,7 @@ public function bench_amqp_batch_message_synchronous(): void $this->publishBatchSynchronously(); } - #[BeforeMethods('setUpAmqpAsyncPublishing')] + #[BeforeMethods('setUpAmqpHighThroughputPublishing')] public function bench_amqp_batch_message_asynchronous(): void { $this->publishBatchAsynchronously(); @@ -185,7 +185,7 @@ public function bench_amqp_multiple_batches_synchronous(): void $this->publishMultipleBatchesSynchronously(); } - #[BeforeMethods('setUpAmqpAsyncPublishing')] + #[BeforeMethods('setUpAmqpHighThroughputPublishing')] public function bench_amqp_multiple_batches_asynchronous(): void { $this->publishMultipleBatchesAsynchronously(); @@ -197,7 +197,7 @@ public function bench_kafka_single_message_synchronous(): void $this->publishSynchronouslyOneByOne(); } - #[BeforeMethods('setUpKafkaAsyncPublishing')] + #[BeforeMethods('setUpKafkaHighThroughputPublishing')] public function bench_kafka_single_message_asynchronous(): void { $this->publishAsynchronouslyOneByOne(); @@ -209,7 +209,7 @@ public function bench_kafka_batch_message_synchronous(): void $this->publishBatchSynchronously(); } - #[BeforeMethods('setUpKafkaAsyncPublishing')] + #[BeforeMethods('setUpKafkaHighThroughputPublishing')] public function bench_kafka_batch_message_asynchronous(): void { $this->publishBatchAsynchronously(); @@ -221,7 +221,7 @@ public function bench_kafka_multiple_batches_synchronous(): void $this->publishMultipleBatchesSynchronously(); } - #[BeforeMethods('setUpKafkaAsyncPublishing')] + #[BeforeMethods('setUpKafkaHighThroughputPublishing')] public function bench_kafka_multiple_batches_asynchronous(): void { $this->publishMultipleBatchesAsynchronously(); @@ -257,7 +257,7 @@ public function bench_sqs_single_message_synchronous(): void $this->publishSynchronouslyOneByOne(); } - #[BeforeMethods('setUpSqsAsyncPublishing')] + #[BeforeMethods('setUpSqsHighThroughputPublishing')] public function bench_sqs_single_message_asynchronous(): void { $this->publishAsynchronouslyOneByOne(); @@ -269,7 +269,7 @@ public function bench_sqs_batch_message_synchronous(): void $this->publishBatchSynchronously(); } - #[BeforeMethods('setUpSqsAsyncPublishing')] + #[BeforeMethods('setUpSqsHighThroughputPublishing')] public function bench_sqs_batch_message_asynchronous(): void { $this->publishBatchAsynchronously(); @@ -281,7 +281,7 @@ public function bench_sqs_multiple_batches_synchronous(): void $this->publishMultipleBatchesSynchronously(); } - #[BeforeMethods('setUpSqsAsyncPublishing')] + #[BeforeMethods('setUpSqsHighThroughputPublishing')] public function bench_sqs_multiple_batches_asynchronous(): void { $this->publishMultipleBatchesAsynchronously(); @@ -298,7 +298,7 @@ private function publishAsynchronouslyOneByOne(): void { $futures = []; for ($messageNumber = 0; $messageNumber < self::AMOUNT_OF_PUBLISHED_MESSAGES; $messageNumber++) { - $futures[] = $this->publisher->asyncPublish(self::MESSAGE_PAYLOAD, MediaType::TEXT_PLAIN); + $futures[] = $this->publisher->publishDeferred(self::MESSAGE_PAYLOAD, MediaType::TEXT_PLAIN); } foreach ($futures as $future) { $future->resolve(); @@ -314,7 +314,7 @@ private function publishBatchSynchronously(): void private function publishBatchAsynchronously(): void { - $this->publisher->asyncPublish($this->buildBatch(self::AMOUNT_OF_PUBLISHED_MESSAGES), MediaType::TEXT_PLAIN)->resolve(); + $this->publisher->publishDeferred($this->buildBatch(self::AMOUNT_OF_PUBLISHED_MESSAGES), MediaType::TEXT_PLAIN)->resolve(); } private function publishMultipleBatchesSynchronously(): void @@ -330,7 +330,7 @@ private function publishMultipleBatchesAsynchronously(): void { $futures = []; for ($batchNumber = 0; $batchNumber < self::AMOUNT_OF_BATCHES; $batchNumber++) { - $futures[] = $this->publisher->asyncPublish($this->buildBatch(self::MESSAGES_PER_BATCH), MediaType::TEXT_PLAIN); + $futures[] = $this->publisher->publishDeferred($this->buildBatch(self::MESSAGES_PER_BATCH), MediaType::TEXT_PLAIN); } foreach ($futures as $future) { $future->resolve(); @@ -375,7 +375,7 @@ private function bootstrapBatchChannel(string $modulePackage, object $channelBui return $messaging->getMessageChannel($channelBuilder->getMessageChannelName()); } - private function bootstrapAmqpPublisher(bool $asyncPublishing): MessagePublisher + private function bootstrapAmqpPublisher(bool $highThroughputPublishing): MessagePublisher { $queueName = uniqid('benchmark_orders_'); $connectionFactory = new AmqpConnectionFactory(['dsn' => getenv('RABBIT_HOST') ?: 'amqp://guest:guest@localhost:5672/%2f']); @@ -385,8 +385,8 @@ private function bootstrapAmqpPublisher(bool $asyncPublishing): MessagePublisher $publisherConfiguration = AmqpMessagePublisherConfiguration::create() ->withAutoDeclareQueueOnSend(true) ->withDefaultRoutingKey($queueName); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } $messaging = EcotoneLite::bootstrapFlowTesting( @@ -435,11 +435,11 @@ private function bootstrapRedisPublisher(): MessagePublisher return $messaging->getGateway(MessagePublisher::class); } - private function bootstrapSqsPublisher(bool $asyncPublishing): MessagePublisher + private function bootstrapSqsPublisher(bool $highThroughputPublishing): MessagePublisher { $publisherConfiguration = SqsMessagePublisherConfiguration::create(queueName: uniqid('benchmark_orders_')); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } $messaging = EcotoneLite::bootstrapFlowTesting( @@ -456,11 +456,11 @@ private function bootstrapSqsPublisher(bool $asyncPublishing): MessagePublisher return $messaging->getGateway(MessagePublisher::class); } - private function bootstrapKafkaPublisher(bool $asyncPublishing): MessagePublisher + private function bootstrapKafkaPublisher(bool $highThroughputPublishing): MessagePublisher { $publisherConfiguration = KafkaPublisherConfiguration::createWithDefaults(topicName: uniqid('benchmark_orders_')); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } $messaging = EcotoneLite::bootstrapFlowTesting( diff --git a/packages/Amqp/src/AmqpBackedMessageChannelBuilder.php b/packages/Amqp/src/AmqpBackedMessageChannelBuilder.php index cfa770b15..7762cabef 100644 --- a/packages/Amqp/src/AmqpBackedMessageChannelBuilder.php +++ b/packages/Amqp/src/AmqpBackedMessageChannelBuilder.php @@ -30,7 +30,7 @@ private function __construct( ->withDefaultRoutingKey($queueName) ->withAutoDeclareOnSend(true) ->withDefaultPersistentMode(true) - ->withAsyncPublishingChannelName($channelName) + ->withPublishingChannelName($channelName) ); } @@ -72,9 +72,14 @@ public function withPublisherConfirms(bool $enabled): self return $this; } - public function withHighThroughputPublishing(bool $enabled = true, ?int $timeoutInMilliseconds = null): self + /** + * @param bool $batchPublishing coalesces published Messages into a single publisher confirms round trip + * @param bool $nonBlockingConfirmation publishes without waiting for publisher confirms, which are awaited before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await publisher confirms before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - $this->getAmqpOutboundChannelAdapter()->withAsyncPublishing($enabled, $timeoutInMilliseconds); + $this->getAmqpOutboundChannelAdapter()->withHighThroughputPublishing($batchPublishing, $nonBlockingConfirmation, $confirmationTimeoutInMilliseconds); return $this; } @@ -93,7 +98,7 @@ public function getMessageChannelName(): string protected function supportsBatchMessages(): bool { - return $this->getAmqpOutboundChannelAdapter()->isAsyncPublishingEnabled(); + return $this->getAmqpOutboundChannelAdapter()->isBatchPublishingEnabled(); } public function getQueueName() diff --git a/packages/Amqp/src/AmqpOutboundChannelAdapter.php b/packages/Amqp/src/AmqpOutboundChannelAdapter.php index 5bbb01105..92ab7b35f 100644 --- a/packages/Amqp/src/AmqpOutboundChannelAdapter.php +++ b/packages/Amqp/src/AmqpOutboundChannelAdapter.php @@ -7,9 +7,9 @@ use Ecotone\Amqp\Transaction\AmqpTransactionInterceptor; use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\ConfigurationException; use Ecotone\Messaging\Conversion\ConversionService; @@ -54,10 +54,11 @@ public function __construct( private OutboundMessageConverter $outboundMessageConverter, private ConversionService $conversionService, private AmqpTransactionInterceptor $amqpTransactionInterceptor, - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, private ?DelayStrategy $delayStrategy = null, - private bool $asyncPublishing = false, - private int $asyncPublishingTimeout = AmqpOutboundChannelAdapterBuilder::DEFAULT_ASYNC_PUBLISHING_TIMEOUT, + private bool $batchPublishing = false, + private bool $nonBlockingConfirmation = false, + private int $confirmationTimeout = AmqpOutboundChannelAdapterBuilder::DEFAULT_CONFIRMATION_TIMEOUT, private string $channelName = '', ) { } @@ -68,8 +69,8 @@ public function __construct( public function handle(Message $message): void { $payload = $message->getPayload(); - if ($payload instanceof BatchMessage && ! $this->asyncPublishing) { - throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires async publishing to be enabled. Enable it with withAsyncPublishing(), available as part of Ecotone Enterprise.', $this->channelName !== '' ? $this->channelName : $this->exchangeName)); + if ($payload instanceof BatchMessage && ! $this->batchPublishing) { + throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires batch publishing to be enabled. Enable it with withHighThroughputPublishing(), available as part of Ecotone Enterprise.', $this->channelName !== '' ? $this->channelName : $this->exchangeName)); } $messagesToPublish = $payload instanceof BatchMessage @@ -89,7 +90,7 @@ public function handle(Message $message): void $publishRecords = $this->publishMessages($messagesToPublish, $context, $confirmations); - if ($publishRecords !== [] && $confirmations !== null && $this->canPublishAsynchronously()) { + if ($publishRecords !== [] && $confirmations !== null && $this->canDeferConfirmation()) { $this->registerPendingDelivery($publishRecords, $context, $confirmations, $prePublishConfirmationsEpoch); return; @@ -98,9 +99,9 @@ public function handle(Message $message): void $this->awaitPublisherConfirmsSynchronously($publishRecords, $context, $confirmations, $prePublishConfirmationsEpoch); } - public function isAsyncPublishingEnabled(): bool + public function isNonBlockingConfirmationEnabled(): bool { - return $this->asyncPublishing; + return $this->nonBlockingConfirmation; } /** @@ -267,11 +268,11 @@ private function prepareInteropMessage(Message $message): array return [$messageToSend, $exchangeName, $outboundMessage->getDeliveryDelay(), $timeToLive]; } - private function canPublishAsynchronously(): bool + private function canDeferConfirmation(): bool { - return $this->asyncPublishing + return $this->nonBlockingConfirmation && $this->publisherConfirms - && $this->asyncPublishingRegistry->isScopeActive(); + && $this->pendingDeliveryRegistry->isScopeActive(); } /** @@ -279,12 +280,12 @@ private function canPublishAsynchronously(): bool */ private function registerPendingDelivery(array $publishRecords, InteropAmqpContext $context, AmqpPublisherConfirmations $confirmations, int $prePublishConfirmationsEpoch): void { - $this->asyncPublishingRegistry->register( + $this->pendingDeliveryRegistry->register( $this->channelName, new AmqpPendingDelivery( $context, $publishRecords, - $this->asyncPublishingTimeout, + $this->confirmationTimeout, $this->channelName, $confirmations, $prePublishConfirmationsEpoch, @@ -302,7 +303,7 @@ private function awaitPublisherConfirmsSynchronously(array $publishRecords, Inte } if ($publishRecords === [] || $confirmations === null) { - $timeoutInSeconds = $this->asyncPublishingTimeout / 1000; + $timeoutInSeconds = $this->confirmationTimeout / 1000; if ($context instanceof AmqpLibContext) { $context->getLibChannel()->wait_for_pending_acks_returns($timeoutInSeconds); } elseif ($context instanceof AmqpExtContext) { @@ -315,7 +316,7 @@ private function awaitPublisherConfirmsSynchronously(array $publishRecords, Inte $deliveryResult = (new AmqpPendingDelivery( $context, $publishRecords, - $this->asyncPublishingTimeout, + $this->confirmationTimeout, $this->channelName, $confirmations, $prePublishConfirmationsEpoch, @@ -325,7 +326,7 @@ private function awaitPublisherConfirmsSynchronously(array $publishRecords, Inte return; } - if ($this->asyncPublishing) { + if ($this->nonBlockingConfirmation) { throw PublishingFailedException::withFailedDeliveries($deliveryResult->getFailedDeliveries()); } diff --git a/packages/Amqp/src/AmqpOutboundChannelAdapterBuilder.php b/packages/Amqp/src/AmqpOutboundChannelAdapterBuilder.php index 002b5a577..696ec8d14 100644 --- a/packages/Amqp/src/AmqpOutboundChannelAdapterBuilder.php +++ b/packages/Amqp/src/AmqpOutboundChannelAdapterBuilder.php @@ -7,7 +7,7 @@ use Ecotone\Amqp\Transaction\AmqpTransactionInterceptor; use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapterBuilder; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -23,7 +23,7 @@ class AmqpOutboundChannelAdapterBuilder extends EnqueueOutboundChannelAdapterBui { private const DEFAULT_PERSISTENT_MODE = true; - public const DEFAULT_ASYNC_PUBLISHING_TIMEOUT = 12000; + public const DEFAULT_CONFIRMATION_TIMEOUT = 12000; private string $amqpConnectionFactoryReferenceName; private string $defaultRoutingKey = ''; @@ -34,9 +34,10 @@ class AmqpOutboundChannelAdapterBuilder extends EnqueueOutboundChannelAdapterBui private array $staticHeadersToAdd = []; private bool $publisherConfirms = true; private ?string $delayStrategyReferenceName = null; - private bool $asyncPublishing = false; - private int $asyncPublishingTimeout = self::DEFAULT_ASYNC_PUBLISHING_TIMEOUT; - private ?string $asyncPublishingChannelName = null; + private bool $batchPublishing = false; + private bool $nonBlockingConfirmation = false; + private int $confirmationTimeout = self::DEFAULT_CONFIRMATION_TIMEOUT; + private ?string $publishingChannelName = null; private function __construct(string $exchangeName, string $amqpConnectionFactoryReferenceName) { @@ -74,25 +75,31 @@ public function withPublisherConfirms(bool $publisherConfirms): self return $this; } - public function withAsyncPublishing(bool $enabled = true, ?int $timeoutInMilliseconds = null): self + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $enabled; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; } - public function withAsyncPublishingChannelName(string $channelName): self + public function isNonBlockingConfirmationEnabled(): bool { - $this->asyncPublishingChannelName = $channelName; + return $this->nonBlockingConfirmation; + } + + public function withPublishingChannelName(string $channelName): self + { + $this->publishingChannelName = $channelName; return $this; } @@ -149,11 +156,13 @@ public function withDefaultPersistentMode(bool $isPersistent): self public function compile(MessagingContainerBuilder $builder): Definition { - if ($this->asyncPublishing) { + if ($this->batchPublishing || $this->nonBlockingConfirmation) { if (! $builder->getServiceConfiguration()->isRunningForEnterprise()) { - throw LicensingException::create('Asynchronous publishing is available only with Ecotone Enterprise licence.'); + throw LicensingException::create('High Throughput Publishing is available only with Ecotone Enterprise licence.'); } - Assert::isTrue($this->publisherConfirms, 'Asynchronous publishing requires publisher confirms to be enabled.'); + } + if ($this->nonBlockingConfirmation) { + Assert::isTrue($this->publisherConfirms, 'Non blocking confirmation requires publisher confirms to be enabled.'); } $connectionFactory = new Definition(CachedConnectionFactory::class, [ @@ -186,11 +195,12 @@ public function compile(MessagingContainerBuilder $builder): Definition $outboundMessageConverter, new Reference(ConversionService::REFERENCE_NAME), Reference::to(AmqpTransactionInterceptor::class), - new Reference(AsyncPublishingRegistry::class), + new Reference(PendingDeliveryRegistry::class), $this->delayStrategyReferenceName ? new Reference($this->delayStrategyReferenceName) : null, - $this->asyncPublishing, - $this->asyncPublishingTimeout, - $this->asyncPublishingChannelName ?? $this->exchangeName, + $this->batchPublishing, + $this->nonBlockingConfirmation, + $this->confirmationTimeout, + $this->publishingChannelName ?? $this->exchangeName, ]); } } diff --git a/packages/Amqp/src/AmqpPendingDelivery.php b/packages/Amqp/src/AmqpPendingDelivery.php index 4c010a9a0..f53de7f92 100644 --- a/packages/Amqp/src/AmqpPendingDelivery.php +++ b/packages/Amqp/src/AmqpPendingDelivery.php @@ -4,9 +4,9 @@ namespace Ecotone\Amqp; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery; use Ecotone\Messaging\Message; use Enqueue\AmqpExt\AmqpContext as AmqpExtContext; use Enqueue\AmqpLib\AmqpContext as AmqpLibContext; diff --git a/packages/Amqp/src/Publisher/AmqpMessagePublisherConfiguration.php b/packages/Amqp/src/Publisher/AmqpMessagePublisherConfiguration.php index e3fa17a60..3588fcb17 100644 --- a/packages/Amqp/src/Publisher/AmqpMessagePublisherConfiguration.php +++ b/packages/Amqp/src/Publisher/AmqpMessagePublisherConfiguration.php @@ -53,9 +53,11 @@ class AmqpMessagePublisherConfiguration */ private $defaultPersistentDelivery = true; - private bool $asyncPublishing = false; + private bool $batchPublishing = false; - private ?int $asyncPublishingTimeout = null; + private bool $nonBlockingConfirmation = false; + + private ?int $confirmationTimeout = null; private function __construct(string $connectionReference, string $exchangeName, ?string $outputDefaultConversionMediaType, string $referenceName) { @@ -155,25 +157,36 @@ public function getDefaultPersistentDelivery(): bool return $this->defaultPersistentDelivery; } - public function withAsyncPublishing(bool $enabled = true, ?int $timeoutInMilliseconds = null): AmqpMessagePublisherConfiguration + /** + * @param bool $batchPublishing coalesces published Messages into a single publisher confirms round trip + * @param bool $nonBlockingConfirmation publishes without waiting for publisher confirms, which are awaited on Future::resolve() or before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await publisher confirms before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): AmqpMessagePublisherConfiguration { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $enabled; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool + { + return $this->batchPublishing; + } + + public function isNonBlockingConfirmationEnabled(): bool { - return $this->asyncPublishing; + return $this->nonBlockingConfirmation; } - public function getAsyncPublishingTimeout(): ?int + public function getConfirmationTimeout(): ?int { - return $this->asyncPublishingTimeout; + return $this->confirmationTimeout; } /** diff --git a/packages/Amqp/src/Publisher/AmqpMessagePublisherModule.php b/packages/Amqp/src/Publisher/AmqpMessagePublisherModule.php index b08a4bd6d..7206126b5 100644 --- a/packages/Amqp/src/Publisher/AmqpMessagePublisherModule.php +++ b/packages/Amqp/src/Publisher/AmqpMessagePublisherModule.php @@ -5,7 +5,7 @@ use Ecotone\Amqp\AmqpOutboundChannelAdapterBuilder; use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver; use Ecotone\Messaging\Config\Configuration; @@ -89,11 +89,11 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO ->withDefaultRoutingKey($amqpPublisher->getDefaultRoutingKey()) ->withRoutingKeyFromHeader($amqpPublisher->getRoutingKeyFromHeader()) ->withDefaultConversionMediaType($mediaType) - ->withAsyncPublishing($amqpPublisher->isAsyncPublishingEnabled(), $amqpPublisher->getAsyncPublishingTimeout()) - ->withAsyncPublishingChannelName($amqpPublisher->getReferenceName()) + ->withHighThroughputPublishing($amqpPublisher->isBatchPublishingEnabled(), $amqpPublisher->isNonBlockingConfirmationEnabled(), $amqpPublisher->getConfirmationTimeout()) + ->withPublishingChannelName($amqpPublisher->getReferenceName()) ); - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $amqpPublisher->getReferenceName(), $amqpPublisher->isAsyncPublishingEnabled()); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $amqpPublisher->getReferenceName(), $amqpPublisher->isNonBlockingConfirmationEnabled()); } } diff --git a/packages/Amqp/tests/Fixture/AsyncPublishing/OrderWasPlaced.php b/packages/Amqp/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php similarity index 72% rename from packages/Amqp/tests/Fixture/AsyncPublishing/OrderWasPlaced.php rename to packages/Amqp/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php index 1787fab3b..18d46c8fc 100644 --- a/packages/Amqp/tests/Fixture/AsyncPublishing/OrderWasPlaced.php +++ b/packages/Amqp/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Amqp\Fixture\AsyncPublishing; +namespace Test\Ecotone\Amqp\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Amqp/tests/Integration/AsyncPublishingReliabilityTest.php b/packages/Amqp/tests/Integration/HighThroughputPublishingReliabilityTest.php similarity index 85% rename from packages/Amqp/tests/Integration/AsyncPublishingReliabilityTest.php rename to packages/Amqp/tests/Integration/HighThroughputPublishingReliabilityTest.php index c566efbfa..8c0b921e4 100644 --- a/packages/Amqp/tests/Integration/AsyncPublishingReliabilityTest.php +++ b/packages/Amqp/tests/Integration/HighThroughputPublishingReliabilityTest.php @@ -8,7 +8,7 @@ use Ecotone\Amqp\Publisher\AmqpMessagePublisherConfiguration; use Ecotone\Lite\EcotoneLite; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\MessagePublisher; @@ -23,7 +23,7 @@ * licence Apache-2.0 * @internal */ -final class AsyncPublishingReliabilityTest extends AmqpMessagingTestCase +final class HighThroughputPublishingReliabilityTest extends AmqpMessagingTestCase { public function test_nacked_message_fails_delivery_confirmation_over_amqp_lib(): void { @@ -33,7 +33,7 @@ public function test_nacked_message_fails_delivery_confirmation_over_amqp_lib(): $this->expectException(PublishingFailedException::class); - $publisher->asyncPublish( + $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first message fills the queue') ->append('second message overflows and gets nacked') @@ -48,7 +48,7 @@ public function test_nacked_message_fails_delivery_confirmation_over_amqp_ext(): $this->expectException(PublishingFailedException::class); - $publisher->asyncPublish( + $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first message fills the queue') ->append('second message overflows and gets nacked') @@ -92,7 +92,7 @@ public function test_unroutable_message_fails_delivery_confirmation_over_amqp_li $this->expectException(PublishingFailedException::class); - $publisher->asyncPublish('order that routes nowhere')->resolve(); + $publisher->publishDeferred('order that routes nowhere')->resolve(); } public function test_unroutable_message_fails_delivery_confirmation_over_amqp_ext(): void @@ -102,7 +102,7 @@ public function test_unroutable_message_fails_delivery_confirmation_over_amqp_ex $this->expectException(PublishingFailedException::class); - $publisher->asyncPublish('order that routes nowhere')->resolve(); + $publisher->publishDeferred('order that routes nowhere')->resolve(); } public function test_each_future_reports_outcome_of_its_own_message_when_sharing_channel(): void @@ -111,8 +111,8 @@ public function test_each_future_reports_outcome_of_its_own_message_when_sharing $queueName = $this->declareQueue($libConnectionFactory); $publisher = $this->bootstrapPublisherWithRoutingKeyFromHeader($libConnectionFactory); - $routableFuture = $publisher->asyncPublish('order that reaches the queue', metadata: ['routingKey' => $queueName]); - $unroutableFuture = $publisher->asyncPublish('order that routes nowhere', metadata: ['routingKey' => Uuid::v7()->toRfc4122()]); + $routableFuture = $publisher->publishDeferred('order that reaches the queue', metadata: ['routingKey' => $queueName]); + $unroutableFuture = $publisher->publishDeferred('order that routes nowhere', metadata: ['routingKey' => Uuid::v7()->toRfc4122()]); $routableFuture->resolve(); @@ -127,8 +127,8 @@ public function test_each_future_reports_outcome_of_its_own_message_when_sharing $queueName = $this->declareQueue($extConnectionFactory); $publisher = $this->bootstrapPublisherWithRoutingKeyFromHeader($extConnectionFactory); - $routableFuture = $publisher->asyncPublish('order that reaches the queue', metadata: ['routingKey' => $queueName]); - $unroutableFuture = $publisher->asyncPublish('order that routes nowhere', metadata: ['routingKey' => Uuid::v7()->toRfc4122()]); + $routableFuture = $publisher->publishDeferred('order that reaches the queue', metadata: ['routingKey' => $queueName]); + $unroutableFuture = $publisher->publishDeferred('order that routes nowhere', metadata: ['routingKey' => Uuid::v7()->toRfc4122()]); $routableFuture->resolve(); @@ -144,10 +144,10 @@ public function test_nack_arriving_during_other_future_await_fails_only_nacked_f $overflowQueue = $this->declareQueueRejectingOverflow($libConnectionFactory); $publisher = $this->bootstrapPublisherWithRoutingKeyFromHeader($libConnectionFactory); - $publisher->asyncPublish('filler order', metadata: ['routingKey' => $overflowQueue])->resolve(); + $publisher->publishDeferred('filler order', metadata: ['routingKey' => $overflowQueue])->resolve(); - $deliveredFuture = $publisher->asyncPublish('delivered order', metadata: ['routingKey' => $normalQueue]); - $nackedFuture = $publisher->asyncPublish('nacked order', metadata: ['routingKey' => $overflowQueue]); + $deliveredFuture = $publisher->publishDeferred('delivered order', metadata: ['routingKey' => $normalQueue]); + $nackedFuture = $publisher->publishDeferred('nacked order', metadata: ['routingKey' => $overflowQueue]); $deliveredFuture->resolve(); @@ -163,10 +163,10 @@ public function test_nack_arriving_during_other_future_await_fails_only_nacked_f $overflowQueue = $this->declareQueueRejectingOverflow($extConnectionFactory); $publisher = $this->bootstrapPublisherWithRoutingKeyFromHeader($extConnectionFactory); - $publisher->asyncPublish('filler order', metadata: ['routingKey' => $overflowQueue])->resolve(); + $publisher->publishDeferred('filler order', metadata: ['routingKey' => $overflowQueue])->resolve(); - $deliveredFuture = $publisher->asyncPublish('delivered order', metadata: ['routingKey' => $normalQueue]); - $nackedFuture = $publisher->asyncPublish('nacked order', metadata: ['routingKey' => $overflowQueue]); + $deliveredFuture = $publisher->publishDeferred('delivered order', metadata: ['routingKey' => $normalQueue]); + $nackedFuture = $publisher->publishDeferred('nacked order', metadata: ['routingKey' => $overflowQueue]); $deliveredFuture->resolve(); @@ -181,7 +181,7 @@ public function test_only_failing_message_from_batch_is_reported_with_per_messag $queueName = $this->declareQueue($libConnectionFactory); $publisher = $this->bootstrapPublisherWithRoutingKeyFromHeader($libConnectionFactory); - $future = $publisher->asyncPublish( + $future = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first delivered order', ['routingKey' => $queueName]) ->append('order that routes nowhere', ['routingKey' => Uuid::v7()->toRfc4122()]) @@ -240,7 +240,7 @@ private function bootstrapPublisherWithRoutingKeyFromHeader(AmqpLibConnection|Am AmqpMessagePublisherConfiguration::create() ->withAutoDeclareQueueOnSend(false) ->withRoutingKeyFromHeader('routingKey') - ->withAsyncPublishing(timeoutInMilliseconds: 3000), + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 3000), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); @@ -273,7 +273,7 @@ private function bootstrapPublisher(AmqpLibConnection|AmqpConnectionFactory $con AmqpMessagePublisherConfiguration::create() ->withAutoDeclareQueueOnSend(false) ->withDefaultRoutingKey($queueName) - ->withAsyncPublishing(timeoutInMilliseconds: 3000), + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 3000), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); diff --git a/packages/Amqp/tests/Integration/AsyncPublishingTest.php b/packages/Amqp/tests/Integration/HighThroughputPublishingTest.php similarity index 71% rename from packages/Amqp/tests/Integration/AsyncPublishingTest.php rename to packages/Amqp/tests/Integration/HighThroughputPublishingTest.php index 7b131ccb9..a45e53bf2 100644 --- a/packages/Amqp/tests/Integration/AsyncPublishingTest.php +++ b/packages/Amqp/tests/Integration/HighThroughputPublishingTest.php @@ -11,7 +11,8 @@ use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; +use Ecotone\Messaging\Config\ConfigurationException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; @@ -30,15 +31,15 @@ use Enqueue\AmqpLib\AmqpConnectionFactory as AmqpLibConnection; use Symfony\Component\Uid\Uuid; use Test\Ecotone\Amqp\AmqpMessagingTestCase; -use Test\Ecotone\Amqp\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Amqp\Fixture\HighThroughputPublishing\OrderWasPlaced; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingTest extends AmqpMessagingTestCase +final class HighThroughputPublishingTest extends AmqpMessagingTestCase { - public function test_multiple_messages_published_asynchronously_from_command_handler_are_delivered(): void + public function test_multiple_messages_published_from_command_handler_are_delivered(): void { $channelName = Uuid::v7()->toRfc4122(); $orderService = $this->createOrderService($channelName); @@ -53,7 +54,7 @@ public function test_multiple_messages_published_asynchronously_from_command_han $this->assertCount(3, $messaging->sendQueryWithRouting('order.getReceived')); } - public function test_async_publishing_requires_enterprise_licence(): void + public function test_high_throughput_publishing_requires_enterprise_licence(): void { $channelName = Uuid::v7()->toRfc4122(); $orderService = $this->createOrderService($channelName); @@ -63,7 +64,7 @@ public function test_async_publishing_requires_enterprise_licence(): void $this->bootstrapEcotone($channelName, $orderService, licenceKey: null); } - public function test_async_publishing_via_message_publisher_requires_enterprise_licence(): void + public function test_high_throughput_publishing_via_message_publisher_requires_enterprise_licence(): void { $this->expectException(LicensingException::class); @@ -75,12 +76,12 @@ public function test_async_publishing_via_message_publisher_requires_enterprise_ ->withExtensionObjects([ AmqpMessagePublisherConfiguration::create() ->withDefaultRoutingKey(Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), ); } - public function test_async_publish_on_publisher_without_async_configuration_throws_before_publishing(): void + public function test_publish_deferred_on_publisher_without_non_blocking_confirmation_throws_before_publishing(): void { $queueName = Uuid::v7()->toRfc4122(); $messaging = EcotoneLite::bootstrapFlowTesting( @@ -99,7 +100,7 @@ public function test_async_publish_on_publisher_without_async_configuration_thro $publishFailed = false; try { - $publisher->asyncPublish('order that must not be published'); + $publisher->publishDeferred('order that must not be published'); } catch (PublishingFailedException) { $publishFailed = true; } @@ -108,7 +109,7 @@ public function test_async_publish_on_publisher_without_async_configuration_thro $this->assertNull($messaging->getMessageChannel('verificationChannel')->receiveWithTimeout(PollingMetadata::create('verification')->setFixedRateInMilliseconds(200))); } - public function test_message_publisher_async_publish_confirms_delivery_on_future_resolve(): void + public function test_publish_deferred_confirms_delivery_on_future_resolve(): void { $queueName = Uuid::v7()->toRfc4122(); $context = self::getRabbitConnectionFactory()->createContext(); @@ -122,14 +123,14 @@ public function test_message_publisher_async_publish_confirms_delivery_on_future AmqpMessagePublisherConfiguration::create() ->withAutoDeclareQueueOnSend(true) ->withDefaultRoutingKey($queueName) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); $publisher = $messaging->getGateway(MessagePublisher::class); - $singleFuture = $publisher->asyncPublish('single order'); - $batchFuture = $publisher->asyncPublish( + $singleFuture = $publisher->publishDeferred('single order'); + $batchFuture = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => '5']) @@ -172,7 +173,7 @@ public function test_delayed_entry_of_published_batch_is_delivered_after_delay() $messaging = $this->bootstrapPublisherWithVerificationChannel($queueName); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('immediate order') ->append('delayed order', [MessageHeaders::DELIVERY_DELAY => 2000]) @@ -191,7 +192,7 @@ public function test_expired_entry_of_published_batch_is_not_delivered(): void $messaging = $this->bootstrapPublisherWithVerificationChannel($queueName); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('expiring order', [MessageHeaders::TIME_TO_LIVE => 100]) ->append('kept order') @@ -235,6 +236,74 @@ public function test_batch_published_over_amqp_lib_connection_is_delivered(): vo ); } + public function test_batch_publishing_without_non_blocking_confirmation_delivers_batch_and_confirms_before_returning(): void + { + $queueName = Uuid::v7()->toRfc4122(); + $messaging = $this->bootstrapPublisherWithVerificationChannel( + $queueName, + publisherConfigurator: fn (AmqpMessagePublisherConfiguration $configuration): AmqpMessagePublisherConfiguration => $configuration->withHighThroughputPublishing(nonBlockingConfirmation: false), + ); + $publisher = $messaging->getGateway(MessagePublisher::class); + + $publisher->convertAndSend( + BatchMessage::constructEmpty() + ->append('first order') + ->append('second order') + ); + + $verificationChannel = $messaging->getMessageChannel('verificationChannel'); + $receivedPayloads = [ + $verificationChannel->receive()->getPayload(), + $verificationChannel->receive()->getPayload(), + ]; + sort($receivedPayloads); + $this->assertSame(['first order', 'second order'], $receivedPayloads); + } + + public function test_batch_publishing_without_non_blocking_confirmation_does_not_offer_publish_deferred(): void + { + $queueName = Uuid::v7()->toRfc4122(); + $messaging = $this->bootstrapPublisherWithVerificationChannel( + $queueName, + publisherConfigurator: fn (AmqpMessagePublisherConfiguration $configuration): AmqpMessagePublisherConfiguration => $configuration->withHighThroughputPublishing(nonBlockingConfirmation: false), + ); + + $this->expectException(PublishingFailedException::class); + $this->expectExceptionMessageMatches('/not configured for non blocking confirmation/'); + + $messaging->getGateway(MessagePublisher::class)->publishDeferred('order that must not be published'); + } + + public function test_non_blocking_confirmation_without_batch_publishing_rejects_batch_message(): void + { + $queueName = Uuid::v7()->toRfc4122(); + $messaging = $this->bootstrapPublisherWithVerificationChannel( + $queueName, + publisherConfigurator: fn (AmqpMessagePublisherConfiguration $configuration): AmqpMessagePublisherConfiguration => $configuration->withHighThroughputPublishing(batchPublishing: false), + ); + + $this->expectException(ConfigurationException::class); + $this->expectExceptionMessageMatches('/requires batch publishing to be enabled/'); + + $messaging->getGateway(MessagePublisher::class)->convertAndSend( + BatchMessage::constructEmpty()->append('first order') + ); + } + + public function test_non_blocking_confirmation_without_batch_publishing_still_defers_single_message_confirmation(): void + { + $queueName = Uuid::v7()->toRfc4122(); + $messaging = $this->bootstrapPublisherWithVerificationChannel( + $queueName, + publisherConfigurator: fn (AmqpMessagePublisherConfiguration $configuration): AmqpMessagePublisherConfiguration => $configuration->withHighThroughputPublishing(batchPublishing: false), + ); + + $future = $messaging->getGateway(MessagePublisher::class)->publishDeferred('single order'); + + $this->assertNull($future->resolve()); + $this->assertSame('single order', $messaging->getMessageChannel('verificationChannel')->receive()->getPayload()); + } + private function createOrderService(string $channelName): object { return new class ($channelName) { @@ -268,8 +337,14 @@ public function getReceived(): array }; } - private function bootstrapPublisherWithVerificationChannel(string $queueName, ?object $commandHandler = null): FlowTestSupport + private function bootstrapPublisherWithVerificationChannel(string $queueName, ?object $commandHandler = null, ?callable $publisherConfigurator = null): FlowTestSupport { + $publisherConfiguration = AmqpMessagePublisherConfiguration::create() + ->withDefaultRoutingKey($queueName); + $publisherConfiguration = $publisherConfigurator === null + ? $publisherConfiguration->withHighThroughputPublishing() + : $publisherConfigurator($publisherConfiguration); + return EcotoneLite::bootstrapFlowTesting( $commandHandler === null ? [] : [$commandHandler::class], $commandHandler === null @@ -278,9 +353,7 @@ private function bootstrapPublisherWithVerificationChannel(string $queueName, ?o ServiceConfiguration::createWithDefaults() ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE, ModulePackageList::AMQP_PACKAGE])) ->withExtensionObjects([ - AmqpMessagePublisherConfiguration::create() - ->withDefaultRoutingKey($queueName) - ->withAsyncPublishing(), + $publisherConfiguration, AmqpBackedMessageChannelBuilder::create('verificationChannel', queueName: $queueName), ]), licenceKey: LicenceTesting::VALID_LICENCE, diff --git a/packages/Dbal/src/Configuration/DbalMessagePublisherConfiguration.php b/packages/Dbal/src/Configuration/DbalMessagePublisherConfiguration.php index 16711c084..72a10b67f 100644 --- a/packages/Dbal/src/Configuration/DbalMessagePublisherConfiguration.php +++ b/packages/Dbal/src/Configuration/DbalMessagePublisherConfiguration.php @@ -35,7 +35,7 @@ class DbalMessagePublisherConfiguration */ private $queueName; - private bool $asyncPublishing = false; + private bool $batchPublishing = false; private function __construct(string $connectionReference, string $queueName, ?string $outputDefaultConversionMediaType, string $referenceName) { @@ -122,15 +122,19 @@ public function getReferenceName(): string return $this->referenceName; } - public function withAsyncPublishing(bool $asyncPublishing = true): self + /** + * Coalesces published Messages into a single multi row insert. + * Non blocking confirmation is not offered here, as the insert blocks until the database confirms it. + */ + public function withHighThroughputPublishing(): self { - $this->asyncPublishing = $asyncPublishing; + $this->batchPublishing = true; return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; } } diff --git a/packages/Dbal/src/Configuration/DbalPublisherModule.php b/packages/Dbal/src/Configuration/DbalPublisherModule.php index 0446c6123..54650359e 100644 --- a/packages/Dbal/src/Configuration/DbalPublisherModule.php +++ b/packages/Dbal/src/Configuration/DbalPublisherModule.php @@ -8,7 +8,7 @@ use Ecotone\Dbal\DbalBackedMessageChannelBuilder; use Ecotone\Dbal\DbalOutboundChannelAdapterBuilder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver; use Ecotone\Messaging\Config\Configuration; @@ -117,10 +117,10 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO ->withAutoDeclareOnSend($dbalPublisher->isAutoDeclareQueueOnSend()) ->withHeaderMapper($dbalPublisher->getHeaderMapper()) ->withDefaultConversionMediaType($mediaType) - ->withAsyncPublishing($dbalPublisher->isAsyncPublishingEnabled()) + ->withBatchPublishing($dbalPublisher->isBatchPublishingEnabled()) ); - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $dbalPublisher->getReferenceName(), $dbalPublisher->isAsyncPublishingEnabled()); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $dbalPublisher->getReferenceName(), nonBlockingConfirmationEnabled: false); } } diff --git a/packages/Dbal/src/DbalBackedMessageChannelBuilder.php b/packages/Dbal/src/DbalBackedMessageChannelBuilder.php index e08f96825..639e49d20 100644 --- a/packages/Dbal/src/DbalBackedMessageChannelBuilder.php +++ b/packages/Dbal/src/DbalBackedMessageChannelBuilder.php @@ -31,16 +31,20 @@ public static function create(string $channelName, string $connectionReferenceNa return new self($channelName, $connectionReferenceName); } - public function withHighThroughputPublishing(bool $enabled = true): self + /** + * Coalesces published Messages into a single multi row insert. + * Non blocking confirmation is not offered here, as the insert blocks until the database confirms it. + */ + public function withHighThroughputPublishing(): self { - $this->getDbalOutboundChannelAdapter()->withAsyncPublishing($enabled); + $this->getDbalOutboundChannelAdapter()->withBatchPublishing(); return $this; } protected function supportsBatchMessages(): bool { - return $this->getDbalOutboundChannelAdapter()->isAsyncPublishingEnabled(); + return $this->getDbalOutboundChannelAdapter()->isBatchPublishingEnabled(); } private function getDbalOutboundChannelAdapter(): DbalOutboundChannelAdapterBuilder diff --git a/packages/Dbal/src/DbalOutboundChannelAdapter.php b/packages/Dbal/src/DbalOutboundChannelAdapter.php index 7dc72077a..78baf93a4 100644 --- a/packages/Dbal/src/DbalOutboundChannelAdapter.php +++ b/packages/Dbal/src/DbalOutboundChannelAdapter.php @@ -8,7 +8,6 @@ use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapter; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Conversion\ConversionService; use Ecotone\Messaging\Message; @@ -30,8 +29,7 @@ public function __construct( OutboundMessageConverter $outboundMessageConverter, ConversionService $conversionService, private EnqueueTableManager $tableManager, - AsyncPublishingRegistry $asyncPublishingRegistry, - bool $asyncPublishing = false, + bool $batchPublishing = false, ) { parent::__construct( $connectionFactory, @@ -39,8 +37,7 @@ public function __construct( $autoDeclare, $outboundMessageConverter, $conversionService, - $asyncPublishingRegistry, - $asyncPublishing, + $batchPublishing, $this->queueName, ); } diff --git a/packages/Dbal/src/DbalOutboundChannelAdapterBuilder.php b/packages/Dbal/src/DbalOutboundChannelAdapterBuilder.php index 7d01a237b..2bc5d363e 100644 --- a/packages/Dbal/src/DbalOutboundChannelAdapterBuilder.php +++ b/packages/Dbal/src/DbalOutboundChannelAdapterBuilder.php @@ -5,7 +5,6 @@ use Ecotone\Dbal\Database\EnqueueTableManager; use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapterBuilder; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -28,7 +27,7 @@ class DbalOutboundChannelAdapterBuilder extends EnqueueOutboundChannelAdapterBui */ private $connectionFactoryReferenceName; - private bool $asyncPublishing = false; + private bool $batchPublishing = false; private function __construct(string $queueName, string $connectionFactoryReferenceName) { @@ -42,22 +41,22 @@ public static function create(string $queueName, string $connectionFactoryRefere return new self($queueName, $connectionFactoryReferenceName); } - public function withAsyncPublishing(bool $asyncPublishing = true): self + public function withBatchPublishing(bool $batchPublishing = true): self { - $this->asyncPublishing = $asyncPublishing; + $this->batchPublishing = $batchPublishing; return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; } public function compile(MessagingContainerBuilder $builder): Definition { - if ($this->asyncPublishing && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { - throw LicensingException::create('Asynchronous publishing is available only with Ecotone Enterprise licence.'); + if ($this->batchPublishing && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { + throw LicensingException::create('High Throughput Publishing is available only with Ecotone Enterprise licence.'); } $connectionFactory = new Definition(CachedConnectionFactory::class, [ @@ -82,8 +81,7 @@ public function compile(MessagingContainerBuilder $builder): Definition $outboundMessageConverter, new Reference(ConversionService::REFERENCE_NAME), new Reference(EnqueueTableManager::class), - new Reference(AsyncPublishingRegistry::class), - $this->asyncPublishing, + $this->batchPublishing, ]); } } diff --git a/packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannel.php b/packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannel.php similarity index 77% rename from packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannel.php rename to packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannel.php index ccfad310a..fdc8c3e84 100644 --- a/packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannel.php +++ b/packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannel.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Test\Ecotone\Dbal\Fixture\AsyncPublishing; +namespace Test\Ecotone\Dbal\Fixture\HighThroughputPublishing; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; use Ecotone\Messaging\Channel\BatchSupportingMessageChannel; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Endpoint\PollingMetadata; use Ecotone\Messaging\Message; use Ecotone\Messaging\PollableChannel; @@ -16,14 +16,14 @@ /** * licence Apache-2.0 */ -final class AsyncPublishingTestChannel implements PollableChannel, BatchSupportingMessageChannel +final class HighThroughputTestChannel implements PollableChannel, BatchSupportingMessageChannel { /** @var Message[] */ private array $queue = []; public function __construct( private string $channelName, - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, private ?string $deliveryFailureReason = null, ) { } @@ -44,7 +44,7 @@ public function send(Message $message): void $pendingDelivery = new TestPendingDelivery($message, $this->channelName, $this->deliveryFailureReason); - if (! $this->asyncPublishingRegistry->isScopeActive()) { + if (! $this->pendingDeliveryRegistry->isScopeActive()) { $deliveryResult = $pendingDelivery->awaitDelivery(); if (! $deliveryResult->isSuccessful()) { throw PublishingFailedException::withFailedDeliveries($deliveryResult->getFailedDeliveries()); @@ -53,7 +53,7 @@ public function send(Message $message): void return; } - $this->asyncPublishingRegistry->register($this->channelName, $pendingDelivery); + $this->pendingDeliveryRegistry->register($this->channelName, $pendingDelivery); } public function receive(): ?Message diff --git a/packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannelBuilder.php b/packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannelBuilder.php similarity index 74% rename from packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannelBuilder.php rename to packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannelBuilder.php index 56d0d2877..a41cda829 100644 --- a/packages/Dbal/tests/Fixture/AsyncPublishing/AsyncPublishingTestChannelBuilder.php +++ b/packages/Dbal/tests/Fixture/HighThroughputPublishing/HighThroughputTestChannelBuilder.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Test\Ecotone\Dbal\Fixture\AsyncPublishing; +namespace Test\Ecotone\Dbal\Fixture\HighThroughputPublishing; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\MessageChannelBuilder; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -13,7 +13,7 @@ /** * licence Apache-2.0 */ -final class AsyncPublishingTestChannelBuilder implements MessageChannelBuilder +final class HighThroughputTestChannelBuilder implements MessageChannelBuilder { private function __construct(private string $channelName, private ?string $deliveryFailureReason) { @@ -41,9 +41,9 @@ public function isStreamingChannel(): bool public function compile(MessagingContainerBuilder $builder): Definition|Reference { - return new Definition(AsyncPublishingTestChannel::class, [ + return new Definition(HighThroughputTestChannel::class, [ $this->channelName, - new Reference(AsyncPublishingRegistry::class), + new Reference(PendingDeliveryRegistry::class), $this->deliveryFailureReason, ]); } diff --git a/packages/Redis/tests/Fixture/AsyncPublishing/OrderWasPlaced.php b/packages/Dbal/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php similarity index 72% rename from packages/Redis/tests/Fixture/AsyncPublishing/OrderWasPlaced.php rename to packages/Dbal/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php index 1bb32fa51..391d29c7e 100644 --- a/packages/Redis/tests/Fixture/AsyncPublishing/OrderWasPlaced.php +++ b/packages/Dbal/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Redis\Fixture\AsyncPublishing; +namespace Test\Ecotone\Dbal\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Dbal/tests/Fixture/AsyncPublishing/TestPendingDelivery.php b/packages/Dbal/tests/Fixture/HighThroughputPublishing/TestPendingDelivery.php similarity index 75% rename from packages/Dbal/tests/Fixture/AsyncPublishing/TestPendingDelivery.php rename to packages/Dbal/tests/Fixture/HighThroughputPublishing/TestPendingDelivery.php index 388a3102e..2c391b16b 100644 --- a/packages/Dbal/tests/Fixture/AsyncPublishing/TestPendingDelivery.php +++ b/packages/Dbal/tests/Fixture/HighThroughputPublishing/TestPendingDelivery.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Test\Ecotone\Dbal\Fixture\AsyncPublishing; +namespace Test\Ecotone\Dbal\Fixture\HighThroughputPublishing; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery; use Ecotone\Messaging\Message; /** diff --git a/packages/Dbal/tests/Integration/AsyncPublishingTest.php b/packages/Dbal/tests/Integration/HighThroughputPublishingTest.php similarity index 81% rename from packages/Dbal/tests/Integration/AsyncPublishingTest.php rename to packages/Dbal/tests/Integration/HighThroughputPublishingTest.php index bdb4b0fbf..f97e60d6d 100644 --- a/packages/Dbal/tests/Integration/AsyncPublishingTest.php +++ b/packages/Dbal/tests/Integration/HighThroughputPublishingTest.php @@ -11,7 +11,7 @@ use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ConfigurationException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; @@ -32,15 +32,15 @@ use Interop\Queue\Exception\Exception; use Symfony\Component\Uid\Uuid; use Test\Ecotone\Dbal\DbalMessagingTestCase; -use Test\Ecotone\Dbal\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Dbal\Fixture\HighThroughputPublishing\OrderWasPlaced; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingTest extends DbalMessagingTestCase +final class HighThroughputPublishingTest extends DbalMessagingTestCase { - public function test_multiple_messages_published_asynchronously_from_command_handler_are_delivered(): void + public function test_multiple_messages_published_from_command_handler_are_delivered(): void { $orderService = $this->createOrderService(); $messaging = $this->bootstrapEcotoneWithChannel($orderService, LicenceTesting::VALID_LICENCE); @@ -57,7 +57,7 @@ public function test_multiple_messages_published_asynchronously_from_command_han ); } - public function test_async_publishing_requires_enterprise_licence(): void + public function test_high_throughput_publishing_requires_enterprise_licence(): void { $orderService = $this->createOrderService(); @@ -66,7 +66,7 @@ public function test_async_publishing_requires_enterprise_licence(): void $this->bootstrapEcotoneWithChannel($orderService, licenceKey: null); } - public function test_async_publishing_via_message_publisher_requires_enterprise_licence(): void + public function test_high_throughput_publishing_via_message_publisher_requires_enterprise_licence(): void { $this->expectException(LicensingException::class); @@ -77,44 +77,36 @@ public function test_async_publishing_via_message_publisher_requires_enterprise_ ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::DBAL_PACKAGE])) ->withExtensionObjects([ DbalMessagePublisherConfiguration::create(MessagePublisher::class, Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), ); } - public function test_async_publish_on_publisher_without_async_configuration_throws_before_publishing(): void + public function test_publish_deferred_is_not_available_as_confirmation_can_not_be_deferred(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: false); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publishFailed = false; - try { - $publisher->asyncPublish('order that must not be published'); - } catch (PublishingFailedException) { - $publishFailed = true; - } + $this->expectException(PublishingFailedException::class); + $this->expectExceptionMessageMatches('/not configured for non blocking confirmation/'); - $this->assertTrue($publishFailed); - $this->assertNull($messaging->getMessageChannel($queueName)->receive()); + $publisher->publishDeferred('order that must not be published'); } - public function test_message_publisher_async_publish_confirms_delivery_on_future_resolve(): void + public function test_message_publisher_delivers_single_and_batched_messages(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $singleFuture = $publisher->asyncPublish('single order'); - $batchFuture = $publisher->asyncPublish( + $publisher->send('single order'); + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => '5']) ); - $this->assertNull($singleFuture->resolve()); - $this->assertNull($batchFuture->resolve()); - $receivedPayloads = []; while ($message = $messaging->getMessageChannel($queueName)->receive()) { $receivedPayloads[] = $message->getPayload(); @@ -123,10 +115,10 @@ public function test_message_publisher_async_publish_confirms_delivery_on_future $this->assertSame(['first order', 'second order', 'single order'], $receivedPayloads); } - public function test_sending_batch_message_over_channel_without_async_publishing_throws(): void + public function test_sending_batch_message_over_channel_without_batch_publishing_throws(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: false); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: false); $this->expectException(ConfigurationException::class); @@ -135,10 +127,10 @@ public function test_sending_batch_message_over_channel_without_async_publishing ); } - public function test_sending_batch_message_via_publisher_without_async_publishing_throws(): void + public function test_sending_batch_message_via_publisher_without_batch_publishing_throws(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: false); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: false); $publisher = $messaging->getGateway(MessagePublisher::class); $this->expectException(ConfigurationException::class); @@ -167,7 +159,7 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE, ModulePackageList::DBAL_PACKAGE])) ->withExtensionObjects([ DbalMessagePublisherConfiguration::create(MessagePublisher::class, $queueName) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), DbalBackedMessageChannelBuilder::create($queueName), ]), licenceKey: LicenceTesting::VALID_LICENCE, @@ -186,14 +178,14 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess public function test_delayed_entry_of_published_batch_is_delivered_after_delay(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('immediate order') ->append('delayed order', [MessageHeaders::DELIVERY_DELAY => 3000]) - )->resolve(); + ); $channel = $messaging->getMessageChannel($queueName); $this->assertSame('immediate order', $channel->receive()->getPayload()); @@ -205,14 +197,14 @@ public function test_delayed_entry_of_published_batch_is_delivered_after_delay() public function test_expired_entry_of_published_batch_is_not_delivered(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('expiring order', [MessageHeaders::TIME_TO_LIVE => 1000]) ->append('kept order') - )->resolve(); + ); sleep(2); @@ -224,15 +216,15 @@ public function test_expired_entry_of_published_batch_is_not_delivered(): void public function test_publishing_after_queue_table_is_dropped_throws(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish('first order')->resolve(); + $publisher->send('first order'); $this->getConnection()->executeStatement('DROP TABLE enqueue'); $this->expectException(Exception::class); - $publisher->asyncPublish('order published into missing table'); + $publisher->send('order published into missing table'); } private function receiveWithDeadline(PollableChannel $channel, int $deadlineInSeconds): ?Message @@ -292,11 +284,11 @@ private function bootstrapEcotoneWithChannel(object $orderService, ?string $lice ); } - private function bootstrapPublisher(string $queueName, bool $asyncPublishing): FlowTestSupport + private function bootstrapPublisher(string $queueName, bool $highThroughputPublishing): FlowTestSupport { $publisherConfiguration = DbalMessagePublisherConfiguration::create(MessagePublisher::class, $queueName); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } return EcotoneLite::bootstrapFlowTesting( diff --git a/packages/Dbal/tests/Integration/AsyncPublishingTransactionTest.php b/packages/Dbal/tests/Integration/HighThroughputPublishingTransactionTest.php similarity index 87% rename from packages/Dbal/tests/Integration/AsyncPublishingTransactionTest.php rename to packages/Dbal/tests/Integration/HighThroughputPublishingTransactionTest.php index 71b962ad1..43b925f40 100644 --- a/packages/Dbal/tests/Integration/AsyncPublishingTransactionTest.php +++ b/packages/Dbal/tests/Integration/HighThroughputPublishingTransactionTest.php @@ -7,7 +7,7 @@ use Ecotone\Dbal\Configuration\DbalConfiguration; use Ecotone\Lite\EcotoneLite; use Ecotone\Lite\Test\FlowTestSupport; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\GlobalPollableChannelConfiguration; use Ecotone\Messaging\Channel\SimpleMessageChannelBuilder; use Ecotone\Messaging\Config\ModulePackageList; @@ -16,7 +16,7 @@ use Ecotone\Modelling\AggregateNotFoundException; use Enqueue\Dbal\DbalConnectionFactory; use Test\Ecotone\Dbal\DbalMessagingTestCase; -use Test\Ecotone\Dbal\Fixture\AsyncPublishing\AsyncPublishingTestChannelBuilder; +use Test\Ecotone\Dbal\Fixture\HighThroughputPublishing\HighThroughputTestChannelBuilder; use Test\Ecotone\Dbal\Fixture\ORM\AsynchronousEventHandler\NotificationService; use Test\Ecotone\Dbal\Fixture\ORM\Person\Person; use Test\Ecotone\Dbal\Fixture\ORM\Person\RegisterPerson; @@ -25,12 +25,12 @@ * licence Apache-2.0 * @internal */ -final class AsyncPublishingTransactionTest extends DbalMessagingTestCase +final class HighThroughputPublishingTransactionTest extends DbalMessagingTestCase { public function test_successful_delivery_confirmations_commit_database_transaction(): void { $ecotoneLite = $this->bootstrapEcotone( - [AsyncPublishingTestChannelBuilder::create('notifications')], + [HighThroughputTestChannelBuilder::create('notifications')], [] ); @@ -43,7 +43,7 @@ public function test_successful_delivery_confirmations_commit_database_transacti public function test_failed_delivery_confirmation_rolls_back_database_transaction(): void { $ecotoneLite = $this->bootstrapEcotone( - [AsyncPublishingTestChannelBuilder::create('notifications', deliveryFailureReason: 'broker not available')], + [HighThroughputTestChannelBuilder::create('notifications', deliveryFailureReason: 'broker not available')], [] ); @@ -64,7 +64,7 @@ public function test_failed_delivery_routed_to_error_channel_commits_database_tr { $ecotoneLite = $this->bootstrapEcotone( [ - AsyncPublishingTestChannelBuilder::create('notifications', deliveryFailureReason: 'broker not available'), + HighThroughputTestChannelBuilder::create('notifications', deliveryFailureReason: 'broker not available'), SimpleMessageChannelBuilder::createQueueChannel('failure_channel'), ], [GlobalPollableChannelConfiguration::createWithDefaults()->withErrorChannel('failure_channel')] diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/ConfirmedDelivery.php b/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/ConfirmedDelivery.php deleted file mode 100644 index f354fb9aa..000000000 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/ConfirmedDelivery.php +++ /dev/null @@ -1,21 +0,0 @@ -registerGatewayBuilder( - GatewayProxyBuilder::create($publisherReferenceName, MessagePublisher::class, 'asyncPublish', $asyncPublishRequestChannel) + GatewayProxyBuilder::create($publisherReferenceName, MessagePublisher::class, 'publishDeferred', $publishDeferredRequestChannel) ->withParameterConverters([ GatewayPayloadBuilder::create('data'), GatewayHeaderBuilder::create('sourceMediaType', MessageHeaders::CONTENT_TYPE), GatewayHeadersBuilder::create('metadata'), ]) ) - ->registerMessageChannel(SimpleMessageChannelBuilder::createDirectMessageChannel($asyncPublishRequestChannel)) + ->registerMessageChannel(SimpleMessageChannelBuilder::createDirectMessageChannel($publishDeferredRequestChannel)) ->registerMessageHandler( ServiceActivatorBuilder::createWithDefinition( - new Definition(AsyncPublishingGateway::class, [ + new Definition(DeferredPublishingGateway::class, [ $publisherReferenceName, - $asyncPublishingEnabled, + $nonBlockingConfirmationEnabled, new Reference(ConfiguredMessagingSystem::class), - new Reference(AsyncPublishingRegistry::class), + new Reference(PendingDeliveryRegistry::class), ]), 'publish' ) - ->withInputChannelName($asyncPublishRequestChannel) - ->withEndpointId($asyncPublishRequestChannel . '.endpoint') + ->withInputChannelName($publishDeferredRequestChannel) + ->withEndpointId($publishDeferredRequestChannel . '.endpoint') ); } } diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/Config/AsyncPublishingModule.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/Config/DeliveryConfirmationModule.php similarity index 80% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/Config/AsyncPublishingModule.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/Config/DeliveryConfirmationModule.php index 55e089750..1c1488167 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/Config/AsyncPublishingModule.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/Config/DeliveryConfirmationModule.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing\Config; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation\Config; use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\AsynchronousRunningEndpoint; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingWaiterInterceptor; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryConfirmationInterceptor; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\PollableChannel\GlobalPollableChannelConfiguration; use Ecotone\Messaging\Channel\PollableChannel\PollableChannelConfiguration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; @@ -30,7 +30,7 @@ /** * licence Enterprise */ -final class AsyncPublishingModule extends NoExternalConfigurationModule implements AnnotationModule +final class DeliveryConfirmationModule extends NoExternalConfigurationModule implements AnnotationModule { public static function create(AnnotationFinder $annotationRegistrationService, InterfaceToCallRegistry $interfaceToCallRegistry): static { @@ -46,9 +46,9 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO } $messagingConfiguration->registerServiceDefinition( - AsyncPublishingWaiterInterceptor::class, - new Definition(AsyncPublishingWaiterInterceptor::class, [ - new Reference(AsyncPublishingRegistry::class), + DeliveryConfirmationInterceptor::class, + new Definition(DeliveryConfirmationInterceptor::class, [ + new Reference(PendingDeliveryRegistry::class), $errorChannels, $globalPollableChannelConfiguration->getErrorChannelName(), new Reference(ErrorChannelService::class), @@ -57,9 +57,9 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO ); $messagingConfiguration->registerAroundMethodInterceptor( AroundInterceptorBuilder::create( - AsyncPublishingWaiterInterceptor::class, - $interfaceToCallRegistry->getFor(AsyncPublishingWaiterInterceptor::class, 'await'), - Precedence::ASYNC_PUBLISHING_AWAIT_PRECEDENCE, + DeliveryConfirmationInterceptor::class, + $interfaceToCallRegistry->getFor(DeliveryConfirmationInterceptor::class, 'await'), + Precedence::DELIVERY_CONFIRMATION_PRECEDENCE, CommandBus::class . '||' . AsynchronousRunningEndpoint::class, ) ); diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingGateway.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeferredPublishingGateway.php similarity index 68% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingGateway.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeferredPublishingGateway.php index d89645210..516002561 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingGateway.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeferredPublishingGateway.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\BatchMessage; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; @@ -14,20 +14,20 @@ /** * licence Enterprise */ -final class AsyncPublishingGateway +final class DeferredPublishingGateway { public function __construct( private string $publisherReference, - private bool $asyncPublishingEnabled, + private bool $nonBlockingConfirmationEnabled, private ConfiguredMessagingSystem $configuredMessagingSystem, - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, ) { } public function publish(Message $message): Future { - if (! $this->asyncPublishingEnabled) { - throw PublishingFailedException::publisherNotConfiguredForAsyncPublishing($this->publisherReference); + if (! $this->nonBlockingConfirmationEnabled) { + throw PublishingFailedException::publisherNotConfiguredForDeferredPublishing($this->publisherReference); } $payload = $message->getPayload(); @@ -35,13 +35,13 @@ public function publish(Message $message): Future return DeliveryFuture::forPendingDeliveries([]); } - $scopeWasActive = $this->asyncPublishingRegistry->isScopeActive(); + $scopeWasActive = $this->pendingDeliveryRegistry->isScopeActive(); if (! $scopeWasActive) { - $this->asyncPublishingRegistry->openScope(); + $this->pendingDeliveryRegistry->openScope(); } try { - $collectionPoint = $this->asyncPublishingRegistry->collectionPoint(); + $collectionPoint = $this->pendingDeliveryRegistry->collectionPoint(); $this->configuredMessagingSystem->getMessageChannelByName($this->publisherReference)->send( MessageBuilder::fromMessage($message) @@ -50,18 +50,18 @@ public function publish(Message $message): Future ->build() ); - $pendingDeliveries = $this->asyncPublishingRegistry->registeredSince($collectionPoint); + $pendingDeliveries = $this->pendingDeliveryRegistry->registeredSince($collectionPoint); if (! $scopeWasActive) { - $this->asyncPublishingRegistry->markRegisteredSinceAsPublisherOwned($collectionPoint); + $this->pendingDeliveryRegistry->markRegisteredSinceAsPublisherOwned($collectionPoint); } } finally { if (! $scopeWasActive) { - $this->asyncPublishingRegistry->closeScope(); + $this->pendingDeliveryRegistry->closeScope(); } } if ($pendingDeliveries === []) { - throw PublishingFailedException::publisherNotConfiguredForAsyncPublishing($this->publisherReference); + throw PublishingFailedException::publisherNotConfiguredForDeferredPublishing($this->publisherReference); } return DeliveryFuture::forPendingDeliveries($pendingDeliveries); diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingWaiterInterceptor.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryConfirmationInterceptor.php similarity index 86% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingWaiterInterceptor.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryConfirmationInterceptor.php index 6de7aca01..5bb702329 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingWaiterInterceptor.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryConfirmationInterceptor.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\BatchMessage; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; @@ -14,13 +14,13 @@ /** * licence Enterprise */ -final class AsyncPublishingWaiterInterceptor +final class DeliveryConfirmationInterceptor { /** * @param array $errorChannels */ public function __construct( - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, private array $errorChannels, private ?string $globalErrorChannelName, private ErrorChannelService $errorChannelService, @@ -30,26 +30,26 @@ public function __construct( public function await(MethodInvocation $methodInvocation): mixed { - if ($this->asyncPublishingRegistry->isScopeActive()) { + if ($this->pendingDeliveryRegistry->isScopeActive()) { return $methodInvocation->proceed(); } - $this->asyncPublishingRegistry->openScope(); + $this->pendingDeliveryRegistry->openScope(); try { $result = $methodInvocation->proceed(); - $deliveryResult = $this->asyncPublishingRegistry->awaitAll(); + $deliveryResult = $this->pendingDeliveryRegistry->awaitAll(); if (! $deliveryResult->isSuccessful()) { $unroutedFailedDeliveries = $this->handleFailedDeliveries($deliveryResult->getFailedDeliveries()); - $errorChannelDeliveryResult = $this->asyncPublishingRegistry->awaitAll(); + $errorChannelDeliveryResult = $this->pendingDeliveryRegistry->awaitAll(); $remainingFailedDeliveries = array_merge($unroutedFailedDeliveries, $errorChannelDeliveryResult->getFailedDeliveries()); if ($remainingFailedDeliveries !== []) { throw PublishingFailedException::withFailedDeliveries($remainingFailedDeliveries); } } } finally { - $this->asyncPublishingRegistry->closeScope(); + $this->pendingDeliveryRegistry->closeScope(); } return $result; diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryFuture.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryFuture.php similarity index 97% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryFuture.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryFuture.php index e73f0dfae..202ca1456 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryFuture.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryFuture.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\Future; use Throwable; diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryResult.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryResult.php similarity index 93% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryResult.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryResult.php index 42542df25..79a469645 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/DeliveryResult.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/DeliveryResult.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; /** * licence Enterprise diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/FailedDelivery.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/FailedDelivery.php similarity index 90% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/FailedDelivery.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/FailedDelivery.php index d62fdbbb0..5d1de86aa 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/FailedDelivery.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/FailedDelivery.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\Message; diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PendingDelivery.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDelivery.php similarity index 76% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PendingDelivery.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDelivery.php index 444704b82..d4a3fec86 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PendingDelivery.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDelivery.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; /** * licence Enterprise diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingRegistry.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDeliveryRegistry.php similarity index 98% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingRegistry.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDeliveryRegistry.php index bedf05ed7..8005c2551 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/AsyncPublishingRegistry.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PendingDeliveryRegistry.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\Handler\Logger\LoggingGateway; use Throwable; @@ -11,7 +11,7 @@ /** * licence Enterprise */ -final class AsyncPublishingRegistry +final class PendingDeliveryRegistry { /** @var array */ private array $pendingDeliveries = []; diff --git a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PublishingFailedException.php b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PublishingFailedException.php similarity index 76% rename from packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PublishingFailedException.php rename to packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PublishingFailedException.php index ddd84d828..5d0cac009 100644 --- a/packages/Ecotone/src/Messaging/Channel/AsyncPublishing/PublishingFailedException.php +++ b/packages/Ecotone/src/Messaging/Channel/DeliveryConfirmation/PublishingFailedException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ecotone\Messaging\Channel\AsyncPublishing; +namespace Ecotone\Messaging\Channel\DeliveryConfirmation; use Ecotone\Messaging\MessagingException; @@ -34,10 +34,10 @@ public static function withFailedDeliveries(array $failedDeliveries): self /** @var FailedDelivery[] */ private array $failedDeliveries = []; - public static function publisherNotConfiguredForAsyncPublishing(string $publisherReference): self + public static function publisherNotConfiguredForDeferredPublishing(string $publisherReference): self { return new self(sprintf( - 'Message Publisher `%s` is not configured for asynchronous publishing. Enable async publishing on the publisher configuration to make use of asyncPublish.', + 'Message Publisher `%s` is not configured for non blocking confirmation. Call withHighThroughputPublishing() on the publisher configuration to make use of publishDeferred.', $publisherReference, )); } diff --git a/packages/Ecotone/src/Messaging/Channel/PollableChannel/SendRetries/SendRetryChannelInterceptor.php b/packages/Ecotone/src/Messaging/Channel/PollableChannel/SendRetries/SendRetryChannelInterceptor.php index bf27eb9fb..6268dae56 100644 --- a/packages/Ecotone/src/Messaging/Channel/PollableChannel/SendRetries/SendRetryChannelInterceptor.php +++ b/packages/Ecotone/src/Messaging/Channel/PollableChannel/SendRetries/SendRetryChannelInterceptor.php @@ -6,9 +6,9 @@ use Ecotone\Messaging\BatchMessage; use Ecotone\Messaging\Channel\AbstractChannelInterceptor; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; use Ecotone\Messaging\Channel\ChannelInterceptor; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; use Ecotone\Messaging\Handler\Gateway\ErrorChannelService; use Ecotone\Messaging\Handler\Recoverability\RetryTemplate; diff --git a/packages/Ecotone/src/Messaging/Config/Container/Compiler/RegisterSingletonMessagingServices.php b/packages/Ecotone/src/Messaging/Config/Container/Compiler/RegisterSingletonMessagingServices.php index b5ffd4eae..74779df18 100644 --- a/packages/Ecotone/src/Messaging/Config/Container/Compiler/RegisterSingletonMessagingServices.php +++ b/packages/Ecotone/src/Messaging/Config/Container/Compiler/RegisterSingletonMessagingServices.php @@ -3,7 +3,7 @@ namespace Ecotone\Messaging\Config\Container\Compiler; use Ecotone\EventSourcing\Mapping\EventMapper; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; use Ecotone\Messaging\Config\Container\ChannelResolverWithContainer; use Ecotone\Messaging\Config\Container\ContainerBuilder; @@ -67,7 +67,7 @@ public function process(ContainerBuilder $builder): void $this->registerDefault($builder, ConfiguredMessagingSystem::class, new Definition(MessagingSystemContainer::class, [new Reference(ContainerInterface::class), [], []])); $this->registerDefault($builder, EventMapper::class, new Definition(EventMapper::class, factory: 'createEmpty')); $this->registerDefault($builder, LicenceDecider::class, new Definition(LicenceDecider::class, [$this->serviceConfiguration->isRunningForEnterprise()])); - $this->registerDefault($builder, AsyncPublishingRegistry::class, new Definition(AsyncPublishingRegistry::class, [new Reference(LoggingGateway::class)])); + $this->registerDefault($builder, PendingDeliveryRegistry::class, new Definition(PendingDeliveryRegistry::class, [new Reference(LoggingGateway::class)])); } private function registerDefault(ContainerBuilder $builder, string $id, Definition|Reference $definition): void diff --git a/packages/Ecotone/src/Messaging/Config/ModuleClassList.php b/packages/Ecotone/src/Messaging/Config/ModuleClassList.php index 227e45b5e..89aaa39cb 100644 --- a/packages/Ecotone/src/Messaging/Config/ModuleClassList.php +++ b/packages/Ecotone/src/Messaging/Config/ModuleClassList.php @@ -27,8 +27,8 @@ use Ecotone\Kafka\Configuration\KafkaModule; use Ecotone\Laravel\Config\LaravelConnectionModule; use Ecotone\Lite\Test\Configuration\EcotoneTestSupportModule; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishingModule; use Ecotone\Messaging\Channel\Collector\Config\CollectorModule; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeliveryConfirmationModule; use Ecotone\Messaging\Channel\DynamicChannel\Config\DynamicMessageChannelModule; use Ecotone\Messaging\Channel\Manager\ChannelSetupModule; use Ecotone\Messaging\Channel\PollableChannel\InMemory\InMemoryQueueAcknowledgeModule; @@ -110,7 +110,7 @@ class ModuleClassList RouterModule::class, ScheduledModule::class, CollectorModule::class, - AsyncPublishingModule::class, + DeliveryConfirmationModule::class, ChannelSetupModule::class, SerializerModule::class, ServiceActivatorModule::class, diff --git a/packages/Ecotone/src/Messaging/MessagePublisher.php b/packages/Ecotone/src/Messaging/MessagePublisher.php index 2ffec2626..bcec3eb21 100644 --- a/packages/Ecotone/src/Messaging/MessagePublisher.php +++ b/packages/Ecotone/src/Messaging/MessagePublisher.php @@ -19,5 +19,10 @@ public function convertAndSend(object|array $data): void; public function convertAndSendWithMetadata(object|array $data, array $metadata): void; - public function asyncPublish(mixed $data, string $sourceMediaType = MediaType::APPLICATION_X_PHP, array $metadata = []): Future; + /** + * Publishes without blocking on delivery confirmation. + * Requires High Throughput Publishing with non blocking confirmation enabled on the publisher. + * Confirmation is awaited on Future::resolve(), or at the latest when the surrounding Command Bus or asynchronous endpoint finishes. + */ + public function publishDeferred(mixed $data, string $sourceMediaType = MediaType::APPLICATION_X_PHP, array $metadata = []): Future; } diff --git a/packages/Ecotone/src/Messaging/Precedence.php b/packages/Ecotone/src/Messaging/Precedence.php index 2744a9945..e6f3992d9 100644 --- a/packages/Ecotone/src/Messaging/Precedence.php +++ b/packages/Ecotone/src/Messaging/Precedence.php @@ -61,12 +61,12 @@ interface Precedence /** * Awaits delivery confirmations of asynchronously published messages before transaction commits */ - public const ASYNC_PUBLISHING_AWAIT_PRECEDENCE = self::DATABASE_TRANSACTION_PRECEDENCE + 1; + public const DELIVERY_CONFIRMATION_PRECEDENCE = self::DATABASE_TRANSACTION_PRECEDENCE + 1; /** * Collects messages to be sent to asynchronous channels. */ - public const COLLECTOR_SENDER_PRECEDENCE = self::ASYNC_PUBLISHING_AWAIT_PRECEDENCE + 1; + public const COLLECTOR_SENDER_PRECEDENCE = self::DELIVERY_CONFIRMATION_PRECEDENCE + 1; public const DATABASE_OBJECT_MANAGER_PRECEDENCE = self::COLLECTOR_SENDER_PRECEDENCE + 1; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderForwarder.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderForwarder.php similarity index 90% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderForwarder.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderForwarder.php index 6dadbde4e..7904215a9 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderForwarder.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderForwarder.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Modelling\Attribute\EventHandler; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderSubscriber.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderSubscriber.php similarity index 90% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderSubscriber.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderSubscriber.php index 8926ce032..23b313207 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/AsyncOrderSubscriber.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/AsyncOrderSubscriber.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Modelling\Attribute\EventHandler; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionInterceptor.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionInterceptor.php similarity index 93% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionInterceptor.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionInterceptor.php index 01edb2d77..12de59c85 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionInterceptor.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionInterceptor.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Messaging\Handler\Processor\MethodInvoker\MethodInvocation; use Throwable; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionModule.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionModule.php similarity index 97% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionModule.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionModule.php index b241c01dd..30740203d 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/FakeTransactionModule.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/FakeTransactionModule.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\AsynchronousRunningEndpoint; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncOutboundAdapter.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputOutboundAdapter.php similarity index 88% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncOutboundAdapter.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputOutboundAdapter.php index 23767a13e..5334043c5 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncOutboundAdapter.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputOutboundAdapter.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Message; use Ecotone\Messaging\Support\MessageBuilder; /** * licence Apache-2.0 */ -final class InMemoryAsyncOutboundAdapter +final class InMemoryHighThroughputOutboundAdapter { /** @var Message[] */ private array $sentMessages = []; @@ -27,7 +27,7 @@ final class InMemoryAsyncOutboundAdapter private bool $registersPendingDeliveries = true; - public function handle(Message $message, #[Reference] AsyncPublishingRegistry $asyncPublishingRegistry): void + public function handle(Message $message, #[Reference] PendingDeliveryRegistry $pendingDeliveryRegistry): void { $this->sentMessages[] = $message; @@ -37,7 +37,7 @@ public function handle(Message $message, #[Reference] AsyncPublishingRegistry $a $pendingDelivery = new InMemoryPendingDelivery($message, $this->resolveFailureReason($message), failedMessages: $this->resolveFailedMessages($message)); $this->pendingDeliveries[] = $pendingDelivery; - $asyncPublishingRegistry->register(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE, $pendingDelivery); + $pendingDeliveryRegistry->register(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE, $pendingDelivery); } /** diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublisherModule.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublisherModule.php similarity index 74% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublisherModule.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublisherModule.php index 4e3a3ebb0..fa29183ca 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublisherModule.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublisherModule.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Channel\SimpleMessageChannelBuilder; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\NoExternalConfigurationModule; @@ -20,9 +20,9 @@ /** * licence Apache-2.0 */ -final class InMemoryAsyncPublisherModule extends NoExternalConfigurationModule implements AnnotationModule +final class InMemoryHighThroughputPublisherModule extends NoExternalConfigurationModule implements AnnotationModule { - public const PUBLISHER_REFERENCE = 'inMemoryAsyncPublisher'; + public const PUBLISHER_REFERENCE = 'inMemoryHighThroughputPublisher'; public static function create(AnnotationFinder $annotationRegistrationService, InterfaceToCallRegistry $interfaceToCallRegistry): static { @@ -33,12 +33,12 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO { $publisherReference = self::PUBLISHER_REFERENCE; - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $publisherReference); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $publisherReference); $messagingConfiguration ->registerMessageChannel(SimpleMessageChannelBuilder::createDirectMessageChannel($publisherReference)) ->registerMessageHandler( - ServiceActivatorBuilder::create(InMemoryAsyncOutboundAdapter::class, 'handle') + ServiceActivatorBuilder::create(InMemoryHighThroughputOutboundAdapter::class, 'handle') ->withInputChannelName($publisherReference) ->withEndpointId($publisherReference . '.handler') ); diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannel.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannel.php similarity index 88% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannel.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannel.php index f4ec8cb11..c2a8e1541 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannel.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannel.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; use Ecotone\Messaging\Channel\BatchSupportingMessageChannel; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Endpoint\PollingMetadata; use Ecotone\Messaging\Message; use Ecotone\Messaging\PollableChannel; @@ -16,7 +16,7 @@ /** * licence Apache-2.0 */ -final class InMemoryAsyncPublishingChannel implements PollableChannel, BatchSupportingMessageChannel +final class InMemoryHighThroughputPublishingChannel implements PollableChannel, BatchSupportingMessageChannel { /** @var Message[] */ private array $queue = []; @@ -27,7 +27,7 @@ final class InMemoryAsyncPublishingChannel implements PollableChannel, BatchSupp public function __construct( private string $channelName, - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, private OperationsLog $operationsLog, ) { } @@ -56,7 +56,7 @@ public function send(Message $message): void failedMessages: $this->resolveFailedMessages($message), ); - if (! $this->asyncPublishingRegistry->isScopeActive()) { + if (! $this->pendingDeliveryRegistry->isScopeActive()) { $deliveryResult = $pendingDelivery->awaitDelivery(); if (! $deliveryResult->isSuccessful()) { throw PublishingFailedException::withFailedDeliveries($deliveryResult->getFailedDeliveries()); @@ -65,7 +65,7 @@ public function send(Message $message): void return; } - $this->asyncPublishingRegistry->register($this->channelName, $pendingDelivery); + $this->pendingDeliveryRegistry->register($this->channelName, $pendingDelivery); } public function receive(): ?Message diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannelBuilder.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannelBuilder.php similarity index 71% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannelBuilder.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannelBuilder.php index edf9fbbf0..78d1aee32 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryAsyncPublishingChannelBuilder.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryHighThroughputPublishingChannelBuilder.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\MessageChannelBuilder; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -13,7 +13,7 @@ /** * licence Apache-2.0 */ -final class InMemoryAsyncPublishingChannelBuilder implements MessageChannelBuilder +final class InMemoryHighThroughputPublishingChannelBuilder implements MessageChannelBuilder { private function __construct(private string $channelName) { @@ -41,9 +41,9 @@ public function isStreamingChannel(): bool public function compile(MessagingContainerBuilder $builder): Definition|Reference { - return new Definition(InMemoryAsyncPublishingChannel::class, [ + return new Definition(InMemoryHighThroughputPublishingChannel::class, [ $this->channelName, - new Reference(AsyncPublishingRegistry::class), + new Reference(PendingDeliveryRegistry::class), new Reference(OperationsLog::class), ]); } diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryPendingDelivery.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryPendingDelivery.php similarity index 86% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryPendingDelivery.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryPendingDelivery.php index 03c733022..35ab5746a 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/InMemoryPendingDelivery.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/InMemoryPendingDelivery.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery; use Ecotone\Messaging\Message; use RuntimeException; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OperationsLog.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OperationsLog.php similarity index 85% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OperationsLog.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OperationsLog.php index 2813100db..d75e7ad37 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OperationsLog.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OperationsLog.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderRequestReceived.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderRequestReceived.php similarity index 71% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderRequestReceived.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderRequestReceived.php index 8d576a417..c6cc0351a 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderRequestReceived.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderRequestReceived.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderService.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderService.php similarity index 92% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderService.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderService.php index 454a16c5d..9f37a65ba 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderService.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderService.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; use Ecotone\Modelling\Attribute\CommandHandler; use Ecotone\Modelling\CommandBus; diff --git a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderWasPlaced.php b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderWasPlaced.php similarity index 70% rename from packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderWasPlaced.php rename to packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderWasPlaced.php index c10c29e5c..9b6392c6b 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AsyncPublishing/OrderWasPlaced.php +++ b/packages/Ecotone/tests/Messaging/Fixture/HighThroughputPublishing/OrderWasPlaced.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Fixture\AsyncPublishing; +namespace Test\Ecotone\Messaging\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishTest.php deleted file mode 100644 index 2272b17d5..000000000 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishTest.php +++ /dev/null @@ -1,112 +0,0 @@ -bootstrapPublisher($outboundAdapter); - - $future = $publisher->asyncPublish('order was placed'); - - $this->assertSame(['order was placed'], $outboundAdapter->getSentPayloads()); - $this->assertSame(0, $outboundAdapter->awaitedDeliveriesCount()); - - $future->resolve(); - - $this->assertSame(1, $outboundAdapter->awaitedDeliveriesCount()); - } - - public function test_resolving_future_twice_awaits_delivery_only_once(): void - { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); - $publisher = $this->bootstrapPublisher($outboundAdapter); - - $future = $publisher->asyncPublish('order was placed'); - $future->resolve(); - $future->resolve(); - - $this->assertSame(1, $outboundAdapter->totalAwaitCalls()); - } - - public function test_resolving_future_throws_when_delivery_failed(): void - { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); - $outboundAdapter->failDeliveriesWith('broker rejected message'); - $publisher = $this->bootstrapPublisher($outboundAdapter); - - $future = $publisher->asyncPublish('order was placed'); - - $this->expectException(PublishingFailedException::class); - - $future->resolve(); - } - - public function test_async_publish_on_synchronous_publisher_throws_clear_exception(): void - { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); - $outboundAdapter->actAsSynchronousPublisher(); - $publisher = $this->bootstrapPublisher($outboundAdapter); - - $this->expectException(PublishingFailedException::class); - $this->expectExceptionMessageMatches('/not configured for asynchronous publishing/'); - - $publisher->asyncPublish('order was placed'); - } - - public function test_metadata_passed_to_async_publish_lands_on_published_message(): void - { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); - $publisher = $this->bootstrapPublisher($outboundAdapter); - - $publisher->asyncPublish('order was placed', metadata: ['orderId' => '123']); - - $this->assertSame('123', $outboundAdapter->getSentMessages()[0]->getHeaders()->get('orderId')); - } - - public function test_flushing_unawaited_deliveries_awaits_only_unresolved_futures(): void - { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); - $ecotoneLite = $this->bootstrapEcotone($outboundAdapter); - $publisher = $ecotoneLite->getGateway(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE); - - $resolvedFuture = $publisher->asyncPublish('first order'); - $resolvedFuture->resolve(); - $publisher->asyncPublish('second order'); - - $ecotoneLite->getServiceFromContainer(AsyncPublishingRegistry::class)->flushUnawaitedDeliveries(); - - $this->assertSame(2, $outboundAdapter->awaitedDeliveriesCount()); - $this->assertSame(2, $outboundAdapter->totalAwaitCalls()); - } - - private function bootstrapPublisher(InMemoryAsyncOutboundAdapter $outboundAdapter): MessagePublisher - { - return $this->bootstrapEcotone($outboundAdapter)->getGateway(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE); - } - - private function bootstrapEcotone(InMemoryAsyncOutboundAdapter $outboundAdapter): FlowTestSupport - { - return EcotoneLite::bootstrapFlowTesting( - [InMemoryAsyncPublisherModule::class, InMemoryAsyncOutboundAdapter::class], - [$outboundAdapter], - ); - } -} diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/DeadLetterOfFailedBatchDeliveriesTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/DeadLetterOfFailedBatchDeliveriesTest.php similarity index 88% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/DeadLetterOfFailedBatchDeliveriesTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/DeadLetterOfFailedBatchDeliveriesTest.php index 426a3bf33..92b4ee755 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/DeadLetterOfFailedBatchDeliveriesTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/DeadLetterOfFailedBatchDeliveriesTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Messaging\BatchMessage; @@ -15,10 +15,10 @@ use Ecotone\Messaging\PollableChannel; use Ecotone\Messaging\Support\MessageBuilder; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderSubscriber; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublishingChannelBuilder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OperationsLog; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderService; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderSubscriber; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublishingChannelBuilder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OperationsLog; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderService; /** * licence Apache-2.0 @@ -57,7 +57,7 @@ public function test_failed_async_batch_deliveries_are_stored_as_separate_dead_l ->withErrorChannel('dead_letters'), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), SimpleMessageChannelBuilder::createQueueChannel('dead_letters'), ], ); @@ -87,7 +87,7 @@ private function bootstrapWithDeadLetterChannel(): \Ecotone\Lite\Test\FlowTestSu ->withErrorChannel('dead_letters'), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), SimpleMessageChannelBuilder::createQueueChannel('dead_letters'), ], ); diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingChannelTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingChannelTest.php similarity index 78% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingChannelTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingChannelTest.php index fc87f03da..1035afc01 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingChannelTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingChannelTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Lite\Test\FlowTestSupport; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\MessageChannelInterceptorAdapter; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderSubscriber; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\FakeTransactionModule; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublishingChannelBuilder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OperationsLog; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderService; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderSubscriber; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\FakeTransactionModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublishingChannelBuilder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OperationsLog; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderService; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingChannelTest extends TestCase +final class HighThroughputPublishingChannelTest extends TestCase { public function test_messages_published_from_command_handler_are_awaited_before_transaction_commits(): void { @@ -75,7 +75,7 @@ private function bootstrapEcotone(OperationsLog $operationsLog): FlowTestSupport [OrderService::class, AsyncOrderSubscriber::class, FakeTransactionModule::class], [new OrderService($operationsLog), new AsyncOrderSubscriber(), OperationsLog::class => $operationsLog], enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), ], ); } diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingCollectorMatrixTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingCollectorMatrixTest.php similarity index 77% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingCollectorMatrixTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingCollectorMatrixTest.php index 8841b2a1f..adeb46aae 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingCollectorMatrixTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingCollectorMatrixTest.php @@ -2,25 +2,25 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Lite\Test\FlowTestSupport; use Ecotone\Messaging\Channel\PollableChannel\PollableChannelConfiguration; use Ecotone\Messaging\Config\ServiceConfiguration; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderSubscriber; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\FakeTransactionModule; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublishingChannelBuilder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OperationsLog; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderService; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderSubscriber; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\FakeTransactionModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublishingChannelBuilder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OperationsLog; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderService; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderWasPlaced; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingCollectorMatrixTest extends TestCase +final class HighThroughputPublishingCollectorMatrixTest extends TestCase { public function test_with_collector_disabled_messages_fire_directly_during_handler_and_await_before_commit(): void { @@ -70,7 +70,7 @@ private function bootstrapEcotone(OperationsLog $operationsLog, bool $collectorE PollableChannelConfiguration::neverRetry('async_orders')->withCollector($collectorEnabled), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), ], ); } diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingReliabilityTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingReliabilityTest.php similarity index 71% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingReliabilityTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingReliabilityTest.php index dbabab250..5d4c3b3f9 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingReliabilityTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingReliabilityTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryFuture; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryFuture; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\MessageChannelInterceptorAdapter; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Handler\Logger\LoggingService; @@ -17,20 +17,20 @@ use Ecotone\Messaging\Support\MessageBuilder; use Ecotone\Modelling\Attribute\CommandHandler; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderSubscriber; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\FakeTransactionModule; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncOutboundAdapter; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublisherModule; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublishingChannelBuilder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryPendingDelivery; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OperationsLog; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderService; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderSubscriber; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\FakeTransactionModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputOutboundAdapter; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublisherModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublishingChannelBuilder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryPendingDelivery; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OperationsLog; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderService; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingReliabilityTest extends TestCase +final class HighThroughputPublishingReliabilityTest extends TestCase { public function test_future_throwing_during_await_keeps_reporting_failure_on_subsequent_resolves(): void { @@ -73,8 +73,8 @@ public function test_failed_deliveries_routed_to_async_error_channel_are_awaited \Ecotone\Messaging\Channel\PollableChannel\GlobalPollableChannelConfiguration::createWithDefaults()->withErrorChannel('failure_channel'), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), - InMemoryAsyncPublishingChannelBuilder::create('failure_channel'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('failure_channel'), ], ); $ordersChannel = $ecotoneLite->getMessageChannel('async_orders'); @@ -91,17 +91,17 @@ public function test_failed_deliveries_routed_to_async_error_channel_are_awaited public function test_future_of_delivery_flushed_by_backlog_limit_still_reports_failure(): void { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); $ecotoneLite = EcotoneLite::bootstrapFlowTesting( - [InMemoryAsyncPublisherModule::class, InMemoryAsyncOutboundAdapter::class], + [InMemoryHighThroughputPublisherModule::class, InMemoryHighThroughputOutboundAdapter::class], [$outboundAdapter], ); - $publisher = $ecotoneLite->getGateway(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE); + $publisher = $ecotoneLite->getGateway(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE); $outboundAdapter->failDeliveriesWith('broker down'); - $firstFuture = $publisher->asyncPublish('first order'); + $firstFuture = $publisher->publishDeferred('first order'); for ($messageNumber = 0; $messageNumber < 1300; $messageNumber++) { - $publisher->asyncPublish('unresolved order ' . $messageNumber); + $publisher->publishDeferred('unresolved order ' . $messageNumber); } $this->assertGreaterThan(0, $outboundAdapter->awaitedDeliveriesCount()); @@ -118,15 +118,15 @@ public function test_unawaited_deliveries_of_all_registries_are_flushed_on_scrip require $argv[1]; - $unawaitedDelivery = new class implements \Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery { + $unawaitedDelivery = new class implements \Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery { private bool $awaited = false; - public function awaitDelivery(): \Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult + public function awaitDelivery(): \Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult { $this->awaited = true; echo 'flushed;'; - return \Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult::successful(); + return \Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult::successful(); } public function isAwaited(): bool @@ -135,9 +135,9 @@ public function isAwaited(): bool } }; - $firstRegistry = new \Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry(new \Ecotone\Messaging\Handler\Logger\LoggingService()); + $firstRegistry = new \Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry(new \Ecotone\Messaging\Handler\Logger\LoggingService()); $firstRegistry->register('orders', $unawaitedDelivery); - $secondRegistry = new \Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry(new \Ecotone\Messaging\Handler\Logger\LoggingService()); + $secondRegistry = new \Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry(new \Ecotone\Messaging\Handler\Logger\LoggingService()); $secondRegistry->register('shipments', clone $unawaitedDelivery); echo 'script finished;'; @@ -166,7 +166,7 @@ private function nearestComposerAutoloadPath(): string public function test_shutdown_flush_continues_when_one_delivery_throws(): void { - $registry = new AsyncPublishingRegistry(new LoggingService()); + $registry = new PendingDeliveryRegistry(new LoggingService()); $throwingDelivery = new InMemoryPendingDelivery(MessageBuilder::withPayload('first order')->build(), throwOnAwait: true); $followingDelivery = new InMemoryPendingDelivery(MessageBuilder::withPayload('second order')->build()); $registry->register('orders', $throwingDelivery); @@ -179,7 +179,7 @@ public function test_shutdown_flush_continues_when_one_delivery_throws(): void public function test_closing_scope_awaits_deliveries_left_unawaited_when_execution_fails_before_await(): void { - $registry = new AsyncPublishingRegistry(new LoggingService()); + $registry = new PendingDeliveryRegistry(new LoggingService()); $registry->openScope(); $delivery = new InMemoryPendingDelivery(MessageBuilder::withPayload('order')->build()); $registry->register('orders', $delivery); @@ -206,18 +206,18 @@ public function test_future_awaits_remaining_deliveries_when_earlier_delivery_th public function test_one_failing_batch_among_many_published_in_command_handler_rolls_back_transaction(): void { $operationsLog = new OperationsLog(); - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); $commandHandler = new class () { #[CommandHandler('order.placeAllBatches')] - public function handle(string $order, #[Reference(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE)] MessagePublisher $publisher): void + public function handle(string $order, #[Reference(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE)] MessagePublisher $publisher): void { - $publisher->asyncPublish(BatchMessage::constructEmpty()->append($order . ' first batch')); - $publisher->asyncPublish(BatchMessage::constructEmpty()->append($order . ' poisoned batch')); - $publisher->asyncPublish(BatchMessage::constructEmpty()->append($order . ' third batch')); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append($order . ' first batch')); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append($order . ' poisoned batch')); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append($order . ' third batch')); } }; $ecotoneLite = EcotoneLite::bootstrapFlowTesting( - [$commandHandler::class, InMemoryAsyncPublisherModule::class, InMemoryAsyncOutboundAdapter::class, FakeTransactionModule::class], + [$commandHandler::class, InMemoryHighThroughputPublisherModule::class, InMemoryHighThroughputOutboundAdapter::class, FakeTransactionModule::class], [$commandHandler, $outboundAdapter, OperationsLog::class => $operationsLog], ); $outboundAdapter->failDeliveriesContaining('poisoned', 'broker rejected the batch'); @@ -237,15 +237,15 @@ public function handle(string $order, #[Reference(InMemoryAsyncPublisherModule:: public function test_unresolved_publisher_futures_above_backlog_limit_are_flushed(): void { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); $ecotoneLite = EcotoneLite::bootstrapFlowTesting( - [InMemoryAsyncPublisherModule::class, InMemoryAsyncOutboundAdapter::class], + [InMemoryHighThroughputPublisherModule::class, InMemoryHighThroughputOutboundAdapter::class], [$outboundAdapter], ); - $publisher = $ecotoneLite->getGateway(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE); + $publisher = $ecotoneLite->getGateway(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE); for ($messageNumber = 0; $messageNumber < 1300; $messageNumber++) { - $publisher->asyncPublish('unresolved order ' . $messageNumber); + $publisher->publishDeferred('unresolved order ' . $messageNumber); } $this->assertGreaterThan(0, $outboundAdapter->awaitedDeliveriesCount()); diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingScenariosTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingScenariosTest.php similarity index 87% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingScenariosTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingScenariosTest.php index e86d34619..d6a9c667f 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/AsyncPublishingScenariosTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/HighThroughputPublishingScenariosTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Lite\Test\FlowTestSupport; @@ -15,20 +15,20 @@ use Ecotone\Messaging\Handler\Recoverability\ErrorContext; use Ecotone\Messaging\MessageHeaders; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderForwarder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\AsyncOrderSubscriber; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\FakeTransactionModule; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublishingChannelBuilder; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OperationsLog; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderRequestReceived; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderService; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderForwarder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\AsyncOrderSubscriber; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\FakeTransactionModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublishingChannelBuilder; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OperationsLog; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderRequestReceived; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderService; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\OrderWasPlaced; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingScenariosTest extends TestCase +final class HighThroughputPublishingScenariosTest extends TestCase { public function test_nested_command_bus_awaits_deliveries_once_at_outermost_boundary(): void { @@ -107,7 +107,7 @@ public function test_failed_deliveries_are_routed_to_error_channel_and_transacti PollableChannelConfiguration::neverRetry('async_orders')->withCollector(false)->withErrorChannel('failure_channel'), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), SimpleMessageChannelBuilder::createQueueChannel('failure_channel'), ], ); @@ -139,7 +139,7 @@ public function test_only_failed_message_from_batch_is_routed_to_error_channel() PollableChannelConfiguration::neverRetry('async_orders')->withCollector(false)->withErrorChannel('failure_channel'), ]), enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), SimpleMessageChannelBuilder::createQueueChannel('failure_channel'), ], ); @@ -168,7 +168,7 @@ private function bootstrapEcotone(OperationsLog $operationsLog): FlowTestSupport OperationsLog::class => $operationsLog, ], enableAsynchronousProcessing: [ - InMemoryAsyncPublishingChannelBuilder::create('async_orders'), + InMemoryHighThroughputPublishingChannelBuilder::create('async_orders'), SimpleMessageChannelBuilder::createQueueChannel('incoming_orders'), ], ); diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishBatchTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredBatchTest.php similarity index 63% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishBatchTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredBatchTest.php index 6a815d99c..5ee02c7d3 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/MessagePublisherAsyncPublishBatchTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredBatchTest.php @@ -2,27 +2,27 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Lite\EcotoneLite; use Ecotone\Messaging\BatchMessage; use Ecotone\Messaging\MessagePublisher; use PHPUnit\Framework\TestCase; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncOutboundAdapter; -use Test\Ecotone\Messaging\Fixture\AsyncPublishing\InMemoryAsyncPublisherModule; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputOutboundAdapter; +use Test\Ecotone\Messaging\Fixture\HighThroughputPublishing\InMemoryHighThroughputPublisherModule; /** * licence Apache-2.0 * @internal */ -final class MessagePublisherAsyncPublishBatchTest extends TestCase +final class MessagePublisherPublishDeferredBatchTest extends TestCase { public function test_publishing_batch_delivers_whole_batch_with_single_pending_delivery(): void { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); $publisher = $this->bootstrapPublisher($outboundAdapter); - $future = $publisher->asyncPublish( + $future = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => 5]) @@ -47,22 +47,22 @@ public function test_publishing_batch_delivers_whole_batch_with_single_pending_d public function test_publishing_empty_batch_resolves_without_sending_anything(): void { - $outboundAdapter = new InMemoryAsyncOutboundAdapter(); + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); $publisher = $this->bootstrapPublisher($outboundAdapter); - $future = $publisher->asyncPublish(BatchMessage::constructEmpty()); + $future = $publisher->publishDeferred(BatchMessage::constructEmpty()); $future->resolve(); $this->assertCount(0, $outboundAdapter->getSentMessages()); } - private function bootstrapPublisher(InMemoryAsyncOutboundAdapter $outboundAdapter): MessagePublisher + private function bootstrapPublisher(InMemoryHighThroughputOutboundAdapter $outboundAdapter): MessagePublisher { $ecotoneLite = EcotoneLite::bootstrapFlowTesting( - [InMemoryAsyncPublisherModule::class, InMemoryAsyncOutboundAdapter::class], + [InMemoryHighThroughputPublisherModule::class, InMemoryHighThroughputOutboundAdapter::class], [$outboundAdapter], ); - return $ecotoneLite->getGateway(InMemoryAsyncPublisherModule::PUBLISHER_REFERENCE); + return $ecotoneLite->getGateway(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE); } } diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredTest.php new file mode 100644 index 000000000..717d2cea8 --- /dev/null +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/MessagePublisherPublishDeferredTest.php @@ -0,0 +1,112 @@ +bootstrapPublisher($outboundAdapter); + + $future = $publisher->publishDeferred('order was placed'); + + $this->assertSame(['order was placed'], $outboundAdapter->getSentPayloads()); + $this->assertSame(0, $outboundAdapter->awaitedDeliveriesCount()); + + $future->resolve(); + + $this->assertSame(1, $outboundAdapter->awaitedDeliveriesCount()); + } + + public function test_resolving_future_twice_awaits_delivery_only_once(): void + { + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); + $publisher = $this->bootstrapPublisher($outboundAdapter); + + $future = $publisher->publishDeferred('order was placed'); + $future->resolve(); + $future->resolve(); + + $this->assertSame(1, $outboundAdapter->totalAwaitCalls()); + } + + public function test_resolving_future_throws_when_delivery_failed(): void + { + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); + $outboundAdapter->failDeliveriesWith('broker rejected message'); + $publisher = $this->bootstrapPublisher($outboundAdapter); + + $future = $publisher->publishDeferred('order was placed'); + + $this->expectException(PublishingFailedException::class); + + $future->resolve(); + } + + public function test_publish_deferred_on_publisher_without_non_blocking_confirmation_throws_clear_exception(): void + { + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); + $outboundAdapter->actAsSynchronousPublisher(); + $publisher = $this->bootstrapPublisher($outboundAdapter); + + $this->expectException(PublishingFailedException::class); + $this->expectExceptionMessageMatches('/not configured for non blocking confirmation/'); + + $publisher->publishDeferred('order was placed'); + } + + public function test_metadata_passed_to_publish_deferred_lands_on_published_message(): void + { + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); + $publisher = $this->bootstrapPublisher($outboundAdapter); + + $publisher->publishDeferred('order was placed', metadata: ['orderId' => '123']); + + $this->assertSame('123', $outboundAdapter->getSentMessages()[0]->getHeaders()->get('orderId')); + } + + public function test_flushing_unawaited_deliveries_awaits_only_unresolved_futures(): void + { + $outboundAdapter = new InMemoryHighThroughputOutboundAdapter(); + $ecotoneLite = $this->bootstrapEcotone($outboundAdapter); + $publisher = $ecotoneLite->getGateway(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE); + + $resolvedFuture = $publisher->publishDeferred('first order'); + $resolvedFuture->resolve(); + $publisher->publishDeferred('second order'); + + $ecotoneLite->getServiceFromContainer(PendingDeliveryRegistry::class)->flushUnawaitedDeliveries(); + + $this->assertSame(2, $outboundAdapter->awaitedDeliveriesCount()); + $this->assertSame(2, $outboundAdapter->totalAwaitCalls()); + } + + private function bootstrapPublisher(InMemoryHighThroughputOutboundAdapter $outboundAdapter): MessagePublisher + { + return $this->bootstrapEcotone($outboundAdapter)->getGateway(InMemoryHighThroughputPublisherModule::PUBLISHER_REFERENCE); + } + + private function bootstrapEcotone(InMemoryHighThroughputOutboundAdapter $outboundAdapter): FlowTestSupport + { + return EcotoneLite::bootstrapFlowTesting( + [InMemoryHighThroughputPublisherModule::class, InMemoryHighThroughputOutboundAdapter::class], + [$outboundAdapter], + ); + } +} diff --git a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/SendRetryOfFailedBatchDeliveriesTest.php b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/SendRetryOfFailedBatchDeliveriesTest.php similarity index 94% rename from packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/SendRetryOfFailedBatchDeliveriesTest.php rename to packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/SendRetryOfFailedBatchDeliveriesTest.php index 79040a1eb..4b1e0198c 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Channel/AsyncPublishing/SendRetryOfFailedBatchDeliveriesTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Channel/DeliveryConfirmation/SendRetryOfFailedBatchDeliveriesTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Test\Ecotone\Messaging\Unit\Channel\AsyncPublishing; +namespace Test\Ecotone\Messaging\Unit\Channel\DeliveryConfirmation; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\SendRetries\SendRetryChannelInterceptor; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; diff --git a/packages/Ecotone/tests/Messaging/Unit/PrecedenceTest.php b/packages/Ecotone/tests/Messaging/Unit/PrecedenceTest.php index 87ebd5e9a..97a1a5faf 100644 --- a/packages/Ecotone/tests/Messaging/Unit/PrecedenceTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/PrecedenceTest.php @@ -15,8 +15,8 @@ final class PrecedenceTest extends TestCase { public function test_async_publishing_await_runs_inside_transaction_and_outside_collector_release(): void { - $this->assertGreaterThan(Precedence::DATABASE_TRANSACTION_PRECEDENCE, Precedence::ASYNC_PUBLISHING_AWAIT_PRECEDENCE); - $this->assertGreaterThan(Precedence::ASYNC_PUBLISHING_AWAIT_PRECEDENCE, Precedence::COLLECTOR_SENDER_PRECEDENCE); + $this->assertGreaterThan(Precedence::DATABASE_TRANSACTION_PRECEDENCE, Precedence::DELIVERY_CONFIRMATION_PRECEDENCE); + $this->assertGreaterThan(Precedence::DELIVERY_CONFIRMATION_PRECEDENCE, Precedence::COLLECTOR_SENDER_PRECEDENCE); $this->assertGreaterThan(Precedence::COLLECTOR_SENDER_PRECEDENCE, Precedence::DATABASE_OBJECT_MANAGER_PRECEDENCE); $this->assertGreaterThan(Precedence::DATABASE_OBJECT_MANAGER_PRECEDENCE, Precedence::LAZY_EVENT_PUBLICATION_PRECEDENCE); } diff --git a/packages/Enqueue/src/EnqueueOutboundChannelAdapter.php b/packages/Enqueue/src/EnqueueOutboundChannelAdapter.php index addbe7121..6479074c9 100644 --- a/packages/Enqueue/src/EnqueueOutboundChannelAdapter.php +++ b/packages/Enqueue/src/EnqueueOutboundChannelAdapter.php @@ -5,8 +5,6 @@ namespace Ecotone\Enqueue; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\ConfirmedDelivery; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessage; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\ConfigurationException; @@ -33,9 +31,8 @@ public function __construct( protected bool $autoDeclare, protected OutboundMessageConverter $outboundMessageConverter, private ConversionService $conversionService, - private AsyncPublishingRegistry $asyncPublishingRegistry, - private bool $asyncPublishing = false, - private string $asyncPublishingChannelName = '', + private bool $batchPublishing = false, + private string $channelName = '', ) { } @@ -43,8 +40,8 @@ abstract public function initialize(): void; public function handle(Message $message): void { - if ($message->getPayload() instanceof BatchMessage && ! $this->asyncPublishing) { - throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires async publishing to be enabled. Enable it with withAsyncPublishing(), available as part of Ecotone Enterprise.', $this->asyncPublishingChannelName)); + if ($message->getPayload() instanceof BatchMessage && ! $this->batchPublishing) { + throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires batch publishing to be enabled. Enable it with withHighThroughputPublishing(), available as part of Ecotone Enterprise.', $this->channelName)); } $context = $this->createOutboundContext(); @@ -54,8 +51,6 @@ public function handle(Message $message): void } else { $this->sendSingleMessage($message, $context); } - - $this->registerSynchronouslyConfirmedDelivery(); } protected function createOutboundContext(): Context @@ -73,15 +68,6 @@ protected function createOutboundContext(): Context return $context; } - protected function registerSynchronouslyConfirmedDelivery(): void - { - if (! $this->asyncPublishing || ! $this->asyncPublishingRegistry->isScopeActive()) { - return; - } - - $this->asyncPublishingRegistry->register($this->asyncPublishingChannelName, new ConfirmedDelivery()); - } - protected function handleBatch(BatchMessage $batchMessage, Context $context): void { foreach ($batchMessage->getEntries() as $entry) { diff --git a/packages/Kafka/src/Channel/KafkaMessageChannel.php b/packages/Kafka/src/Channel/KafkaMessageChannel.php index 99a5bca78..45ca35c8a 100644 --- a/packages/Kafka/src/Channel/KafkaMessageChannel.php +++ b/packages/Kafka/src/Channel/KafkaMessageChannel.php @@ -26,7 +26,7 @@ public function __construct( public function supportsBatchMessages(): bool { - return $this->outboundChannelAdapter->isAsyncPublishingEnabled(); + return $this->outboundChannelAdapter->isBatchPublishingEnabled(); } public function send(Message $message): void diff --git a/packages/Kafka/src/Channel/KafkaMessageChannelBuilder.php b/packages/Kafka/src/Channel/KafkaMessageChannelBuilder.php index fcabf14ba..807f29d1b 100644 --- a/packages/Kafka/src/Channel/KafkaMessageChannelBuilder.php +++ b/packages/Kafka/src/Channel/KafkaMessageChannelBuilder.php @@ -26,8 +26,9 @@ final class KafkaMessageChannelBuilder implements MessageChannelWithSerializatio private KafkaOutboundChannelAdapterBuilder $outboundChannelAdapterBuilder; private string $headerMapper; private ?MediaType $conversionMediaType = null; - private bool $asyncPublishing = false; - private ?int $asyncPublishingTimeout = null; + private bool $batchPublishing = false; + private bool $nonBlockingConfirmation = false; + private ?int $confirmationTimeout = null; private function __construct( private string $channelName, @@ -119,25 +120,36 @@ public function withDefaultConversionMediaType(string $mediaType): self return $this; } - public function withHighThroughputPublishing(bool $enabled = true, ?int $timeoutInMilliseconds = null): self + /** + * @param bool $batchPublishing coalesces produced Messages into broker side batches by enabling producer lingering + * @param bool $nonBlockingConfirmation produces without flushing, delivery reports are awaited before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await delivery reports before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $enabled; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isHighThroughputPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool + { + return $this->batchPublishing; + } + + public function isNonBlockingConfirmationEnabled(): bool { - return $this->asyncPublishing; + return $this->nonBlockingConfirmation; } - public function getAsyncPublishingTimeout(): ?int + public function getConfirmationTimeout(): ?int { - return $this->asyncPublishingTimeout; + return $this->confirmationTimeout; } /** diff --git a/packages/Kafka/src/Configuration/KafkaModule.php b/packages/Kafka/src/Configuration/KafkaModule.php index ad1883bfe..4cc9b884c 100644 --- a/packages/Kafka/src/Configuration/KafkaModule.php +++ b/packages/Kafka/src/Configuration/KafkaModule.php @@ -11,7 +11,7 @@ use Ecotone\Kafka\Inbound\KafkaInboundChannelAdapterBuilder; use Ecotone\Kafka\Outbound\KafkaOutboundChannelAdapterBuilder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\NoExternalConfigurationModule; @@ -113,7 +113,7 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO MessagePublisher::class . '::' . $extensionObject->getMessageChannelName(), ) ->withHeaderMapper($extensionObject->getHeaderMapper()) - ->withAsyncPublishing($extensionObject->isHighThroughputPublishingEnabled(), $extensionObject->getAsyncPublishingTimeout()); + ->withHighThroughputPublishing($extensionObject->isBatchPublishingEnabled(), $extensionObject->isNonBlockingConfirmationEnabled(), $extensionObject->getConfirmationTimeout()); } } @@ -227,7 +227,7 @@ private function registerMessagePublisher(Configuration $messagingConfiguration, ->withDefaultConversionMediaType($mediaType) ); - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $extensionObject->getReferenceName(), $extensionObject->isAsyncPublishingEnabled()); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $extensionObject->getReferenceName(), $extensionObject->isNonBlockingConfirmationEnabled()); } private function getPublisherEndpointId(string $referenceName): string diff --git a/packages/Kafka/src/Configuration/KafkaPublisherConfiguration.php b/packages/Kafka/src/Configuration/KafkaPublisherConfiguration.php index c14098d6e..d5c621c92 100644 --- a/packages/Kafka/src/Configuration/KafkaPublisherConfiguration.php +++ b/packages/Kafka/src/Configuration/KafkaPublisherConfiguration.php @@ -21,7 +21,7 @@ final class KafkaPublisherConfiguration implements DefinedObject { public const ACKNOWLEDGE_TIMEOUT = '8000'; - public const DEFAULT_ASYNC_PUBLISHING_TIMEOUT = 12000; + public const DEFAULT_CONFIRMATION_TIMEOUT = 12000; /** * @param array $configuration @@ -33,8 +33,9 @@ public function __construct( private string $brokerConfigurationReference, private HeaderMapper $headerMapper, private ?string $outputDefaultConversionMediaType = null, - private bool $asyncPublishing = false, - private int $asyncPublishingTimeout = self::DEFAULT_ASYNC_PUBLISHING_TIMEOUT, + private bool $batchPublishing = false, + private bool $nonBlockingConfirmation = false, + private int $confirmationTimeout = self::DEFAULT_CONFIRMATION_TIMEOUT, ) { } @@ -106,25 +107,36 @@ public function getHeaderMapper(): HeaderMapper return $this->headerMapper; } - public function withAsyncPublishing(bool $asyncPublishing = true, ?int $timeoutInMilliseconds = null): self + /** + * @param bool $batchPublishing coalesces produced Messages into broker side batches by enabling producer lingering + * @param bool $nonBlockingConfirmation produces without flushing, delivery reports are awaited on Future::resolve() or before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await delivery reports before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $asyncPublishing; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool + { + return $this->batchPublishing; + } + + public function isNonBlockingConfirmationEnabled(): bool { - return $this->asyncPublishing; + return $this->nonBlockingConfirmation; } - public function getAsyncPublishingTimeout(): int + public function getConfirmationTimeout(): int { - return $this->asyncPublishingTimeout; + return $this->confirmationTimeout; } public function getOutputDefaultConversionMediaType(): ?string @@ -140,7 +152,7 @@ public function getBrokerConfigurationReference(): string public function getAsKafkaConfig(): Conf { $configuration = $this->configuration; - if ($this->asyncPublishing && ! isset($configuration['linger.ms']) && ! isset($configuration['queue.buffering.max.ms'])) { + if ($this->batchPublishing && ! isset($configuration['linger.ms']) && ! isset($configuration['queue.buffering.max.ms'])) { $configuration['linger.ms'] = '20'; } @@ -171,8 +183,9 @@ public function getDefinition(): Definition $this->brokerConfigurationReference, $this->headerMapper->getDefinition(), $this->outputDefaultConversionMediaType, - $this->asyncPublishing, - $this->asyncPublishingTimeout, + $this->batchPublishing, + $this->nonBlockingConfirmation, + $this->confirmationTimeout, ]); } } diff --git a/packages/Kafka/src/Outbound/KafkaDeliveryTracker.php b/packages/Kafka/src/Outbound/KafkaDeliveryTracker.php index a6fc25814..1471f0ae9 100644 --- a/packages/Kafka/src/Outbound/KafkaDeliveryTracker.php +++ b/packages/Kafka/src/Outbound/KafkaDeliveryTracker.php @@ -4,8 +4,8 @@ namespace Ecotone\Kafka\Outbound; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; use Ecotone\Messaging\Message; use RdKafka\Message as KafkaMessage; diff --git a/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapter.php b/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapter.php index 1dedee701..f2ade7890 100644 --- a/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapter.php +++ b/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapter.php @@ -8,9 +8,9 @@ use Ecotone\Kafka\Configuration\KafkaAdmin; use Ecotone\Kafka\Configuration\KafkaPublisherConfiguration; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\ConfigurationException; use Ecotone\Messaging\Conversion\ConversionService; @@ -36,7 +36,7 @@ public function __construct( private KafkaAdmin $kafkaAdmin, private ConversionService $conversionService, private OutboundMessageConverter $outboundMessageConverter, - private AsyncPublishingRegistry $asyncPublishingRegistry, + private PendingDeliveryRegistry $pendingDeliveryRegistry, ) { } @@ -45,8 +45,8 @@ public function __construct( */ public function handle(Message $message): void { - if ($message->getPayload() instanceof BatchMessage && ! $this->isAsyncPublishingEnabled()) { - throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires async publishing to be enabled. Enable it with withAsyncPublishing(), available as part of Ecotone Enterprise.', $this->referenceName)); + if ($message->getPayload() instanceof BatchMessage && ! $this->isBatchPublishingEnabled()) { + throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires batch publishing to be enabled. Enable it with withHighThroughputPublishing(), available as part of Ecotone Enterprise.', $this->referenceName)); } $producer = $this->kafkaAdmin->getProducer($this->referenceName); @@ -61,7 +61,7 @@ public function handle(Message $message): void $deliveryId = $this->produce($message, $topic, trackDelivery: true); $producer->poll(0); - if ($this->canPublishAsynchronously()) { + if ($this->canDeferConfirmation()) { $this->registerPendingDelivery($producer, [$deliveryId]); return; @@ -70,9 +70,14 @@ public function handle(Message $message): void $this->flushSynchronously($producer, [$deliveryId]); } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->isAsyncPublishingEnabled(); + return $this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->isBatchPublishingEnabled(); + } + + public function isNonBlockingConfirmationEnabled(): bool + { + return $this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->isNonBlockingConfirmationEnabled(); } private function handleBatch(BatchMessage $batchMessage, Producer $producer, ProducerTopic $topic): void @@ -99,7 +104,7 @@ private function handleBatch(BatchMessage $batchMessage, Producer $producer, Pro throw $exception; } - if ($this->canPublishAsynchronously()) { + if ($this->canDeferConfirmation()) { $this->registerPendingDelivery($producer, $deliveryIds); return; @@ -130,7 +135,7 @@ private function produce(Message $message, ProducerTopic $topic, bool $trackDeli : null; try { - $retryDeadline = microtime(true) + ($this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->getAsyncPublishingTimeout() / 1000); + $retryDeadline = microtime(true) + ($this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->getConfirmationTimeout() / 1000); while (true) { try { $this->produceTracked($topic, $outboundMessage, $partitionKey, $headers, $deliveryId); @@ -173,9 +178,9 @@ private function produceTracked(ProducerTopic $topic, \Ecotone\Messaging\Channel ); } - private function canPublishAsynchronously(): bool + private function canDeferConfirmation(): bool { - return $this->isAsyncPublishingEnabled() && $this->asyncPublishingRegistry->isScopeActive(); + return $this->isNonBlockingConfirmationEnabled() && $this->pendingDeliveryRegistry->isScopeActive(); } /** @@ -183,13 +188,13 @@ private function canPublishAsynchronously(): bool */ private function registerPendingDelivery(Producer $producer, array $deliveryIds): void { - $this->asyncPublishingRegistry->register( + $this->pendingDeliveryRegistry->register( $this->referenceName, new KafkaPendingDelivery( $producer, $this->kafkaAdmin->getDeliveryTracker($this->referenceName), $deliveryIds, - $this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->getAsyncPublishingTimeout(), + $this->kafkaAdmin->getConfigurationForPublisher($this->referenceName)->getConfirmationTimeout(), $this->referenceName, ), ); @@ -209,7 +214,7 @@ private function flushSynchronously(Producer $producer, array $deliveryIds = []) if ($deliveryIds !== []) { $deliveryResult = $this->kafkaAdmin->getDeliveryTracker($this->referenceName)->collectResult($deliveryIds, $this->referenceName); if (! $deliveryResult->isSuccessful()) { - if ($this->isAsyncPublishingEnabled()) { + if ($this->isNonBlockingConfirmationEnabled()) { throw PublishingFailedException::withFailedDeliveries($deliveryResult->getFailedDeliveries()); } diff --git a/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapterBuilder.php b/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapterBuilder.php index 1c0497bae..8ec563a2d 100644 --- a/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapterBuilder.php +++ b/packages/Kafka/src/Outbound/KafkaOutboundChannelAdapterBuilder.php @@ -5,7 +5,7 @@ namespace Ecotone\Kafka\Outbound; use Ecotone\Kafka\Configuration\KafkaAdmin; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -100,7 +100,7 @@ public function compile(MessagingContainerBuilder $builder): Definition new Reference(KafkaAdmin::class), new Reference(ConversionService::REFERENCE_NAME), $outboundMessageConverter, - new Reference(AsyncPublishingRegistry::class), + new Reference(PendingDeliveryRegistry::class), ]); } diff --git a/packages/Kafka/src/Outbound/KafkaPendingDelivery.php b/packages/Kafka/src/Outbound/KafkaPendingDelivery.php index 3811e2da7..37921320f 100644 --- a/packages/Kafka/src/Outbound/KafkaPendingDelivery.php +++ b/packages/Kafka/src/Outbound/KafkaPendingDelivery.php @@ -4,8 +4,8 @@ namespace Ecotone\Kafka\Outbound; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery; use RdKafka\Producer; /** diff --git a/packages/Kafka/tests/Integration/AsyncPublishingReliabilityTest.php b/packages/Kafka/tests/Integration/HighThroughputPublishingReliabilityTest.php similarity index 90% rename from packages/Kafka/tests/Integration/AsyncPublishingReliabilityTest.php rename to packages/Kafka/tests/Integration/HighThroughputPublishingReliabilityTest.php index c36069602..f090b0982 100644 --- a/packages/Kafka/tests/Integration/AsyncPublishingReliabilityTest.php +++ b/packages/Kafka/tests/Integration/HighThroughputPublishingReliabilityTest.php @@ -8,7 +8,7 @@ use Ecotone\Kafka\Configuration\KafkaPublisherConfiguration; use Ecotone\Kafka\Outbound\MessagePublishingException; use Ecotone\Lite\EcotoneLite; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\MessagePublisher; @@ -23,7 +23,7 @@ * @internal */ #[RunTestsInSeparateProcesses] -final class AsyncPublishingReliabilityTest extends TestCase +final class HighThroughputPublishingReliabilityTest extends TestCase { public function test_broker_rejected_message_fails_synchronous_fallback_of_async_publisher(): void { @@ -38,7 +38,7 @@ public function test_broker_rejected_message_fails_async_publish_on_future_resol { $publisher = $this->bootstrapPublisher(); - $future = $publisher->asyncPublish(str_repeat('x', 2_000_000)); + $future = $publisher->publishDeferred(str_repeat('x', 2_000_000)); $this->expectException(PublishingFailedException::class); @@ -74,7 +74,7 @@ private function bootstrapPublisher(): MessagePublisher ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::KAFKA_PACKAGE])) ->withExtensionObjects([ KafkaPublisherConfiguration::createWithDefaults(topicName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(timeoutInMilliseconds: 10000) + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 10000) ->setConfiguration('message.max.bytes', '4000000'), ]), licenceKey: LicenceTesting::VALID_LICENCE, diff --git a/packages/Kafka/tests/Integration/AsyncPublishingTest.php b/packages/Kafka/tests/Integration/HighThroughputPublishingTest.php similarity index 91% rename from packages/Kafka/tests/Integration/AsyncPublishingTest.php rename to packages/Kafka/tests/Integration/HighThroughputPublishingTest.php index 40a65ea87..539c33215 100644 --- a/packages/Kafka/tests/Integration/AsyncPublishingTest.php +++ b/packages/Kafka/tests/Integration/HighThroughputPublishingTest.php @@ -14,7 +14,7 @@ use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; @@ -36,9 +36,9 @@ * @internal */ #[RunTestsInSeparateProcesses] -final class AsyncPublishingTest extends TestCase +final class HighThroughputPublishingTest extends TestCase { - public function test_multiple_messages_published_asynchronously_from_command_handler_are_delivered(): void + public function test_multiple_messages_published_from_command_handler_are_delivered(): void { $channelName = 'async_orders'; $orderService = $this->createOrderService($channelName); @@ -61,7 +61,7 @@ public function test_failing_to_deliver_asynchronously_published_messages_throws $channelName, $orderService, KafkaBrokerConfiguration::createWithDefaults(['wronghost:9092']), - asyncPublishingTimeout: 500, + confirmationTimeout: 500, ); $this->expectException(PublishingFailedException::class); @@ -69,7 +69,7 @@ public function test_failing_to_deliver_asynchronously_published_messages_throws $messaging->sendCommandWithRoutingKey('order.place', 'espresso'); } - public function test_async_publishing_requires_enterprise_licence(): void + public function test_high_throughput_publishing_requires_enterprise_licence(): void { $this->expectException(LicensingException::class); @@ -80,7 +80,7 @@ public function test_async_publishing_requires_enterprise_licence(): void ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::KAFKA_PACKAGE])) ->withExtensionObjects([ KafkaPublisherConfiguration::createWithDefaults(topicName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), ); } @@ -104,7 +104,7 @@ public function test_async_publishing_via_message_channel_requires_enterprise_li ); } - public function test_message_publisher_async_publish_confirms_delivery_on_future_resolve(): void + public function test_publish_deferred_confirms_delivery_on_future_resolve(): void { $messaging = EcotoneLite::bootstrapFlowTesting( [], @@ -113,14 +113,14 @@ public function test_message_publisher_async_publish_confirms_delivery_on_future ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::KAFKA_PACKAGE])) ->withExtensionObjects([ KafkaPublisherConfiguration::createWithDefaults(topicName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); $publisher = $messaging->getGateway(MessagePublisher::class); - $singleFuture = $publisher->asyncPublish('single order'); - $batchFuture = $publisher->asyncPublish( + $singleFuture = $publisher->publishDeferred('single order'); + $batchFuture = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => '5']) @@ -166,7 +166,7 @@ public function getReceived(): array ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE, ModulePackageList::KAFKA_PACKAGE])) ->withExtensionObjects([ KafkaPublisherConfiguration::createWithDefaults(topicName: $topicName) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), TopicConfiguration::createWithReferenceName('batchOrdersTopic', $topicName), ]), licenceKey: LicenceTesting::VALID_LICENCE, @@ -214,7 +214,7 @@ public function getReceived(): array }; } - private function bootstrapEcotone(string $channelName, object $orderService, KafkaBrokerConfiguration $brokerConfiguration, ?int $asyncPublishingTimeout = null): FlowTestSupport + private function bootstrapEcotone(string $channelName, object $orderService, KafkaBrokerConfiguration $brokerConfiguration, ?int $confirmationTimeout = null): FlowTestSupport { $channelBuilder = KafkaMessageChannelBuilder::create( $channelName, @@ -222,8 +222,8 @@ private function bootstrapEcotone(string $channelName, object $orderService, Kaf messageGroupId: $uniqueId, )->withHighThroughputPublishing(); - if ($asyncPublishingTimeout !== null) { - $channelBuilder = $channelBuilder->withHighThroughputPublishing(timeoutInMilliseconds: $asyncPublishingTimeout); + if ($confirmationTimeout !== null) { + $channelBuilder = $channelBuilder->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: $confirmationTimeout); } return EcotoneLite::bootstrapFlowTesting( diff --git a/packages/Redis/src/Configuration/RedisMessagePublisherConfiguration.php b/packages/Redis/src/Configuration/RedisMessagePublisherConfiguration.php index 4aaafbc3c..c165830fb 100644 --- a/packages/Redis/src/Configuration/RedisMessagePublisherConfiguration.php +++ b/packages/Redis/src/Configuration/RedisMessagePublisherConfiguration.php @@ -14,7 +14,7 @@ final class RedisMessagePublisherConfiguration { private bool $autoDeclareOnSend = true; private string $headerMapper = ''; - private bool $asyncPublishing = false; + private bool $batchPublishing = false; private function __construct(private string $connectionReference, private string $queueName, private ?string $outputDefaultConversionMediaType, private string $referenceName) { @@ -73,15 +73,19 @@ public function getReferenceName(): string return $this->referenceName; } - public function withAsyncPublishing(bool $asyncPublishing = true): self + /** + * Coalesces published Messages into a single scripted round trip. + * Non blocking confirmation is not offered here, as the round trip blocks until Redis confirms it. + */ + public function withHighThroughputPublishing(): self { - $this->asyncPublishing = $asyncPublishing; + $this->batchPublishing = true; return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; } } diff --git a/packages/Redis/src/Configuration/RedisMessagePublisherModule.php b/packages/Redis/src/Configuration/RedisMessagePublisherModule.php index 82435d4af..cbe12b7b8 100644 --- a/packages/Redis/src/Configuration/RedisMessagePublisherModule.php +++ b/packages/Redis/src/Configuration/RedisMessagePublisherModule.php @@ -6,7 +6,7 @@ use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\NoExternalConfigurationModule; @@ -82,10 +82,10 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO ->withAutoDeclareOnSend($messagePublisher->isAutoDeclareOnSend()) ->withHeaderMapper($messagePublisher->getHeaderMapper()) ->withDefaultConversionMediaType($mediaType) - ->withAsyncPublishing($messagePublisher->isAsyncPublishingEnabled()) + ->withBatchPublishing($messagePublisher->isBatchPublishingEnabled()) ); - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $messagePublisher->getReferenceName(), $messagePublisher->isAsyncPublishingEnabled()); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $messagePublisher->getReferenceName(), nonBlockingConfirmationEnabled: false); } } diff --git a/packages/Redis/src/RedisBackedMessageChannelBuilder.php b/packages/Redis/src/RedisBackedMessageChannelBuilder.php index c0096ac6b..08747f00a 100644 --- a/packages/Redis/src/RedisBackedMessageChannelBuilder.php +++ b/packages/Redis/src/RedisBackedMessageChannelBuilder.php @@ -33,16 +33,20 @@ public static function create(string $channelName, string $connectionReferenceNa return new self($channelName, $connectionReferenceName); } - public function withHighThroughputPublishing(bool $enabled = true): self + /** + * Coalesces published Messages into a single scripted round trip. + * Non blocking confirmation is not offered here, as the round trip blocks until Redis confirms it. + */ + public function withHighThroughputPublishing(): self { - $this->getRedisOutboundChannelAdapter()->withAsyncPublishing($enabled); + $this->getRedisOutboundChannelAdapter()->withBatchPublishing(); return $this; } protected function supportsBatchMessages(): bool { - return $this->getRedisOutboundChannelAdapter()->isAsyncPublishingEnabled(); + return $this->getRedisOutboundChannelAdapter()->isBatchPublishingEnabled(); } private function getRedisOutboundChannelAdapter(): RedisOutboundChannelAdapterBuilder diff --git a/packages/Redis/src/RedisOutboundChannelAdapter.php b/packages/Redis/src/RedisOutboundChannelAdapter.php index a115f6921..f85f310ed 100644 --- a/packages/Redis/src/RedisOutboundChannelAdapter.php +++ b/packages/Redis/src/RedisOutboundChannelAdapter.php @@ -7,9 +7,8 @@ use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapter; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Conversion\ConversionService; use Ecotone\Messaging\Message; @@ -54,8 +53,7 @@ public function __construct( bool $autoDeclare, OutboundMessageConverter $outboundMessageConverter, ConversionService $conversionService, - AsyncPublishingRegistry $asyncPublishingRegistry, - bool $asyncPublishing = false, + bool $batchPublishing = false, ) { parent::__construct( $connectionFactory, @@ -63,8 +61,7 @@ public function __construct( $autoDeclare, $outboundMessageConverter, $conversionService, - $asyncPublishingRegistry, - $asyncPublishing, + $batchPublishing, $queueName, ); } diff --git a/packages/Redis/src/RedisOutboundChannelAdapterBuilder.php b/packages/Redis/src/RedisOutboundChannelAdapterBuilder.php index 22311888f..97073fa0b 100644 --- a/packages/Redis/src/RedisOutboundChannelAdapterBuilder.php +++ b/packages/Redis/src/RedisOutboundChannelAdapterBuilder.php @@ -7,7 +7,6 @@ use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapterBuilder; use Ecotone\Enqueue\HttpReconnectableConnectionFactory; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -21,7 +20,7 @@ */ final class RedisOutboundChannelAdapterBuilder extends EnqueueOutboundChannelAdapterBuilder { - private bool $asyncPublishing = false; + private bool $batchPublishing = false; private function __construct(private string $queueName, private string $connectionFactoryReferenceName) { @@ -36,22 +35,22 @@ public static function createWith(string $queueName, string $connectionFactoryRe ); } - public function withAsyncPublishing(bool $asyncPublishing = true): self + public function withBatchPublishing(bool $batchPublishing = true): self { - $this->asyncPublishing = $asyncPublishing; + $this->batchPublishing = $batchPublishing; return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; } public function compile(MessagingContainerBuilder $builder): Definition { - if ($this->asyncPublishing && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { - throw LicensingException::create('Asynchronous publishing is available only with Ecotone Enterprise licence.'); + if ($this->batchPublishing && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { + throw LicensingException::create('High Throughput Publishing is available only with Ecotone Enterprise licence.'); } $connectionFactory = new Definition(CachedConnectionFactory::class, [ @@ -75,8 +74,7 @@ public function compile(MessagingContainerBuilder $builder): Definition $this->autoDeclare, $outboundMessageConverter, new Reference(ConversionService::REFERENCE_NAME), - new Reference(AsyncPublishingRegistry::class), - $this->asyncPublishing, + $this->batchPublishing, ]); } } diff --git a/packages/Sqs/tests/Fixture/AsyncPublishing/OrderWasPlaced.php b/packages/Redis/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php similarity index 71% rename from packages/Sqs/tests/Fixture/AsyncPublishing/OrderWasPlaced.php rename to packages/Redis/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php index 1cf3396e4..bdb3ac8d6 100644 --- a/packages/Sqs/tests/Fixture/AsyncPublishing/OrderWasPlaced.php +++ b/packages/Redis/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Sqs\Fixture\AsyncPublishing; +namespace Test\Ecotone\Redis\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Redis/tests/Integration/AsyncPublishingReliabilityTest.php b/packages/Redis/tests/Integration/HighThroughputPublishingReliabilityTest.php similarity index 95% rename from packages/Redis/tests/Integration/AsyncPublishingReliabilityTest.php rename to packages/Redis/tests/Integration/HighThroughputPublishingReliabilityTest.php index 181039554..6a8180c72 100644 --- a/packages/Redis/tests/Integration/AsyncPublishingReliabilityTest.php +++ b/packages/Redis/tests/Integration/HighThroughputPublishingReliabilityTest.php @@ -7,7 +7,7 @@ use Ecotone\Lite\EcotoneLite; use Ecotone\Lite\Test\FlowTestSupport; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\PollableChannelConfiguration; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; @@ -25,7 +25,7 @@ * licence Apache-2.0 * @internal */ -final class AsyncPublishingReliabilityTest extends ConnectionTestCase +final class HighThroughputPublishingReliabilityTest extends ConnectionTestCase { private const CHANNEL_NAME = 'partiallyFailingChannel'; diff --git a/packages/Redis/tests/Integration/AsyncPublishingTest.php b/packages/Redis/tests/Integration/HighThroughputPublishingTest.php similarity index 83% rename from packages/Redis/tests/Integration/AsyncPublishingTest.php rename to packages/Redis/tests/Integration/HighThroughputPublishingTest.php index 0116072ca..3624b637d 100644 --- a/packages/Redis/tests/Integration/AsyncPublishingTest.php +++ b/packages/Redis/tests/Integration/HighThroughputPublishingTest.php @@ -9,7 +9,7 @@ use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; @@ -27,14 +27,14 @@ use Enqueue\Redis\RedisContext; use Symfony\Component\Uid\Uuid; use Test\Ecotone\Redis\ConnectionTestCase; -use Test\Ecotone\Redis\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Redis\Fixture\HighThroughputPublishing\OrderWasPlaced; use Throwable; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingTest extends ConnectionTestCase +final class HighThroughputPublishingTest extends ConnectionTestCase { public function setUp(): void { @@ -44,7 +44,7 @@ public function setUp(): void $context->getRedis()->del('asyncOrdersChannel:delayed'); } - public function test_multiple_messages_published_asynchronously_from_command_handler_are_delivered(): void + public function test_multiple_messages_published_from_command_handler_are_delivered(): void { $orderService = $this->createOrderService(); $messaging = $this->bootstrapEcotoneWithChannel($orderService, LicenceTesting::VALID_LICENCE); @@ -61,7 +61,7 @@ public function test_multiple_messages_published_asynchronously_from_command_han ); } - public function test_async_publishing_requires_enterprise_licence(): void + public function test_high_throughput_publishing_requires_enterprise_licence(): void { $orderService = $this->createOrderService(); @@ -70,7 +70,7 @@ public function test_async_publishing_requires_enterprise_licence(): void $this->bootstrapEcotoneWithChannel($orderService, licenceKey: null); } - public function test_async_publishing_via_message_publisher_requires_enterprise_licence(): void + public function test_high_throughput_publishing_via_message_publisher_requires_enterprise_licence(): void { $this->expectException(LicensingException::class); @@ -81,44 +81,36 @@ public function test_async_publishing_via_message_publisher_requires_enterprise_ ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::REDIS_PACKAGE])) ->withExtensionObjects([ RedisMessagePublisherConfiguration::create(queueName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), ); } - public function test_async_publish_on_publisher_without_async_configuration_throws_before_publishing(): void + public function test_publish_deferred_is_not_available_as_confirmation_can_not_be_deferred(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: false); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publishFailed = false; - try { - $publisher->asyncPublish('order that must not be published'); - } catch (PublishingFailedException) { - $publishFailed = true; - } + $this->expectException(PublishingFailedException::class); + $this->expectExceptionMessageMatches('/not configured for non blocking confirmation/'); - $this->assertTrue($publishFailed); - $this->assertNull($messaging->getMessageChannel($queueName)->receive()); + $publisher->publishDeferred('order that must not be published'); } - public function test_message_publisher_async_publish_confirms_delivery_on_future_resolve(): void + public function test_message_publisher_delivers_single_and_batched_messages(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $singleFuture = $publisher->asyncPublish('single order'); - $batchFuture = $publisher->asyncPublish( + $publisher->send('single order'); + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => '5']) ); - $this->assertNull($singleFuture->resolve()); - $this->assertNull($batchFuture->resolve()); - $receivedPayloads = []; while ($message = $messaging->getMessageChannel($queueName)->receive()) { $receivedPayloads[] = $message->getPayload(); @@ -148,7 +140,7 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE, ModulePackageList::REDIS_PACKAGE])) ->withExtensionObjects([ RedisMessagePublisherConfiguration::create(queueName: $queueName) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), RedisBackedMessageChannelBuilder::create($queueName), ]), licenceKey: LicenceTesting::VALID_LICENCE, @@ -167,14 +159,14 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess public function test_delayed_entry_of_published_batch_lands_in_delayed_set(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('immediate order') ->append('delayed order', [MessageHeaders::DELIVERY_DELAY => 60000]) - )->resolve(); + ); $receivedPayloads = []; while ($message = $messaging->getMessageChannel($queueName)->receive()) { @@ -190,14 +182,14 @@ public function test_delayed_entry_of_published_batch_lands_in_delayed_set(): vo public function test_expired_entry_of_published_batch_is_not_delivered(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('expiring order', [MessageHeaders::TIME_TO_LIVE => 1000]) ->append('kept order') - )->resolve(); + ); sleep(2); @@ -209,7 +201,7 @@ public function test_expired_entry_of_published_batch_is_not_delivered(): void public function test_publishing_to_wrong_type_key_throws(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); /** @var RedisContext $context */ $context = $this->getConnectionFactory()->createContext(); $context->getRedis()->eval('redis.call("set", KEYS[1], "blocked") return 1', [$queueName]); @@ -217,7 +209,7 @@ public function test_publishing_to_wrong_type_key_throws(): void $publishFailed = false; try { - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('first order') ->append('second order') @@ -232,7 +224,7 @@ public function test_publishing_to_wrong_type_key_throws(): void public function test_partially_applied_mixed_batch_throws_while_immediate_entries_remain(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); /** @var RedisContext $context */ $context = $this->getConnectionFactory()->createContext(); $context->getRedis()->lpush($queueName . ':delayed', 'poison entry forcing wrong type on the delayed set'); @@ -240,7 +232,7 @@ public function test_partially_applied_mixed_batch_throws_while_immediate_entrie $publishFailed = false; try { - $publisher->asyncPublish( + $publisher->convertAndSend( BatchMessage::constructEmpty() ->append('immediate order') ->append('delayed order', [MessageHeaders::DELIVERY_DELAY => 60000]) @@ -299,11 +291,11 @@ private function bootstrapEcotoneWithChannel(object $orderService, ?string $lice ); } - private function bootstrapPublisher(string $queueName, bool $asyncPublishing): FlowTestSupport + private function bootstrapPublisher(string $queueName, bool $highThroughputPublishing): FlowTestSupport { $publisherConfiguration = RedisMessagePublisherConfiguration::create(queueName: $queueName); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } return EcotoneLite::bootstrapFlowTesting( diff --git a/packages/Sqs/src/Configuration/SqsMessagePublisherConfiguration.php b/packages/Sqs/src/Configuration/SqsMessagePublisherConfiguration.php index f38f4a1e3..ad5b573d1 100644 --- a/packages/Sqs/src/Configuration/SqsMessagePublisherConfiguration.php +++ b/packages/Sqs/src/Configuration/SqsMessagePublisherConfiguration.php @@ -15,8 +15,9 @@ final class SqsMessagePublisherConfiguration { private bool $autoDeclareOnSend = true; private string $headerMapper = ''; - private bool $asyncPublishing = false; - private ?int $asyncPublishingTimeout = null; + private bool $batchPublishing = false; + private bool $nonBlockingConfirmation = false; + private ?int $confirmationTimeout = null; private function __construct(private string $connectionReference, private string $queueName, private ?string $outputDefaultConversionMediaType, private string $referenceName) { @@ -75,24 +76,35 @@ public function getReferenceName(): string return $this->referenceName; } - public function withAsyncPublishing(bool $asyncPublishing = true, ?int $timeoutInMilliseconds = null): self + /** + * @param bool $batchPublishing coalesces published Messages into SQS batch send requests + * @param bool $nonBlockingConfirmation dispatches send requests without waiting on their responses, which are awaited on Future::resolve() or before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await a send response before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $asyncPublishing; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool + { + return $this->batchPublishing; + } + + public function isNonBlockingConfirmationEnabled(): bool { - return $this->asyncPublishing; + return $this->nonBlockingConfirmation; } - public function getAsyncPublishingTimeout(): ?int + public function getConfirmationTimeout(): ?int { - return $this->asyncPublishingTimeout; + return $this->confirmationTimeout; } } diff --git a/packages/Sqs/src/Configuration/SqsMessagePublisherModule.php b/packages/Sqs/src/Configuration/SqsMessagePublisherModule.php index b65b234ad..970752be9 100644 --- a/packages/Sqs/src/Configuration/SqsMessagePublisherModule.php +++ b/packages/Sqs/src/Configuration/SqsMessagePublisherModule.php @@ -6,7 +6,7 @@ use Ecotone\AnnotationFinder\AnnotationFinder; use Ecotone\Messaging\Attribute\ModuleAnnotation; -use Ecotone\Messaging\Channel\AsyncPublishing\Config\AsyncPublishGatewayRegistration; +use Ecotone\Messaging\Channel\DeliveryConfirmation\Config\DeferredPublishingGatewayRegistration; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\ExtensionObjectResolver; use Ecotone\Messaging\Config\Annotation\ModuleConfiguration\NoExternalConfigurationModule; @@ -82,10 +82,10 @@ public function prepare(Configuration $messagingConfiguration, array $extensionO ->withAutoDeclareOnSend($messagePublisher->isAutoDeclareOnSend()) ->withHeaderMapper($messagePublisher->getHeaderMapper()) ->withDefaultConversionMediaType($mediaType) - ->withAsyncPublishing($messagePublisher->isAsyncPublishingEnabled(), $messagePublisher->getAsyncPublishingTimeout()) + ->withHighThroughputPublishing($messagePublisher->isBatchPublishingEnabled(), $messagePublisher->isNonBlockingConfirmationEnabled(), $messagePublisher->getConfirmationTimeout()) ); - AsyncPublishGatewayRegistration::registerFor($messagingConfiguration, $messagePublisher->getReferenceName(), $messagePublisher->isAsyncPublishingEnabled()); + DeferredPublishingGatewayRegistration::registerFor($messagingConfiguration, $messagePublisher->getReferenceName(), $messagePublisher->isNonBlockingConfirmationEnabled()); } } diff --git a/packages/Sqs/src/SqsBackedMessageChannelBuilder.php b/packages/Sqs/src/SqsBackedMessageChannelBuilder.php index cb6df98e4..ca455c567 100644 --- a/packages/Sqs/src/SqsBackedMessageChannelBuilder.php +++ b/packages/Sqs/src/SqsBackedMessageChannelBuilder.php @@ -33,16 +33,21 @@ public static function create(string $channelName, string $connectionReferenceNa return new self($channelName, $connectionReferenceName); } - public function withHighThroughputPublishing(bool $enabled = true, ?int $timeoutInMilliseconds = null): self + /** + * @param bool $batchPublishing coalesces published Messages into SQS batch send requests + * @param bool $nonBlockingConfirmation dispatches send requests without waiting on their responses, which are awaited before the surrounding Command Bus or asynchronous endpoint finishes + * @param int|null $confirmationTimeoutInMilliseconds how long to await a send response before treating the delivery as failed + */ + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - $this->getSqsOutboundChannelAdapter()->withAsyncPublishing($enabled, $timeoutInMilliseconds); + $this->getSqsOutboundChannelAdapter()->withHighThroughputPublishing($batchPublishing, $nonBlockingConfirmation, $confirmationTimeoutInMilliseconds); return $this; } protected function supportsBatchMessages(): bool { - return $this->getSqsOutboundChannelAdapter()->isAsyncPublishingEnabled(); + return $this->getSqsOutboundChannelAdapter()->isBatchPublishingEnabled(); } private function getSqsOutboundChannelAdapter(): SqsOutboundChannelAdapterBuilder diff --git a/packages/Sqs/src/SqsOutboundChannelAdapter.php b/packages/Sqs/src/SqsOutboundChannelAdapter.php index 4cd75e82c..969ad1f72 100644 --- a/packages/Sqs/src/SqsOutboundChannelAdapter.php +++ b/packages/Sqs/src/SqsOutboundChannelAdapter.php @@ -7,8 +7,8 @@ use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapter; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\ConfigurationException; use Ecotone\Messaging\Conversion\ConversionService; @@ -34,9 +34,10 @@ public function __construct( bool $autoDeclare, OutboundMessageConverter $outboundMessageConverter, ConversionService $conversionService, - private AsyncPublishingRegistry $asyncPublishingRegistry, - private bool $asyncPublishing = false, - private int $asyncPublishingTimeout = SqsOutboundChannelAdapterBuilder::DEFAULT_ASYNC_PUBLISHING_TIMEOUT, + private PendingDeliveryRegistry $pendingDeliveryRegistry, + private bool $batchPublishing = false, + private bool $nonBlockingConfirmation = false, + private int $confirmationTimeout = SqsOutboundChannelAdapterBuilder::DEFAULT_CONFIRMATION_TIMEOUT, ) { $this->requestDispatchPool = new SqsRequestDispatchPool(); parent::__construct( @@ -45,8 +46,7 @@ public function __construct( $autoDeclare, $outboundMessageConverter, $conversionService, - $asyncPublishingRegistry, - $asyncPublishing, + $batchPublishing, $queueName, ); } @@ -61,8 +61,8 @@ public function initialize(): void public function handle(Message $message): void { - if ($message->getPayload() instanceof BatchMessage && ! $this->asyncPublishing) { - throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires async publishing to be enabled. Enable it with withAsyncPublishing(), available as part of Ecotone Enterprise.', $this->queueName)); + if ($message->getPayload() instanceof BatchMessage && ! $this->batchPublishing) { + throw ConfigurationException::create(sprintf('Sending BatchMessage over `%s` requires batch publishing to be enabled. Enable it with withHighThroughputPublishing(), available as part of Ecotone Enterprise.', $this->queueName)); } /** @var SqsContext $context */ @@ -88,8 +88,8 @@ public function handle(Message $message): void $pendingDelivery = new SqsPendingDelivery($sendRequestPromises, $trackedMessagesPerRequest, $this->queueName); - if ($this->asyncPublishing && $this->asyncPublishingRegistry->isScopeActive()) { - $this->asyncPublishingRegistry->register($this->queueName, $pendingDelivery); + if ($this->nonBlockingConfirmation && $this->pendingDeliveryRegistry->isScopeActive()) { + $this->pendingDeliveryRegistry->register($this->queueName, $pendingDelivery); return; } @@ -181,7 +181,7 @@ private function buildBatchRequest(SqsDestination $destination, string $queueUrl 'Entries' => $entries, ]; - $arguments['@http'] = ['timeout' => $this->asyncPublishingTimeout / 1000]; + $arguments['@http'] = ['timeout' => $this->confirmationTimeout / 1000]; return ['arguments' => $arguments, 'trackedMessages' => $trackedMessages]; } diff --git a/packages/Sqs/src/SqsOutboundChannelAdapterBuilder.php b/packages/Sqs/src/SqsOutboundChannelAdapterBuilder.php index d3b1cbe42..24084eab3 100644 --- a/packages/Sqs/src/SqsOutboundChannelAdapterBuilder.php +++ b/packages/Sqs/src/SqsOutboundChannelAdapterBuilder.php @@ -7,7 +7,7 @@ use Ecotone\Enqueue\CachedConnectionFactory; use Ecotone\Enqueue\EnqueueOutboundChannelAdapterBuilder; use Ecotone\Enqueue\HttpReconnectableConnectionFactory; -use Ecotone\Messaging\Channel\AsyncPublishing\AsyncPublishingRegistry; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDeliveryRegistry; use Ecotone\Messaging\Channel\PollableChannel\Serialization\OutboundMessageConverter; use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Config\Container\MessagingContainerBuilder; @@ -22,10 +22,11 @@ */ final class SqsOutboundChannelAdapterBuilder extends EnqueueOutboundChannelAdapterBuilder { - public const DEFAULT_ASYNC_PUBLISHING_TIMEOUT = 25000; + public const DEFAULT_CONFIRMATION_TIMEOUT = 25000; - private bool $asyncPublishing = false; - private int $asyncPublishingTimeout = self::DEFAULT_ASYNC_PUBLISHING_TIMEOUT; + private bool $batchPublishing = false; + private bool $nonBlockingConfirmation = false; + private int $confirmationTimeout = self::DEFAULT_CONFIRMATION_TIMEOUT; private function __construct(private string $queueName, private string $connectionFactoryReferenceName) { @@ -37,26 +38,32 @@ public static function create(string $queueName, string $connectionFactoryRefere return new self($queueName, $connectionFactoryReferenceName); } - public function withAsyncPublishing(bool $asyncPublishing = true, ?int $timeoutInMilliseconds = null): self + public function withHighThroughputPublishing(bool $batchPublishing = true, bool $nonBlockingConfirmation = true, ?int $confirmationTimeoutInMilliseconds = null): self { - Assert::isTrue($timeoutInMilliseconds === null || $timeoutInMilliseconds > 0, 'Async publishing timeout must be a positive amount of milliseconds.'); - $this->asyncPublishing = $asyncPublishing; - if ($timeoutInMilliseconds !== null) { - $this->asyncPublishingTimeout = $timeoutInMilliseconds; + Assert::isTrue($confirmationTimeoutInMilliseconds === null || $confirmationTimeoutInMilliseconds > 0, 'Confirmation timeout must be a positive amount of milliseconds.'); + $this->batchPublishing = $batchPublishing; + $this->nonBlockingConfirmation = $nonBlockingConfirmation; + if ($confirmationTimeoutInMilliseconds !== null) { + $this->confirmationTimeout = $confirmationTimeoutInMilliseconds; } return $this; } - public function isAsyncPublishingEnabled(): bool + public function isBatchPublishingEnabled(): bool { - return $this->asyncPublishing; + return $this->batchPublishing; + } + + public function isNonBlockingConfirmationEnabled(): bool + { + return $this->nonBlockingConfirmation; } public function compile(MessagingContainerBuilder $builder): Definition { - if ($this->asyncPublishing && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { - throw LicensingException::create('Asynchronous publishing is available only with Ecotone Enterprise licence.'); + if (($this->batchPublishing || $this->nonBlockingConfirmation) && ! $builder->getServiceConfiguration()->isRunningForEnterprise()) { + throw LicensingException::create('High Throughput Publishing is available only with Ecotone Enterprise licence.'); } $connectionFactory = new Definition(CachedConnectionFactory::class, [ @@ -80,9 +87,10 @@ public function compile(MessagingContainerBuilder $builder): Definition $this->autoDeclare, $outboundMessageConverter, new Reference(ConversionService::REFERENCE_NAME), - new Reference(AsyncPublishingRegistry::class), - $this->asyncPublishing, - $this->asyncPublishingTimeout, + new Reference(PendingDeliveryRegistry::class), + $this->batchPublishing, + $this->nonBlockingConfirmation, + $this->confirmationTimeout, ]); } } diff --git a/packages/Sqs/src/SqsPendingDelivery.php b/packages/Sqs/src/SqsPendingDelivery.php index 5d1e3ca6e..09535c609 100644 --- a/packages/Sqs/src/SqsPendingDelivery.php +++ b/packages/Sqs/src/SqsPendingDelivery.php @@ -5,9 +5,9 @@ namespace Ecotone\Sqs; use Aws\Result; -use Ecotone\Messaging\Channel\AsyncPublishing\DeliveryResult; -use Ecotone\Messaging\Channel\AsyncPublishing\FailedDelivery; -use Ecotone\Messaging\Channel\AsyncPublishing\PendingDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\DeliveryResult; +use Ecotone\Messaging\Channel\DeliveryConfirmation\FailedDelivery; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PendingDelivery; use Ecotone\Messaging\Message; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\Utils; diff --git a/packages/Dbal/tests/Fixture/AsyncPublishing/OrderWasPlaced.php b/packages/Sqs/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php similarity index 72% rename from packages/Dbal/tests/Fixture/AsyncPublishing/OrderWasPlaced.php rename to packages/Sqs/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php index b1b0edf39..8bf2049e1 100644 --- a/packages/Dbal/tests/Fixture/AsyncPublishing/OrderWasPlaced.php +++ b/packages/Sqs/tests/Fixture/HighThroughputPublishing/OrderWasPlaced.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Test\Ecotone\Dbal\Fixture\AsyncPublishing; +namespace Test\Ecotone\Sqs\Fixture\HighThroughputPublishing; /** * licence Apache-2.0 diff --git a/packages/Sqs/tests/Integration/AsyncPublishingReliabilityTest.php b/packages/Sqs/tests/Integration/HighThroughputPublishingReliabilityTest.php similarity index 84% rename from packages/Sqs/tests/Integration/AsyncPublishingReliabilityTest.php rename to packages/Sqs/tests/Integration/HighThroughputPublishingReliabilityTest.php index ed9707cac..7bb0258ac 100644 --- a/packages/Sqs/tests/Integration/AsyncPublishingReliabilityTest.php +++ b/packages/Sqs/tests/Integration/HighThroughputPublishingReliabilityTest.php @@ -8,7 +8,7 @@ use Ecotone\Lite\Test\FlowTestSupport; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\MessagePublisher; @@ -25,14 +25,14 @@ * licence Apache-2.0 * @internal */ -final class AsyncPublishingReliabilityTest extends ConnectionTestCase +final class HighThroughputPublishingReliabilityTest extends ConnectionTestCase { public function test_broker_rejected_batch_fails_async_publish_on_future_resolve(): void { $messaging = $this->bootstrapPublisher(Uuid::v7()->toRfc4122()); $publisher = $messaging->getGateway(MessagePublisher::class); - $future = $publisher->asyncPublish( + $future = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('valid order') ->append(str_repeat('x', 300_000)) @@ -63,9 +63,9 @@ public function test_one_failing_batch_among_many_published_in_command_handler_f #[CommandHandler('order.placeAllBatches')] public function handle(string $order, #[Reference(MessagePublisher::class)] MessagePublisher $publisher): void { - $publisher->asyncPublish(BatchMessage::constructEmpty()->append($order . ' first valid order')); - $publisher->asyncPublish(BatchMessage::constructEmpty()->append(str_repeat('x', 300_000))); - $publisher->asyncPublish(BatchMessage::constructEmpty()->append($order . ' third valid order')); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append($order . ' first valid order')); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append(str_repeat('x', 300_000))); + $publisher->publishDeferred(BatchMessage::constructEmpty()->append($order . ' third valid order')); } }; $messaging = EcotoneLite::bootstrapFlowTesting( @@ -75,7 +75,7 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::SQS_PACKAGE])) ->withExtensionObjects([ SqsMessagePublisherConfiguration::create(queueName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(timeoutInMilliseconds: 10000), + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 10000), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); @@ -94,7 +94,7 @@ private function bootstrapPublisher(string $queueName): FlowTestSupport ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::SQS_PACKAGE])) ->withExtensionObjects([ SqsMessagePublisherConfiguration::create(queueName: $queueName) - ->withAsyncPublishing(timeoutInMilliseconds: 10000), + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 10000), ]), licenceKey: LicenceTesting::VALID_LICENCE, ); diff --git a/packages/Sqs/tests/Integration/AsyncPublishingTest.php b/packages/Sqs/tests/Integration/HighThroughputPublishingTest.php similarity index 85% rename from packages/Sqs/tests/Integration/AsyncPublishingTest.php rename to packages/Sqs/tests/Integration/HighThroughputPublishingTest.php index 492c746e1..ae2a13221 100644 --- a/packages/Sqs/tests/Integration/AsyncPublishingTest.php +++ b/packages/Sqs/tests/Integration/HighThroughputPublishingTest.php @@ -9,7 +9,7 @@ use Ecotone\Messaging\Attribute\Asynchronous; use Ecotone\Messaging\Attribute\Parameter\Reference; use Ecotone\Messaging\BatchMessage; -use Ecotone\Messaging\Channel\AsyncPublishing\PublishingFailedException; +use Ecotone\Messaging\Channel\DeliveryConfirmation\PublishingFailedException; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; @@ -26,15 +26,15 @@ use Enqueue\Sqs\SqsConnectionFactory; use Symfony\Component\Uid\Uuid; use Test\Ecotone\Sqs\ConnectionTestCase; -use Test\Ecotone\Sqs\Fixture\AsyncPublishing\OrderWasPlaced; +use Test\Ecotone\Sqs\Fixture\HighThroughputPublishing\OrderWasPlaced; /** * licence Apache-2.0 * @internal */ -final class AsyncPublishingTest extends ConnectionTestCase +final class HighThroughputPublishingTest extends ConnectionTestCase { - public function test_multiple_messages_published_asynchronously_from_command_handler_are_delivered(): void + public function test_multiple_messages_published_from_command_handler_are_delivered(): void { $orderService = $this->createOrderService(); $messaging = $this->bootstrapEcotoneWithChannel($orderService, LicenceTesting::VALID_LICENCE); @@ -50,7 +50,7 @@ public function test_multiple_messages_published_asynchronously_from_command_han $this->assertSame(['espresso-1', 'espresso-2', 'espresso-3'], $receivedEvents); } - public function test_async_publishing_requires_enterprise_licence(): void + public function test_high_throughput_publishing_requires_enterprise_licence(): void { $orderService = $this->createOrderService(); @@ -59,7 +59,7 @@ public function test_async_publishing_requires_enterprise_licence(): void $this->bootstrapEcotoneWithChannel($orderService, licenceKey: null); } - public function test_async_publishing_via_message_publisher_requires_enterprise_licence(): void + public function test_high_throughput_publishing_via_message_publisher_requires_enterprise_licence(): void { $this->expectException(LicensingException::class); @@ -70,20 +70,20 @@ public function test_async_publishing_via_message_publisher_requires_enterprise_ ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::SQS_PACKAGE])) ->withExtensionObjects([ SqsMessagePublisherConfiguration::create(queueName: Uuid::v7()->toRfc4122()) - ->withAsyncPublishing(), + ->withHighThroughputPublishing(), ]), ); } - public function test_async_publish_on_publisher_without_async_configuration_throws_before_publishing(): void + public function test_publish_deferred_on_publisher_without_non_blocking_confirmation_throws_before_publishing(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: false); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: false); $publisher = $messaging->getGateway(MessagePublisher::class); $publishFailed = false; try { - $publisher->asyncPublish('order that must not be published'); + $publisher->publishDeferred('order that must not be published'); } catch (PublishingFailedException) { $publishFailed = true; } @@ -92,14 +92,14 @@ public function test_async_publish_on_publisher_without_async_configuration_thro $this->assertNull($messaging->getMessageChannel($queueName)->receive()); } - public function test_message_publisher_async_publish_confirms_delivery_on_future_resolve(): void + public function test_publish_deferred_confirms_delivery_on_future_resolve(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); - $singleFuture = $publisher->asyncPublish('single order'); - $batchFuture = $publisher->asyncPublish( + $singleFuture = $publisher->publishDeferred('single order'); + $batchFuture = $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('first order') ->append('second order', ['priority' => '5']) @@ -137,7 +137,7 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE, ModulePackageList::SQS_PACKAGE])) ->withExtensionObjects([ SqsMessagePublisherConfiguration::create(queueName: $queueName) - ->withAsyncPublishing(timeoutInMilliseconds: 10000), + ->withHighThroughputPublishing(confirmationTimeoutInMilliseconds: 10000), SqsBackedMessageChannelBuilder::create($queueName), ]), licenceKey: LicenceTesting::VALID_LICENCE, @@ -156,7 +156,7 @@ public function handle(string $order, #[Reference(MessagePublisher::class)] Mess public function test_batch_larger_than_ten_messages_is_chunked_and_delivered(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); $batch = BatchMessage::constructEmpty(); @@ -164,7 +164,7 @@ public function test_batch_larger_than_ten_messages_is_chunked_and_delivered(): $batch = $batch->append('order ' . $orderNumber); } - $this->assertNull($publisher->asyncPublish($batch)->resolve()); + $this->assertNull($publisher->publishDeferred($batch)->resolve()); $receivedPayloads = []; while ($message = $messaging->getMessageChannel($queueName)->receive()) { @@ -176,11 +176,11 @@ public function test_batch_larger_than_ten_messages_is_chunked_and_delivered(): public function test_delayed_entry_of_published_batch_is_delivered_after_delay(): void { $queueName = Uuid::v7()->toRfc4122(); - $messaging = $this->bootstrapPublisher($queueName, asyncPublishing: true); + $messaging = $this->bootstrapPublisher($queueName, highThroughputPublishing: true); $publisher = $messaging->getGateway(MessagePublisher::class); $publishedAt = microtime(true); - $publisher->asyncPublish( + $publisher->publishDeferred( BatchMessage::constructEmpty() ->append('immediate order') ->append('delayed order', [MessageHeaders::DELIVERY_DELAY => 1000]) @@ -246,11 +246,11 @@ private function bootstrapEcotoneWithChannel(object $orderService, ?string $lice ); } - private function bootstrapPublisher(string $queueName, bool $asyncPublishing): FlowTestSupport + private function bootstrapPublisher(string $queueName, bool $highThroughputPublishing): FlowTestSupport { $publisherConfiguration = SqsMessagePublisherConfiguration::create(queueName: $queueName); - if ($asyncPublishing) { - $publisherConfiguration = $publisherConfiguration->withAsyncPublishing(); + if ($highThroughputPublishing) { + $publisherConfiguration = $publisherConfiguration->withHighThroughputPublishing(); } return EcotoneLite::bootstrapFlowTesting(