Effects only in Actor¶
Once the complexity grows inside your Actor
, you might be tempted to extract some responsibilities to other classes.
This is fine, however, don't do this:
class ActorImpl(
private val executor1: Executor1,
private val executor2: Executor2
) : Actor<State, Wish, Effect> {
override fun invoke(state: State, wish: Wish): Observable<Effect> = when (wish) {
is Wish1 -> executor1.doSomething()
is Wish2 -> executor2.doSomething()
}
}
// In some other files:
class Executor1 {
fun doSomething(): Observable<Effect> = TODO()
}
class Executor2 {
fun doSomething(): Observable<Effect> = TODO()
}
This way the reader of your code can have no simple understanding of what Effect
is the result of what and where.
Resolution:
- Keep your
Effects
internal to yourFeature
- In your extracted classes use only local result types
- Map those results to
Effects
in yourActor
, ensuring that all business logic is understandable in a high-level overview in one place.
Example:
class ActorImpl(
private val executor1: Executor1,
private val executor2: Executor2
) : Actor<State, Wish, Effect> {
override fun invoke(state: State, wish: Wish): Observable<Effect> = when (wish) {
is Wish1 -> executor1
.doSomething()
.map { when (it) {
is SomethingHappened1 -> Effect1(it.data)
is SomethingHappened2 -> Effect2
}}
is Wish2 -> executor2
.doSomething()
.map { when (it) {
is SomethingElseHappened1 -> Effect3
is SomethingElseHappened2 -> Effect4(it.error)
}}
}
}
// In some other files:
class Executor1 {
fun doSomething(): Observable<LocalResult> = TODO()
sealed class LocalResult {
data class SomethingHappened1(val data: Any) : LocalResult()
object SomethingHappened2 : LocalResult()
}
}
class Executor2 {
fun doSomething(): Observable<LocalResult> = TODO()
sealed class LocalResult {
object SomethingElseHappened1 : LocalResult()
data class SomethingElseHappened2(val error: Throwable) : LocalResult()
}
}