With the arrival of Arrow 1.1.x we got the new Effect class.
Up to now, my business classes returned Either to model the effect of returning an error or a value, e.g.:
@Service
class CreateDepartmentUseCaseImpl(
private val createDepartmentsDrivenPort: CreateDepartmentsDrivenPort,
private val getDepartmentByCodeDrivenPort: GetDepartmentByCodeDrivenPort
) : CreateDepartmentUseCase {
override suspend fun execute(param: Department): Either<DomainError, Department> =
Either.conditionally(getDepartmentByCodeDrivenPort.get(param.code) == null,
{ DepartmentAlreadyExistsError(param.code.value) },
{ createDepartmentsDrivenPort.create(param) })
}
With the new Effect
this could be refactored to something like:
@Service
class CreateDepartmentUseCaseImpl(
private val createDepartmentsDrivenPort: CreateDepartmentsDrivenPort,
private val getDepartmentByCodeDrivenPort: GetDepartmentByCodeDrivenPort
) : CreateDepartmentUseCase {
override suspend fun execute(param: Department): Effect<DomainError, Department> = effect {
ensure(getDepartmentByCodeDrivenPort.get(param.code) == null) { DepartmentAlreadyExistsError(param.code.value) }
createDepartmentsDrivenPort.create(param)
}
}
In tests, the mocking changed from:
@Test
fun `should create department`() = runTest {
val dep = createValidDepartment()
val createDepartmentRequest = CreateDepartmentRequest(dep.code.value, dep.name.value, dep.description.value)
`when`(createDepartmentsUseCase.execute(dep)).thenReturn(dep.right())
to...
@Test
fun `should create department`() = runTest {
val dep: Department = createValidDepartment()
val createDepartmentRequest = CreateDepartmentRequest(dep.code.value, dep.name.value, dep.description.value)
`when`(createDepartmentsUseCase.execute(dep)).thenReturn(effect { dep })
The question is, what's the best approach to model our business services, the new Effect
class or Either
, and why?
CodePudding user response:
There is no reason to refactor to Effect
if Either
fits your use case.
The question is, what's the best approach to model our business services, the new Effect class or Either, and why?
Sadly as often in software, there is no 1-fit-all answer but to model business services I would in general recommend Either
.
Effect<E, A>
is semantically equivalent to suspend EffectScope<E>.() -> A
, and the result of running that lambda can be Either<E, A>
.
That is also why we can assign Effect
to a val
, and when returning Either
we need to invoke the suspend lambda which requires suspend fun
.
val operation: Effect<E, A> =
effect { ... }
suspend fun operation(): Either<E, A> =
operation.toEither()
So if you just need to model the result of an operation then using Either<E, A>
is desired. If you need to pass around a lambda/operation then using Effect
can be used.
You can consider Effect
a lower level implementation than Either
, since all computation blocks such as either { }
, option { }
, ior { }
, result { }
, etc are now all implemented through effect
.
Which you can see in the Arrow implementation for either { }
.
suspend fun <E, A> either(block: suspend EffectScope<E>.() -> A): Either<E, A> =
effect(block).toEither()