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?
There is no reason to refactor to
EffectifEitherfits your use case.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 tosuspend EffectScope<E>.() -> A, and the result of running that lambda can beEither<E, A>.That is also why we can assign
Effectto aval, and when returningEitherwe need to invoke the suspend lambda which requiressuspend fun.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 usingEffectcan be used.You can consider
Effecta lower level implementation thanEither, since all computation blocks such aseither { },option { },ior { },result { }, etc are now all implemented througheffect.Which you can see in the Arrow implementation for
either { }.