Home > database >  Which should we choose between Effect and Either as a return type of our business services?
Which should we choose between Effect and Either as a return type of our business services?

Time:05-18

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()
  • Related