From 50c387368a2a3cc7e282cd48a4d8f9ae98684424 Mon Sep 17 00:00:00 2001 From: kasfandiyarov Date: Thu, 29 Aug 2019 09:36:49 +0300 Subject: [PATCH 1/3] propose new api for context encapsulation --- .../reactor/example/PurchaseGraph.java | 66 +++++++++++++++---- ...rchasePayload.java => PurchaseSubmit.java} | 49 ++------------ .../reactor/example/SubscribeGraph.kt | 35 ++++++---- ...SubscribePayload.kt => SubscribeSubmit.kt} | 16 +---- .../reactor/example/PurchaseGraphTest.java | 28 +++++--- .../reactor/example/SubscribeGraphTest.kt | 43 ++++++------ .../kotlin/CompletableReactorExtension.kt | 12 ++++ .../graph/kotlin/{Graph.kt => KGraph.kt} | 57 ++++++++-------- .../reactor/graph/kotlin/KotlinGraphTest.kt | 46 +++++++------ .../completable/reactor/graph/Payload.java | 7 ++ .../reactor/graph/EmptyContext.java | 6 ++ .../completable/reactor/graph/Execution.kt | 14 ++++ .../reactor/graph/{Graph.kt => JGraph.kt} | 37 ++++++----- .../fix/completable/reactor/graph/Subgraph.kt | 6 +- .../fix/completable/reactor/graph/Submit.kt | 6 ++ .../reactor/graph/runtime/RuntimeVertex.kt | 6 +- .../reactor/runtime/CompletableReactor.java | 66 +++++++++++-------- 17 files changed, 288 insertions(+), 212 deletions(-) rename completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/{PurchasePayload.java => PurchaseSubmit.java} (60%) rename completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/{SubscribePayload.kt => SubscribeSubmit.kt} (53%) create mode 100644 completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/CompletableReactorExtension.kt rename completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/{Graph.kt => KGraph.kt} (61%) create mode 100644 completable-reactor-graph/src/main/java/ru/fix/completable/reactor/graph/Payload.java create mode 100644 completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/EmptyContext.java create mode 100644 completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Execution.kt rename completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/{Graph.kt => JGraph.kt} (71%) create mode 100644 completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Submit.kt diff --git a/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseGraph.java b/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseGraph.java index fafd4b0b..fb6c81b4 100644 --- a/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseGraph.java +++ b/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseGraph.java @@ -3,7 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import ru.fix.completable.reactor.example.services.*; -import ru.fix.completable.reactor.graph.Graph; +import ru.fix.completable.reactor.graph.JGraph; import ru.fix.completable.reactor.graph.Vertex; import ru.fix.completable.reactor.runtime.CompletableReactor; @@ -15,7 +15,45 @@ * # Defines purchase process when user buys good in the shop. */ @Configuration -public class PurchaseGraph extends Graph { +public class PurchaseGraph extends JGraph< + PurchaseSubmit, + PurchaseSubmit.Request, + PurchaseSubmit.Response, + PurchaseGraph.Context> { + + class Context { + ServiceInfo serviceInfo; + UserProfile userInfo; + Long bonusService; + + public ServiceInfo getServiceInfo() { + return serviceInfo; + } + + public Context setServiceInfo(ServiceInfo serviceInfo) { + this.serviceInfo = serviceInfo; + return this; + } + + public UserProfile getUserInfo() { + return userInfo; + } + + public Context setUserInfo(UserProfile userInfo) { + this.userInfo = userInfo; + return this; + } + + public Long getBonusService() { + return bonusService; + } + + public Context setBonusService(Long bonusService) { + this.bonusService = bonusService; + return this; + } + } + @Autowired UserProfileManager userProfileManager; @@ -67,7 +105,7 @@ public void initialize() { return Flow.STOP; case OK: - pld.intermediateData.setUserInfo(result.userProfile); + pld.context.setUserInfo(result.userProfile); return Flow.CONTINUE; } throw new IllegalArgumentException("result.status = " + result.status); @@ -105,8 +143,8 @@ Vertex logTransaction() { User will end up with negative balance after this operation. */ pld -> bank.withdrawMoneyWithMinus( - pld.intermediateData.getUserInfo(), - pld.intermediateData.getServiceInfo()) + pld.context.getUserInfo(), + pld.context.getServiceInfo()) ).withRoutingMerger( /* # CheckWithdraw @@ -138,7 +176,7 @@ Vertex logTransaction() { Update payload response. */ pld -> { - if (pld.intermediateData.serviceInfo.isPartnerService()) { + if (pld.context.serviceInfo.isPartnerService()) { pld.response.isPartnerService = true; } }); @@ -163,7 +201,7 @@ Vertex logTransaction() { pld.response.setStatus(result.getStatus()); return Flow.STOP; case OK: - pld.intermediateData.setServiceInfo(result.getServiceInfo()); + pld.context.setServiceInfo(result.getServiceInfo()); if (result.getServiceInfo().isActive()) { return Flow.WITHDRAWAL; @@ -180,7 +218,7 @@ Vertex logTransaction() { Optional bonus = marketingService.checkBonuses(pld.request.userId, pld.request.serviceId); if (bonus.isPresent()) { - pld.intermediateData.bonusService = bonus.get(); + pld.context.bonusService = bonus.get(); return Flow.BONUS_EXIST; } else { return Flow.NO_BONUS; @@ -190,17 +228,17 @@ Vertex logTransaction() { Vertex bonusPurchaseSubgraph = subgraph( //# Make bonus purchase - PurchasePayload.class, + PurchaseSubmit.class, pld -> { - PurchasePayload subgraphRequest = new PurchasePayload(); - subgraphRequest.request + PurchaseSubmit.Request request = new PurchaseSubmit.Request(); + request .setServiceId(107L) .setUserId(pld.request.userId); - return subgraphRequest; + return request; } - ).withMerger((pld, subgraphResult) -> - pld.response.bonusServiceStatus = subgraphResult.response.status + ).withMerger((pld, subgraphResponse) -> + pld.response.bonusServiceStatus = subgraphResponse.status ); { diff --git a/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchasePayload.java b/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseSubmit.java similarity index 60% rename from completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchasePayload.java rename to completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseSubmit.java index 422f9a22..ef64abc4 100644 --- a/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchasePayload.java +++ b/completable-reactor-example/src/main/java/ru/fix/completable/reactor/example/PurchaseSubmit.java @@ -1,17 +1,16 @@ package ru.fix.completable.reactor.example; -import ru.fix.completable.reactor.example.services.ServiceInfo; -import ru.fix.completable.reactor.example.services.UserProfile; +import ru.fix.completable.reactor.graph.Submit; import java.math.BigDecimal; /** * @author Kamil Asfandiyarov */ -public class PurchasePayload { +public interface PurchaseSubmit extends Submit { - public static class Request { + class Request { Long userId; Long serviceId; @@ -34,7 +33,7 @@ public Request setServiceId(Long serviceId) { } } - public static class Response { + class Response { Enum status; BigDecimal newAmount; boolean withdrawalWasInMinus; @@ -86,44 +85,4 @@ public Response setPartnerService(boolean partnerService) { return this; } } - - public static class IntermediateData { - ServiceInfo serviceInfo; - UserProfile userInfo; - Long bonusService; - - public ServiceInfo getServiceInfo() { - return serviceInfo; - } - - public IntermediateData setServiceInfo(ServiceInfo serviceInfo) { - this.serviceInfo = serviceInfo; - return this; - } - - public UserProfile getUserInfo() { - return userInfo; - } - - public IntermediateData setUserInfo(UserProfile userInfo) { - this.userInfo = userInfo; - return this; - } - - public Long getBonusService() { - return bonusService; - } - - public IntermediateData setBonusService(Long bonusService) { - this.bonusService = bonusService; - return this; - } - - - } - - public final Request request = new Request(); - public final IntermediateData intermediateData = new IntermediateData(); - public final Response response = new Response(); - } diff --git a/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeGraph.kt b/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeGraph.kt index aa9f836c..2de9516a 100644 --- a/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeGraph.kt +++ b/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeGraph.kt @@ -4,7 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Configuration import ru.fix.completable.reactor.example.services.* import ru.fix.completable.reactor.example.services.UserProfileManager.Status -import ru.fix.completable.reactor.graph.kotlin.Graph +import ru.fix.completable.reactor.graph.kotlin.KGraph import ru.fix.completable.reactor.runtime.CompletableReactor import javax.annotation.PostConstruct @@ -14,7 +14,17 @@ import javax.annotation.PostConstruct * Created by Kamil Asfandiyarov */ @Configuration -open class SubscribeGraph : Graph() { +open class SubscribeGraph : KGraph< + SubscribeSubmit, + SubscribeSubmit.Request, + SubscribeSubmit.Response, + SubscribeGraph.Context>() { + + class Context { + lateinit var serviceInfo: ServiceInfo + lateinit var accountInfo: AccountInfo + lateinit var userInfo: UserProfile + } @Autowired lateinit var userProfile: UserProfileManager @@ -63,7 +73,7 @@ open class SubscribeGraph : Graph() { } Status.OK -> { - intermediateData.userInfo = it.userProfile + context.userInfo = it.userProfile Flow.CONTINUE } @@ -114,8 +124,8 @@ open class SubscribeGraph : Graph() { val withdrawMoney = handler { bank.withdrawMoney( - intermediateData.userInfo, - intermediateData.serviceInfo) + context.userInfo, + context.serviceInfo) }.withRoutingMerger { //update new amount @@ -145,7 +155,7 @@ open class SubscribeGraph : Graph() { Flow.STOP } ServiceRegistry.Status.OK -> { - intermediateData.serviceInfo = result.serviceInfo + context.serviceInfo = result.serviceInfo if (result.serviceInfo.isActive) { Flow.CONTINUE @@ -160,7 +170,7 @@ open class SubscribeGraph : Graph() { val checkTrialPeriod = router { // Checks whether service supports trial period - if (intermediateData.serviceInfo.isSupportTrialPeriod) { + if (context.serviceInfo.isSupportTrialPeriod) { Flow.NO_WITHDRAWAL } else { Flow.WITHDRAWAL @@ -168,16 +178,15 @@ open class SubscribeGraph : Graph() { } val bonusPurchaseSubgraph = - subgraph(PurchasePayload::class) { + subgraph(PurchaseSubmit::class) { //# Make bonus purchase - PurchasePayload().apply { - request - .setServiceId(107L) - .setUserId(request.userId) + PurchaseSubmit.Request().apply { + setServiceId(107L) + setUserId(request.userId) } }.withMerger { - response.bonusPurchaseStatus = it.response.status + response.bonusPurchaseStatus = it.status Flow.CONTINUE } diff --git a/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribePayload.kt b/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeSubmit.kt similarity index 53% rename from completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribePayload.kt rename to completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeSubmit.kt index 8972698d..b47b48bd 100644 --- a/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribePayload.kt +++ b/completable-reactor-example/src/main/kotlin/ru/fix/completable/reactor/example/SubscribeSubmit.kt @@ -1,18 +1,12 @@ package ru.fix.completable.reactor.example; -import ru.fix.completable.reactor.example.services.ServiceInfo; -import ru.fix.completable.reactor.example.services.UserProfile; -import ru.fix.completable.reactor.example.services.AccountInfo; +import ru.fix.completable.reactor.graph.Submit import java.math.BigDecimal /** * Created by swarmshine on 16.10.2016. */ -data class SubscribePayload(val request: Request) { - - val intermediateData = IntermediateData() - val response = Response() - +interface SubscribeSubmit: Submit { data class Request( val userId: Long, val serviceId: Long @@ -26,10 +20,4 @@ data class SubscribePayload(val request: Request) { var bonusPurchaseStatus: Enum<*>? = null, var remoteServiceNotificationResult: Boolean = false ) - - class IntermediateData { - lateinit var serviceInfo: ServiceInfo - lateinit var accountInfo: AccountInfo - lateinit var userInfo: UserProfile - } } diff --git a/completable-reactor-example/src/test/java/ru/fix/completable/reactor/example/PurchaseGraphTest.java b/completable-reactor-example/src/test/java/ru/fix/completable/reactor/example/PurchaseGraphTest.java index 01c39b5a..2ad43d45 100644 --- a/completable-reactor-example/src/test/java/ru/fix/completable/reactor/example/PurchaseGraphTest.java +++ b/completable-reactor-example/src/test/java/ru/fix/completable/reactor/example/PurchaseGraphTest.java @@ -78,24 +78,32 @@ CompletableReactor reactor() { CompletableReactor reactor; @Test() - public void invalid_user_purchases_invalid_service() throws Exception { + void invalid_user_purchases_invalid_service() throws Exception { - PurchasePayload payload = new PurchasePayload(); - payload.request.setUserId(UserProfileManager.USER_ID_INVALID).setServiceId(ServiceRegistry.SERVICE_ID_INVALID); + PurchaseSubmit.Request request = new PurchaseSubmit.Request() + .setUserId(UserProfileManager.USER_ID_INVALID) + .setServiceId(ServiceRegistry.SERVICE_ID_INVALID); - PurchasePayload result = reactor.submit(payload).getResultFuture().get(5, TimeUnit.SECONDS); + PurchaseSubmit.Response response = reactor.graph(PurchaseSubmit.class) + .submit(request) + .getResultFuture() + .get(5, TimeUnit.SECONDS); - assertEquals(UserProfileManager.Status.USER_NOT_FOUND, result.response.getStatus()); + assertEquals(UserProfileManager.Status.USER_NOT_FOUND, response.getStatus()); } @Test - public void john_purchase_car_wash() throws Exception { - PurchasePayload payload = new PurchasePayload(); - payload.request.setUserId(UserProfileManager.USER_ID_JOHN).setServiceId(ServiceRegistry.SERVICE_ID_CAR_WASH); + void john_purchase_car_wash() throws Exception { + PurchaseSubmit.Request request = new PurchaseSubmit.Request() + .setUserId(UserProfileManager.USER_ID_JOHN) + .setServiceId(ServiceRegistry.SERVICE_ID_CAR_WASH); - PurchasePayload result = reactor.submit(payload).getResultFuture().get(5, TimeUnit.SECONDS); + PurchaseSubmit.Response response = reactor.graph(PurchaseSubmit.class) + .submit(request) + .getResultFuture() + .get(5, TimeUnit.SECONDS); - assertEquals(Bank.Withdraw.Status.OK, result.response.getStatus()); + assertEquals(Bank.Withdraw.Status.OK, response.getStatus()); } } \ No newline at end of file diff --git a/completable-reactor-example/src/test/kotlin/ru/fix/completable/reactor/example/SubscribeGraphTest.kt b/completable-reactor-example/src/test/kotlin/ru/fix/completable/reactor/example/SubscribeGraphTest.kt index d56211b3..957cbf01 100644 --- a/completable-reactor-example/src/test/kotlin/ru/fix/completable/reactor/example/SubscribeGraphTest.kt +++ b/completable-reactor-example/src/test/kotlin/ru/fix/completable/reactor/example/SubscribeGraphTest.kt @@ -11,6 +11,7 @@ import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.junit.jupiter.SpringExtension import ru.fix.aggregating.profiler.AggregatingProfiler import ru.fix.completable.reactor.example.services.* +import ru.fix.completable.reactor.graph.kotlin.graph import ru.fix.completable.reactor.runtime.CompletableReactor import java.util.concurrent.TimeUnit @@ -66,9 +67,9 @@ open class SubscribeGraphTest { @BeforeEach fun before(){ - reactor.registerGraphSync(PurchasePayload::class.java){ - it.apply{ - response.status = Bank.Withdraw.Status.OK + reactor.registerGraphImplementation(PurchaseSubmit::class.java){ + PurchaseSubmit.Response().apply { + status = Bank.Withdraw.Status.OK } } } @@ -77,44 +78,48 @@ open class SubscribeGraphTest { @Test fun invalid_user_subscribes_on_invalid_service() { - val payload = SubscribePayload(SubscribePayload.Request( + val request = SubscribeSubmit.Request( userId = UserProfileManager.USER_ID_INVALID, serviceId = ServiceRegistry.SERVICE_ID_INVALID - )) + ) - val result = reactor.submit(payload).resultFuture.get(5, TimeUnit.SECONDS) + val response = reactor.graph() + .submit(request) + .resultFuture.get(5, TimeUnit.SECONDS) - assertEquals(UserProfileManager.Status.USER_NOT_FOUND, result.response.status) + assertEquals(UserProfileManager.Status.USER_NOT_FOUND, response.status) } @Test fun john_subscribes_on_car_wash() { - val payload = SubscribePayload(SubscribePayload.Request( + val request = SubscribeSubmit.Request( userId = UserProfileManager.USER_ID_JOHN, serviceId = ServiceRegistry.SERVICE_ID_CAR_WASH - )) + ) - val execution = reactor.submit(payload) + val response = reactor.graph() + .submit(request) + .resultFuture + .get(5, TimeUnit.SECONDS) - val result = execution.resultFuture.get(5, TimeUnit.SECONDS) - - assertEquals(Bank.Withdraw.Status.OK, result.response.status) + assertEquals(Bank.Withdraw.Status.OK, response.status) } @Test fun bob_wallet_not_found() { - val payload = SubscribePayload(SubscribePayload.Request( + val request = SubscribeSubmit.Request( userId = UserProfileManager.USER_WITH_NO_WALLET, serviceId = ServiceRegistry.SERVICE_ID_CAR_WASH - )) - - val execution = reactor.submit(payload) + ) - val result = execution.resultFuture.get(5, TimeUnit.SECONDS) + val response = reactor.graph() + .submit(request) + .resultFuture + .get(5, TimeUnit.SECONDS) - assertEquals(Bank.Withdraw.Status.WALLET_NOT_FOUND, result.response.status) + assertEquals(Bank.Withdraw.Status.WALLET_NOT_FOUND, response.status) } } \ No newline at end of file diff --git a/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/CompletableReactorExtension.kt b/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/CompletableReactorExtension.kt new file mode 100644 index 00000000..0c156a5e --- /dev/null +++ b/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/CompletableReactorExtension.kt @@ -0,0 +1,12 @@ +package ru.fix.completable.reactor.graph.kotlin + +import ru.fix.completable.reactor.graph.Submit +import ru.fix.completable.reactor.runtime.CompletableReactor +import kotlin.reflect.KClass + +inline fun > CompletableReactor.graph(): Submit = + this.graph(Submit::class.java) + +fun CompletableReactor.registerGraphImplementation( + submitType: KClass>, + implementation: (Request) -> Response) = this.registerGraphImplementation(submitType.java, implementation) diff --git a/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/Graph.kt b/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/KGraph.kt similarity index 61% rename from completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/Graph.kt rename to completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/KGraph.kt index a337c7ee..95ca2441 100644 --- a/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/Graph.kt +++ b/completable-reactor-graph-kotlin/src/main/kotlin/ru/fix/completable/reactor/graph/kotlin/KGraph.kt @@ -12,10 +12,15 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.ForkJoinPool import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass +//TODO docs how to use +open class KGraph< + Submit : ru.fix.completable.reactor.graph.Submit, + Request, + Response, + Context> : Graphable { -open class Graph : Graphable { companion object { - private val log = LoggerFactory.getLogger(Graph::class.java) + private val log = LoggerFactory.getLogger(KGraph::class.java) //TODO: add ability to use different pool for completable reactor, update default value accordingly. /** * By default, Completable Reactor uses Common pool for execution. @@ -45,7 +50,7 @@ open class Graph : Graphable { private val graphBuilderValidator = GraphBuilderValidator() - protected fun payload(): PayloadTransitionBuilder { + protected fun payload(): PayloadTransitionBuilder> { return DslPayloadImpl(graph) } @@ -54,8 +59,8 @@ open class Graph : Graphable { } - protected fun handler(handler: Payload.() -> CompletableFuture): - MergerBuilder { + protected fun handler(handler: Payload.() -> CompletableFuture): + MergerBuilder, HandlerResult> { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) @@ -63,9 +68,8 @@ open class Graph : Graphable { graphBuilderValidator.validateHandler(vx) - @Suppress("UNCHECKED_CAST") - vx.handler = object : Handler { - override fun handle(payload: Payload): CompletableFuture { + vx.handler = object : Handler, HandlerResult> { + override fun handle(payload: Payload): CompletableFuture { return handler(payload) } } as Handler @@ -76,11 +80,13 @@ open class Graph : Graphable { /** * Builds new handler based on coroutine. * Coroutine context is inherited from a [coroutineScope]. - * By default [Graph.defaultCoroutineScope] will be used. - * Default [Graph.defaultCoroutineScope] could be redefined by user globally. + * By default [KGraph.defaultCoroutineScope] will be used. + * Default [KGraph.defaultCoroutineScope] could be redefined by user globally. */ - protected fun suspendHandler(coroutineScope: CoroutineScope? = null, handler: suspend Payload.() -> HandlerResult): - MergerBuilder { + protected fun suspendHandler( + coroutineScope: CoroutineScope? = null, + handler: suspend Payload.() -> HandlerResult): + MergerBuilder, HandlerResult> { return handler { (coroutineScope ?: defaultCoroutineScope).future { @@ -89,7 +95,7 @@ open class Graph : Graphable { } } - protected fun mutator(mutator: Payload.() -> Unit): Vertex { + protected fun mutator(mutator: Payload.() -> Unit): Vertex { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) graph.vertices.add(vx) @@ -99,8 +105,8 @@ open class Graph : Graphable { vx.handler = RuntimeEmptyHandler() @Suppress("UNCHECKED_CAST") vx.merger = RuntimeMutatorMerger( - object : Mutator { - override fun mutate(payload: Payload) { + object : Mutator> { + override fun mutate(payload: Payload) { mutator(payload) } } as Mutator) @@ -109,7 +115,7 @@ open class Graph : Graphable { return vertex } - protected fun router(router: Payload.() -> Enum<*>): Vertex { + protected fun router(router: Payload.() -> Enum<*>): Vertex { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) graph.vertices.add(vx) @@ -117,10 +123,9 @@ open class Graph : Graphable { graphBuilderValidator.validateRouter(vx) vx.handler = RuntimeEmptyHandler() - @Suppress("UNCHECKED_CAST") vx.merger = RuntimeRouterMerger( - object : Router { - override fun route(payload: Payload): Enum<*> { + object : Router> { + override fun route(payload: Payload): Enum<*> { return router(payload) } } as Router @@ -130,9 +135,10 @@ open class Graph : Graphable { return vertex } - protected fun subgraph( - subgraphPayloadType: KClass, - subgraph: Payload.() -> SubgraphPayload): MergerBuilder { + protected fun subgraph( + subgraphSubmitType: KClass>, + subgraph: Payload.() -> SubgraphRequest): + MergerBuilder, SubgraphResponse> { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) @@ -140,10 +146,9 @@ open class Graph : Graphable { graphBuilderValidator.validateSubgraph(vx) - vx.subgraphPayloadType = subgraphPayloadType.java - @Suppress("UNCHECKED_CAST") - vx.subgraphPayloadBuilder = object : Subgraph { - override fun subgraph(payload: Payload): SubgraphPayload { + vx.subgraphSubmitType = subgraphSubmitType.java + vx.subgraphRequestBuilder = object : Subgraph, SubgraphRequest> { + override fun subgraph(payload: Payload): SubgraphRequest { return subgraph(payload) } } as Subgraph diff --git a/completable-reactor-graph-kotlin/src/test/kotlin/ru/fix/completable/reactor/graph/kotlin/KotlinGraphTest.kt b/completable-reactor-graph-kotlin/src/test/kotlin/ru/fix/completable/reactor/graph/kotlin/KotlinGraphTest.kt index 2b506f43..73f938eb 100644 --- a/completable-reactor-graph-kotlin/src/test/kotlin/ru/fix/completable/reactor/graph/kotlin/KotlinGraphTest.kt +++ b/completable-reactor-graph-kotlin/src/test/kotlin/ru/fix/completable/reactor/graph/kotlin/KotlinGraphTest.kt @@ -13,6 +13,8 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import ru.fix.aggregating.profiler.AggregatingProfiler +import ru.fix.completable.reactor.graph.EmptyContext +import ru.fix.completable.reactor.graph.Submit import ru.fix.completable.reactor.runtime.CompletableReactor import java.util.* import java.util.concurrent.CompletableFuture.completedFuture @@ -54,11 +56,13 @@ class KotlinGraphTest { * When IdListPayload goes through processors chain each processor adds their id * so at the end we can clarify by witch processor and in what order this payload was processed. */ - open class IdListPayload { - val idSequence: MutableList = ArrayList() + interface IdListSubmit: Submit { + class Request + class Response { + val idSequence: MutableList = ArrayList() + } } - /** * Payload contains list of integer ids. * When payload goes through vertex chain each vertex adds their id. @@ -68,13 +72,13 @@ class KotlinGraphTest { * Test will check that single handler id end up in payload.", * Expected result: {1} */ - class SingleProcessorGraph : Graph() { + class SingleProcessorGraph : KGraph() { var idProcessor1 = handler { IdProcessor(1).handle() }.withMerger { - idSequence.add(it) + response.idSequence.add(it) } init { @@ -89,7 +93,7 @@ class KotlinGraphTest { reactor.registerGraphIfAbsent(SingleProcessorGraph::class.java) - val resultPayload = reactor.submit(IdListPayload()) + val resultPayload = reactor.graph(IdListSubmit) .resultFuture .get(10, SECONDS) @@ -100,7 +104,7 @@ class KotlinGraphTest { * Test will check that two processor ids end up at payloads idList in correct order. * Expected result: {1, 2} */ - class TwoProcessorSequentialMergeGraph : Graph() { + class TwoProcessorSequentialMergeGraph : KGraph() { var idProcessor1 = handler { IdProcessor(1).handle() } .withRoutingMerger { @@ -147,7 +151,7 @@ class KotlinGraphTest { * * Expected result: {1, 2} */ - class DetachedProcessorGraph : Graph() { + class DetachedProcessorGraph : KGraph() { var detachedProcessor = IdProcessor(3).withLaunchingLatch() @@ -210,7 +214,7 @@ class KotlinGraphTest { * Subgraph behave the same way as plain processor. * The only difference is that instead of simple async operation CompletableReactor launches subgraph execution. */ - class SubgraphPayloadGraph : Graph() { + class SubgraphPayloadGraph : KGraph() { var idProcessor11 = handler { IdProcessor(11).handle() } .withMerger { @@ -246,7 +250,7 @@ class KotlinGraphTest { * Parent graph is a simple graph that calls subgraph during its flow. * Expected result: 1, 11, 12, 13, 2, 3 */ - class ParentGraphPayloadGraph : Graph() { + class ParentGraphPayloadGraph : KGraph() { var idProcessor1 = handler { IdProcessor(1).handle() } .withMerger { idSequence.add(it) @@ -311,7 +315,7 @@ class KotlinGraphTest { * Expected result: {42, 1, 0} */ - class RouterFromStartPointGraph : Graph() { + class RouterFromStartPointGraph : KGraph() { var idProcessor0 = handler { IdProcessor(0).handle() } .withMerger { @@ -372,7 +376,7 @@ class KotlinGraphTest { * Router simply modify payload and send it through outgoing transitions. * Expected result: {0, 1, 42} */ - class RouterFromProcessorsMergerGraph : Graph() { + class RouterFromProcessorsMergerGraph : KGraph() { var idProcessor0 = handler { IdProcessor(0).handle() } .withMerger { @@ -438,7 +442,7 @@ class KotlinGraphTest { LEFT, RIGHT } - class OptionalProcessorExecutionGraph : Graph() { + class OptionalProcessorExecutionGraph : KGraph() { var idProcessor1 = handler { IdProcessor(1).handle() } .withMerger { idSequence.add(it) @@ -528,7 +532,7 @@ class KotlinGraphTest { If condition evaluated to false then this outgoing transitions marked as dead. If all incoming transitions to handler or subgraph marked as dead, then this handler or subgraph and it's merge point marked as dead. */ - class DeadTransitionBreaksFlowGraph : Graph() { + class DeadTransitionBreaksFlowGraph : KGraph() { fun idProcessor1Tempalte() = handler { IdProcessor(1).handle() } .withMerger { @@ -628,7 +632,7 @@ class KotlinGraphTest { * or subgraph merge point. * Expected result: {2, 0, 1, 3} */ - class StartPointRouterGraph : Graph() { + class StartPointRouterGraph : KGraph() { var mergePoint2Semaphore = Semaphore(0) @@ -717,7 +721,7 @@ class KotlinGraphTest { * Dead transition deactivates merger and all outgoing transitions * Expected result: {2, 0, 1, 3} */ - class DeadTransitionGraph : Graph() { + class DeadTransitionGraph : KGraph() { enum class Status { FIRST, SECOND, OK @@ -827,7 +831,7 @@ class KotlinGraphTest { * Test will check that single detached merge point id end up at payloads idList. * Expected result: {1} */ - class SingleRouterGraph : Graph() { + class SingleRouterGraph : KGraph() { var mergePoint = router { idSequence.add(1) @@ -859,7 +863,7 @@ class KotlinGraphTest { /** * Handler return null as a result. */ - class ReturnNullInHandlerGraph : Graph() { + class ReturnNullInHandlerGraph : KGraph() { val nullResultHandler = NullResultHandler() @@ -900,7 +904,7 @@ class KotlinGraphTest { } - class RiseExceptionForImplicitEmptyMergerWithOnTransitionGraph : Graph() { + class RiseExceptionForImplicitEmptyMergerWithOnTransitionGraph : KGraph() { enum class STATUS { OK } @@ -934,7 +938,7 @@ class KotlinGraphTest { val syncSeq:ConcurrentLinkedDeque = ConcurrentLinkedDeque() ) - class ImplicitEmptyMergerWithOnAnyTransitionGraph : Graph() { + class ImplicitEmptyMergerWithOnAnyTransitionGraph : KGraph() { val vertex11 = handler { syncSeq.add(11) @@ -983,7 +987,7 @@ class KotlinGraphTest { val syncSeq:ConcurrentLinkedDeque = ConcurrentLinkedDeque() ) - class ExceptionInSuspendHandlerGraph : Graph() { + class ExceptionInSuspendHandlerGraph : KGraph() { val vertex1 = suspendHandler { syncSeq.add(1) diff --git a/completable-reactor-graph/src/main/java/ru/fix/completable/reactor/graph/Payload.java b/completable-reactor-graph/src/main/java/ru/fix/completable/reactor/graph/Payload.java new file mode 100644 index 00000000..67d17f2e --- /dev/null +++ b/completable-reactor-graph/src/main/java/ru/fix/completable/reactor/graph/Payload.java @@ -0,0 +1,7 @@ +package ru.fix.completable.reactor.graph; +//TODO: doc +public class Payload { + public Request request; + public Response response; + public Context context; +} diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/EmptyContext.java b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/EmptyContext.java new file mode 100644 index 00000000..cc34a41c --- /dev/null +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/EmptyContext.java @@ -0,0 +1,6 @@ +package ru.fix.completable.reactor.graph; + +/** + * TODO: docs + */ +public class EmptyContext{} \ No newline at end of file diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Execution.kt b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Execution.kt new file mode 100644 index 00000000..c3f141dc --- /dev/null +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Execution.kt @@ -0,0 +1,14 @@ +package ru.fix.completable.reactor.graph + +import java.util.concurrent.CompletableFuture + +interface Execution{ + /** + * Will be completed with payload when terminal graph state would be reached. + */ + val resultFuture: CompletableFuture + /** + * Completes when all processes within given execution of single request is complete + */ + val executionFuture: CompletableFuture +} \ No newline at end of file diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Graph.kt b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/JGraph.kt similarity index 71% rename from completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Graph.kt rename to completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/JGraph.kt index 6752dbed..3896ba8c 100644 --- a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Graph.kt +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/JGraph.kt @@ -4,14 +4,14 @@ import ru.fix.completable.reactor.graph.internal.* import ru.fix.completable.reactor.graph.runtime.RuntimeGraph import ru.fix.completable.reactor.graph.runtime.RuntimeVertex import java.util.concurrent.CompletableFuture - +//TODO docs /** * Base class for Completable Reactor Graphs. * * Provides DSL builder methods to create Graph. * * ``` - * class MyGraph extends Graph { + * class MyGraph extends JGraph<> { * Vertex vx1 = handler(...).withMerger(...); * Vertex vx2 = router(...); * ... @@ -26,7 +26,11 @@ import java.util.concurrent.CompletableFuture * } * ``` */ -abstract class Graph : Graphable { +abstract class JGraph< + Submit: ru.fix.completable.reactor.graph.Submit, + Request, + Response, + Context> : Graphable { // Field accessed via reflection by field name private val graph = RuntimeGraph() @@ -42,7 +46,7 @@ abstract class Graph : Graphable { /** * Specifies StartPoint of the graph. Can be used to create transitions from StartPoint to Vertices. */ - protected fun payload(): PayloadTransitionBuilder { + protected fun payload(): PayloadTransitionBuilder> { return DslPayloadImpl(graph) } @@ -57,10 +61,10 @@ abstract class Graph : Graphable { * Creates vertex based on async method invocation. */ protected fun handler( - handler: NoArgHandler): MergerBuilder { + handler: NoArgHandler): MergerBuilder, HandlerResult> { - return handler(object : Handler { - override fun handle(payload: Payload): CompletableFuture { + return handler(object : Handler, HandlerResult> { + override fun handle(payload: Payload): CompletableFuture { return handler.handle() } }) @@ -73,8 +77,8 @@ abstract class Graph : Graphable { * Then waits the function to complete. * Then Handler pass function result and payload to to Merger if one was specified during graph construction. */ - protected fun handler(handler: Handler): - MergerBuilder { + protected fun handler(handler: Handler, HandlerResult>): + MergerBuilder, HandlerResult> { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) @@ -89,7 +93,7 @@ abstract class Graph : Graphable { /** * Creates vertex based on sync method invocation. */ - protected fun router(router: Router): Vertex { + protected fun router(router: Router>): Vertex { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) graph.vertices.add(vx) @@ -106,7 +110,7 @@ abstract class Graph : Graphable { /** * Creates vertex based on sync method invocation. */ - protected fun mutator(mutator: Mutator): Vertex { + protected fun mutator(mutator: Mutator>): Vertex { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) graph.vertices.add(vx) @@ -123,9 +127,10 @@ abstract class Graph : Graphable { /** * Creates vertex based on subgraph invocation. */ - protected fun subgraph( - subgraphPayloadType: Class, - subgraph: Subgraph): MergerBuilder { + protected fun subgraph( + subgraphSubmitType: Class>, + subgraph: Subgraph, SubgraphRequest>): + MergerBuilder, SubgraphResponse> { val vertex = Vertex() val vx = InternalDslAccessor.vx(vertex) @@ -133,8 +138,8 @@ abstract class Graph : Graphable { graphBuilderValidator.validateSubgraph(vx) - vx.subgraphPayloadType = subgraphPayloadType - vx.subgraphPayloadBuilder = subgraph as Subgraph + vx.subgraphSubmitType = subgraphSubmitType + vx.subgraphRequestBuilder = subgraph as Subgraph return DslMergerBuilder(vertex) } } diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Subgraph.kt b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Subgraph.kt index 670c0623..578676c3 100644 --- a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Subgraph.kt +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Subgraph.kt @@ -1,6 +1,6 @@ package ru.fix.completable.reactor.graph - +//TODO: docs @FunctionalInterface -interface Subgraph { - fun subgraph(payload: Payload): SubgraphPayload +interface Subgraph { + fun subgraph(payload: Payload): SubgraphRequest } diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Submit.kt b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Submit.kt new file mode 100644 index 00000000..574249cb --- /dev/null +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/Submit.kt @@ -0,0 +1,6 @@ +package ru.fix.completable.reactor.graph +//TODO doc with example +@FunctionalInterface +interface Submit { + fun submit(request: Request): Execution +} \ No newline at end of file diff --git a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/runtime/RuntimeVertex.kt b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/runtime/RuntimeVertex.kt index afb10cf9..13555db7 100644 --- a/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/runtime/RuntimeVertex.kt +++ b/completable-reactor-graph/src/main/kotlin/ru/fix/completable/reactor/graph/runtime/RuntimeVertex.kt @@ -35,13 +35,13 @@ class RuntimeVertex(val sourceVertex: Vertex) { @JvmField var merger: RoutingMerger? = null - +//TODO support in implementation chaneg from payload to submit interface @JvmField - var subgraphPayloadType: Class<*>? = null + var subgraphSubmitType: Class<*>? = null @JvmField - var subgraphPayloadBuilder: Subgraph? = null + var subgraphRequestBuilder: Subgraph? = null @JvmField val transitions: MutableList = ArrayList() diff --git a/completable-reactor-runtime/src/main/java/ru/fix/completable/reactor/runtime/CompletableReactor.java b/completable-reactor-runtime/src/main/java/ru/fix/completable/reactor/runtime/CompletableReactor.java index 23772993..f86cbb10 100644 --- a/completable-reactor-runtime/src/main/java/ru/fix/completable/reactor/runtime/CompletableReactor.java +++ b/completable-reactor-runtime/src/main/java/ru/fix/completable/reactor/runtime/CompletableReactor.java @@ -6,6 +6,7 @@ import ru.fix.aggregating.profiler.ProfiledCall; import ru.fix.aggregating.profiler.Profiler; import ru.fix.completable.reactor.graph.Graphable; +import ru.fix.completable.reactor.graph.Submit; import ru.fix.completable.reactor.graph.runtime.RuntimeGraph; import ru.fix.completable.reactor.runtime.debug.DebugSerializer; import ru.fix.completable.reactor.runtime.debug.ToStringDebugSerializer; @@ -427,16 +428,19 @@ public void registerGraph( * @param payloadProcessingFunction * @param */ - public void registerGraphSync( - Class payloadType, - Function payloadProcessingFunction) { + public void registerGraphImplementation( + Class> submitType, + Function implementation) { - registerGraph( - payloadType, - pld -> CompletableFuture.completedFuture(payloadProcessingFunction.apply(pld))); + //TODO implement + throw new UnsupportedOperationException(); +// registerGraph( +// payloadType, +// pld -> CompletableFuture.completedFuture(payloadProcessingFunction.apply(pld))); } //TODO extract statistics + //TODO: remove statistics, we are using profiler static class PayloadStatCounters { final LongAdder runningTotal = new LongAdder(); @@ -518,28 +522,28 @@ public CompletableFuture getResultFuture() { } } - - public Optional> trySubmit(PayloadType payload) { - return trySubmit(payload, executionTimeoutMs); - } - - public Optional> trySubmit(PayloadType payload, long timeoutMs) { - if (pendingRequestCount.get() > maxPendingRequestCount.get()) { - return Optional.empty(); - } - return Optional.of(submit(payload, timeoutMs)); - } - - public Execution submit(PayloadType payload) { - return submit(payload, executionTimeoutMs); - } - - public Execution submit(PayloadType payload, long timeoutMs) { - if (isClosed.get()) { - throw new IllegalStateException("CompletableReactor is closed. Payload " + payload + " is discarded."); - } - return internalSubmit(payload, timeoutMs); - } +//TODO port to graph() api +// public Optional> trySubmit(PayloadType payload) { +// return trySubmit(payload, executionTimeoutMs); +// } +// +// public Optional> trySubmit(PayloadType payload, long timeoutMs) { +// if (pendingRequestCount.get() > maxPendingRequestCount.get()) { +// return Optional.empty(); +// } +// return Optional.of(submit(payload, timeoutMs)); +// } +// +// public Execution submit(PayloadType payload) { +// return submit(payload, executionTimeoutMs); +// } +// +// public Execution submit(PayloadType payload, long timeoutMs) { +// if (isClosed.get()) { +// throw new IllegalStateException("CompletableReactor is closed. Payload " + payload + " is discarded."); +// } +// return internalSubmit(payload, timeoutMs); +// } /** * Submit request without checking whether reactor closed or not. @@ -735,4 +739,10 @@ public void close() throws Exception { } } } + + public Submit graph( + Class submitType) { + //TODO: implement + throw new UnsupportedOperationException(); + } } From 6f0b03fd75f76f9257acae854d58a5364beafc54 Mon Sep 17 00:00:00 2001 From: kasfandiyarov Date: Fri, 10 Jan 2020 11:12:09 +0300 Subject: [PATCH 2/3] Update gradle and kotlin versions --- buildSrc/src/main/kotlin/Dependencies.kt | 4 +-- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 33 ++++++++++------------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5e071cba..4556a697 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,8 +1,8 @@ object Vers { //Plugins val asciidoctor = "1.5.9.2" - val kotlin = "1.3.41" - val kotlin_coroutines = "1.2.2" + val kotlin = "1.3.61" + val kotlin_coroutines = "1.3.3" val sl4j = "1.7.25" val dokka = "0.9.18" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644 GIT binary patch delta 16535 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 Date: Fri, 10 Jan 2020 11:27:04 +0300 Subject: [PATCH 3/3] Add Projs enum --- buildSrc/src/main/kotlin/Dependencies.kt | 17 +++++++++++++++++ settings.gradle.kts | 23 +++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 4556a697..f3ca490e 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -63,4 +63,21 @@ object Libs { val hamcrest = "org.hamcrest:hamcrest-all:1.3" val hamkrest = "com.natpryce:hamkrest:1.7.0.0" +} + +enum class Proj{ + `completable-reactor-example`, + `completable-reactor-graph`, + `completable-reactor-graph-kotlin`, + `completable-reactor-graph-viewer`, + `completable-reactor-graph-viewer-app`, + `completable-reactor-jmh`, + `completable-reactor-model`, + `completable-reactor-parser`, + `completable-reactor-plugin-idea`, + `completable-reactor-runtime`, + `completable-reactor-spring`, + `completable-reactor-test-utils`; + + val asDependency get(): String = ":$name" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ad123ede..7bc60959 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,11 +1,14 @@ rootProject.name = "completable-reactor" -include("completable-reactor-example") -include("completable-reactor-graph") -include("completable-reactor-graph-kotlin") -include("completable-reactor-graph-viewer") -include("completable-reactor-graph-viewer-app") -include("completable-reactor-model") -include("completable-reactor-parser") -include("completable-reactor-runtime") -include("completable-reactor-spring") -include("completable-reactor-test-utils") +for (project in listOf( + "completable-reactor-example", + "completable-reactor-graph", + "completable-reactor-graph-kotlin", + "completable-reactor-graph-viewer", + "completable-reactor-graph-viewer-app", + "completable-reactor-model", + "completable-reactor-parser", + "completable-reactor-runtime", + "completable-reactor-spring", + "completable-reactor-test-utils")) { + include(project) +} \ No newline at end of file