From 5c59ed69f3605b7c9eb5b6f0219edaa10f2d1532 Mon Sep 17 00:00:00 2001 From: Oguzhan Soykan Date: Tue, 29 Oct 2024 16:27:48 +0100 Subject: [PATCH] Ordered pipeline behaviour implementation fixes #270 --- .../com/trendyol/kediatr/MediatorImpl.kt | 2 +- .../com/trendyol/kediatr/PipelineBehavior.kt | 30 ++++- .../com/trendyol/kediatr/MediatorTests.kt | 8 +- .../kediatr/testing/MediatorUseCases.kt | 33 +++++ .../com/trendyol/kediatr/testing/models.kt | 113 +++++++++++++++++- .../trendyol/kediatr/koin/MediatorTests.kt | 12 ++ .../trendyol/kediatr/quarkus/MediatorTests.kt | 18 +++ .../trendyol/kediatr/spring/MediatorTests.kt | 8 +- .../trendyol/kediatr/spring/MediatorTests.kt | 8 +- 9 files changed, 222 insertions(+), 10 deletions(-) diff --git a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/MediatorImpl.kt b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/MediatorImpl.kt index 35eb824..8f4480f 100644 --- a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/MediatorImpl.kt +++ b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/MediatorImpl.kt @@ -46,7 +46,7 @@ class MediatorImpl( handler: RequestHandlerDelegate ): TResponse = pipelineBehaviors - .reversed() + .sortedByDescending { it.order } .fold(handler) { next, pipeline -> { pipeline.handle(request) { next(it) } } }(request) diff --git a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/PipelineBehavior.kt b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/PipelineBehavior.kt index 9ee863b..01122fa 100644 --- a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/PipelineBehavior.kt +++ b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/PipelineBehavior.kt @@ -3,9 +3,37 @@ package com.trendyol.kediatr /** * Interface to be implemented for a non-blocking pipeline behavior * - * @since 1.0.12 */ interface PipelineBehavior { + companion object { + /** + * Useful constant for the highest precedence value. + * @see java.lang.Integer.MIN_VALUE + */ + const val HIGHEST_PRECEDENCE = Int.MIN_VALUE + + /** + * Useful constant for the lowest precedence value. + * @see java.lang.Integer.MAX_VALUE + */ + const val LOWEST_PRECEDENCE = Int.MAX_VALUE + } + + /** + * Get the order value of this object. + * + * Higher values are interpreted as lower priority. As a consequence, + * the object with the lowest value has the highest priority. + * + * Same order values will result in arbitrary sort positions for the + * affected objects. + * @return the order value + * @see .HIGHEST_PRECEDENCE + * + * @see .LOWEST_PRECEDENCE + */ + val order: Int get() = HIGHEST_PRECEDENCE + /** * Process to invoke before handling any query, command or notification * diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt index 8245da3..5f68ed2 100644 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt +++ b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt @@ -31,7 +31,13 @@ class MediatorTests : MediatorUseCases() { ExceptionPipelineBehavior(), LoggingPipelineBehavior(), InheritedPipelineBehaviour(), - ParameterizedQueryHandler() + ParameterizedQueryHandler(), + FirstPipelineBehaviour(), + SecondPipelineBehaviour(), + ThirdPipelineBehaviour(), + CommandHandlerThatPassesThroughOrderedPipelineBehaviours(), + QueryHandlerThatPassesThroughOrderedPipelineBehaviours(), + NotificationHandlerThatPassesThroughOrderedPipelineBehaviours() ) ) diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt index deaad3c..e6a49f4 100644 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt @@ -208,4 +208,37 @@ abstract class MediatorUseCases : MediatorTestConvention() { result shouldBe "60" query.invocationCount() shouldBe 1 } + + @Test + fun ordered_pipeline_behaviours_should_be_executed_in_order_for_command() = runTest { + val command = CommandThatPassesThroughOrderedPipelineBehaviours() + testMediator.send(command) + command.visitedPipelines() shouldBe listOf( + FirstPipelineBehaviour::class.simpleName, + SecondPipelineBehaviour::class.simpleName, + ThirdPipelineBehaviour::class.simpleName + ) + } + + @Test + fun ordered_pipeline_behaviours_should_be_executed_in_order_for_query() = runTest { + val query = QueryThatPassesThroughOrderedPipelineBehaviours() + testMediator.send(query) + query.visitedPipelines() shouldBe listOf( + FirstPipelineBehaviour::class.simpleName, + SecondPipelineBehaviour::class.simpleName, + ThirdPipelineBehaviour::class.simpleName + ) + } + + @Test + fun ordered_pipeline_behaviours_should_be_executed_in_order_for_notification() = runTest { + val notification = NotificationThatPassesThroughOrderedPipelineBehaviours() + testMediator.publish(notification) + notification.visitedPipelines() shouldBe listOf( + FirstPipelineBehaviour::class.simpleName, + SecondPipelineBehaviour::class.simpleName, + ThirdPipelineBehaviour::class.simpleName + ) + } } diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt index b1a8a0d..5978cef 100644 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt @@ -152,7 +152,12 @@ class TestCommandWithResultCommandHandler(val mediator: MediatorAccessor) : Comm } } -class CommandThatPassesThroughPipelineBehaviours : Command, EnrichedWithMetadata() +class CommandThatPassesThroughPipelineBehaviours : + Command, + EnrichedWithMetadata(), + CanPassLoggingPipelineBehaviour, + CanPassExceptionPipelineBehaviour, + CanPassInheritedPipelineBehaviour class TestPipelineCommandHandler( private val mediator: MediatorAccessor @@ -163,7 +168,12 @@ class TestPipelineCommandHandler( } } -class CommandForWithoutInjectionThatPassesThroughPipelineBehaviours : Command, EnrichedWithMetadata() +class CommandForWithoutInjectionThatPassesThroughPipelineBehaviours : + Command, + EnrichedWithMetadata(), + CanPassLoggingPipelineBehaviour, + CanPassExceptionPipelineBehaviour, + CanPassInheritedPipelineBehaviour class TestPipelineCommandHandlerWithoutInjection : CommandHandler { override suspend fun handle(command: CommandForWithoutInjectionThatPassesThroughPipelineBehaviours) { @@ -293,6 +303,7 @@ class ParameterizedQueryHandler : QueryHandler handle( @@ -300,7 +311,10 @@ class ExceptionPipelineBehavior : PipelineBehavior { next: RequestHandlerDelegate ): TResponse = try { when (request) { - is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + is CanPassExceptionPipelineBehaviour -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } } next(request) } catch (ex: Exception) { @@ -308,13 +322,18 @@ class ExceptionPipelineBehavior : PipelineBehavior { } } +interface CanPassLoggingPipelineBehaviour + class LoggingPipelineBehavior : PipelineBehavior { override suspend fun handle( request: TRequest, next: RequestHandlerDelegate ): TResponse { when (request) { - is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + is CanPassLoggingPipelineBehaviour -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } } return next(request) } @@ -322,13 +341,97 @@ class LoggingPipelineBehavior : PipelineBehavior { abstract class MyBasePipelineBehaviour : PipelineBehavior +interface CanPassInheritedPipelineBehaviour + class InheritedPipelineBehaviour : MyBasePipelineBehaviour() { override suspend fun handle( request: TRequest, next: RequestHandlerDelegate ): TResponse { when (request) { - is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + is CanPassInheritedPipelineBehaviour -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } + } + return next(request) + } +} + +interface OrderedPipelineUseCase + +class CommandThatPassesThroughOrderedPipelineBehaviours : Command, EnrichedWithMetadata(), OrderedPipelineUseCase + +class QueryThatPassesThroughOrderedPipelineBehaviours : Query, EnrichedWithMetadata(), OrderedPipelineUseCase + +class NotificationThatPassesThroughOrderedPipelineBehaviours : Notification, EnrichedWithMetadata(), OrderedPipelineUseCase + +class CommandHandlerThatPassesThroughOrderedPipelineBehaviours : CommandHandler { + override suspend fun handle(command: CommandThatPassesThroughOrderedPipelineBehaviours) { + command.incrementInvocationCount() + } +} + +class QueryHandlerThatPassesThroughOrderedPipelineBehaviours : QueryHandler { + override suspend fun handle(query: QueryThatPassesThroughOrderedPipelineBehaviours): String { + query.incrementInvocationCount() + return "hello" + } +} + +class NotificationHandlerThatPassesThroughOrderedPipelineBehaviours : + NotificationHandler { + override suspend fun handle(notification: NotificationThatPassesThroughOrderedPipelineBehaviours) { + notification.incrementInvocationCount() + } +} + +class FirstPipelineBehaviour : PipelineBehavior { + override val order: Int = 1 + + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse { + when (request) { + is OrderedPipelineUseCase -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } + } + return next(request) + } +} + +class SecondPipelineBehaviour : PipelineBehavior { + override val order: Int = 2 + + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse { + when (request) { + is OrderedPipelineUseCase -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } + } + return next(request) + } +} + +class ThirdPipelineBehaviour : PipelineBehavior { + override val order: Int = 3 + + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse { + when (request) { + is OrderedPipelineUseCase -> { + request as EnrichedWithMetadata + request.visitedPipeline(this::class.java.simpleName) + } } return next(request) } diff --git a/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt b/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt index f7c0a31..d3bc08e 100644 --- a/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt +++ b/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt @@ -14,9 +14,16 @@ class MediatorTests : KoinTest, MediatorUseCases() { modules( module { single { KediatRKoin.getMediator() } + + // Pipeline behaviours single { InheritedPipelineBehaviour() } single { ExceptionPipelineBehavior() } single { LoggingPipelineBehavior() } + single { FirstPipelineBehaviour() } + single { SecondPipelineBehaviour() } + single { ThirdPipelineBehaviour() } + + // Handlers single { TestCommandHandler(get()) } single { TestCommandWithResultCommandHandler(get()) } bind CommandWithResultHandler::class single { TestQueryHandler(get()) } bind QueryHandler::class @@ -40,6 +47,11 @@ class MediatorTests : KoinTest, MediatorUseCases() { single { TestPipelineCommandHandlerWithoutInjection() } bind CommandHandler::class single { TestPipelineCommandHandlerThatFails() } bind CommandHandler::class single { ParameterizedQueryHandler() } bind QueryHandler::class + single { CommandHandlerThatPassesThroughOrderedPipelineBehaviours() } bind CommandHandler::class + single { QueryHandlerThatPassesThroughOrderedPipelineBehaviours() } bind QueryHandler::class + single { NotificationHandlerThatPassesThroughOrderedPipelineBehaviours() } bind NotificationHandler::class + + // Extra single { { get() } } } ) diff --git a/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt b/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt index 4ca5c11..020e836 100644 --- a/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt +++ b/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt @@ -90,4 +90,22 @@ class MediatorTests : MediatorUseCases() { @Produces fun handler22() = ParameterizedQueryHandler() + + @Produces + fun pipeline4() = FirstPipelineBehaviour() + + @Produces + fun pipeline5() = SecondPipelineBehaviour() + + @Produces + fun pipeline6() = ThirdPipelineBehaviour() + + @Produces + fun handler23() = CommandHandlerThatPassesThroughOrderedPipelineBehaviours() + + @Produces + fun handler24() = QueryHandlerThatPassesThroughOrderedPipelineBehaviours() + + @Produces + fun handler25() = NotificationHandlerThatPassesThroughOrderedPipelineBehaviours() } diff --git a/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt b/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt index 4c224e5..a197d31 100644 --- a/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt +++ b/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt @@ -35,7 +35,13 @@ import org.springframework.context.annotation.* TestPipelineCommandHandlerWithoutInjection::class, TestPipelineCommandHandlerThatFails::class, InheritedPipelineBehaviour::class, - ParameterizedQueryHandler::class + ParameterizedQueryHandler::class, + FirstPipelineBehaviour::class, + SecondPipelineBehaviour::class, + ThirdPipelineBehaviour::class, + CommandHandlerThatPassesThroughOrderedPipelineBehaviours::class, + QueryHandlerThatPassesThroughOrderedPipelineBehaviours::class, + NotificationHandlerThatPassesThroughOrderedPipelineBehaviours::class ] ) class MediatorTests : MediatorUseCases() { diff --git a/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt b/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt index 4c224e5..a197d31 100644 --- a/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt +++ b/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt @@ -35,7 +35,13 @@ import org.springframework.context.annotation.* TestPipelineCommandHandlerWithoutInjection::class, TestPipelineCommandHandlerThatFails::class, InheritedPipelineBehaviour::class, - ParameterizedQueryHandler::class + ParameterizedQueryHandler::class, + FirstPipelineBehaviour::class, + SecondPipelineBehaviour::class, + ThirdPipelineBehaviour::class, + CommandHandlerThatPassesThroughOrderedPipelineBehaviours::class, + QueryHandlerThatPassesThroughOrderedPipelineBehaviours::class, + NotificationHandlerThatPassesThroughOrderedPipelineBehaviours::class ] ) class MediatorTests : MediatorUseCases() {