Add composition for DSL API
SuLG-ik opened this issue · 1 comments
p.s. Early I make PR with same name, but now I would like to discuss about important of this API.
Reasons
- to extend onIntent behavior should create own extension functions, that's easy with basic functional (like skips intent's when previous still is in execution with async), but hard with more difficult functional (like add multiple intent's with disabling/enabling with feature toggle and merge it with some another behavior like skipping)
- no abilities to change behavior for multiple registered intents, now executes only first
What's new
- ExecutionHandler (might be named as ExecutionChain, ExecutionProvider) with method handle(Scope, Intent/Action): Boolean
- common ExecutorDSL that has methods onIntent/onAction to store ExecutionHandlers
- abstract DslExecutorImpl that execute ExecutionHandlers on intents/actions
Solution
ExecutionHandlers flow
DirectExecutionHandler (just call provided lambda with scope, always true) <=
<= .... some user's handlers or nothing <=
<= TypedExecutionHandler (compare type of executed intent and call execution handler above if matches and return true, or if not false) <=
<= MultipleExecutionHandler (always false, that's understood by executor as continue calling next registered handlers) or SingleExecutionHandler (return status by handlers above, for example TypedExecutionHandler does not matches types or other user's provided handlers does not execute handler)
User's usage
Each ExecutionHandler, exclude DirectExecutionHandler, should has next handler to call it if some condition
CoroutineSkippingExecutionHandler example
class CoroutineSkippingExecutionHandler<T : Any, State : Any, Message : Any, Action : Any, Label : Any> @PublishedApi internal constructor(
private val nextHandler: ExecutionHandler<T, CoroutineExecutorScope<State, Message, Action, Label>>,
) : ExecutionHandler<T, CoroutineExecutorScope<State, Message, Action, Label>> {
private var jobHost: Job = Job()
override fun handle(scope: CoroutineExecutorScope<State, Message, Action, Label>, value: T): Boolean {
if (jobHost.children.any()) {
return false
}
val newScope = CoroutineSkippingExecutorScope(jobHost, scope)
nextHandler.handle(newScope, value)
return true
}
}
and usage-friendly creating for give to common ExecutorDsl's onIntent
inline fun <reified T : Any, Action : Any, State : Any, Message : Any, Label : Any> coroutineSkippingExecutionHandler(
noinline handler: CoroutineExecutorScope<State, Message, Action, Label>.(T) -> Unit
): ExecutionHandler<T, CoroutineExecutorScope<State, Message, Action, Label>> {
return singleExecutionHandler(typedExecutionHandler(CoroutineSkippingExecutionHandler(directExecutionHandler(handler))))
}
in Executor
coroutineExecutorFactory<Some, Nothing, Nothing, Nothing, Nothing> {
onIntent(coroutineSkippingExecutionHandler { intent: Some.A ->
/* something */
})
}
or if add to base api of CoroutineExecutor
coroutineExecutorFactory<Some, Nothing, Nothing, Nothing, Nothing>(Dispatchers.Unconfined) {
onIntentSkipping<Some.A> {
/* something */
}
}
About abilities for changing execution multiple/single strategy
enum class ExecutionStartegy {
SINGLE, MuLTIPLE
}
currently exists onIntent method with new parameter in exists CoroutineExecutorBuilder
inline fun <reified T : Intent> onIntent(
noinline handler: CoroutineExecutorScope<State, Message, Action, Label>.(intent: T) -> Unit,
strategy: ExecutionStartegy = ExecutionStartegy.Single,
) {
// logic matches new suggested api
onIntent(coroutineExecutionHandler(handler, startegy)) // call common ExecutorBuilder's onIntent
}
and under the hood as bottom ExecutionHandlers flow use's ExecutionStrategyExecutionHandler
private fun <T : Any, Scope : Any> ExecutionStrategy.handler(nestedHandler: ExecutionHandler<T, Scope>): ExecutionHandler<T, Scope> {
return when (this) {
ExecutionStrategy.SINGLE -> singleExecutionHandler(nestedHandler)
ExecutionStrategy.MULTIPLE -> multipleExecutionHandler(nestedHandler)
}
}
class ExecutionStrategyExecutionHandler<T : Any, Scope : Any>(
nestedHandler: ExecutionHandler<T, Scope>,
strategy: ExecutionStrategy,
) : ExecutionHandler<T, Scope> {
private val handler = strategy.handler(nestedHandler)
override fun handle(scope: Scope, value: T): Boolean {
return handler.handle(scope, value)
}
}
Benefits
- better control of execution by user
- ability to composite execution behaviours
- save exists API, onIntent/onAction with lambda of exists CoroutineExecutorBuilder/ReactiveExecutorBuilder still exists
Thanks for describing the request in details. The current Store DSL is primarily designed for simple cases. For more complex cases it's recommended to extend CoroutineExecutor
. The proposed API will dramatically increase cognitive load for the developer when working with it.
If you really want such complex logic with Store DSL, feel free to create your API specifically for your needs.
Thanks again, but I don't think it's a good idea to bring this to MVIKotlin.