С появлением Arrow 1.1.x мы получили новый класс Эффект.
До сих пор мои бизнес-классы возвращали Либо для моделирования эффекта возврата ошибки или значения, например:
@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) })
}
С новым Effect
это можно было бы преобразовать во что-то вроде:
@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)
}
}
В тестах насмешка изменилась с:
@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())
к...
@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 })
Вопрос в том, как лучше всего моделировать наши бизнес-услуги, новый класс Effect
или Either
, и почему?
Нет причин проводить рефакторинг на Effect
, если Either
подходит для вашего варианта использования.
The question is, what's the best approach to model our business services, the new Effect class or Either, and why?
К сожалению, как это часто бывает в программном обеспечении, нет универсального ответа, но для моделирования бизнес-услуг я бы в целом рекомендовал Either
.
Effect<E, A>
семантически эквивалентен suspend EffectScope<E>.() -> A
, и результатом выполнения этой лямбды может быть Either<E, A>
.
Вот почему мы можем присвоить Effect
val
, а при возврате Either
нам нужно вызывать приостановить лямбду, которая требует suspend fun
.
val operation: Effect<E, A> =
effect { ... }
suspend fun operation(): Either<E, A> =
operation.toEither()
Поэтому, если вам просто нужно смоделировать результат операции, желательно использовать Either<E, A>
. Если вам нужно обойти лямбду/операцию, можно использовать Effect
.
Вы можете рассматривать Effect
как реализацию более низкого уровня, чем Either
, поскольку все вычислительные блоки, такие как either { }
, option { }
, ior { }
, result { }
и т. д., теперь реализуются через effect
.
Что вы можете увидеть в реализации Arrow для either { }
.
suspend fun <E, A> either(block: suspend EffectScope<E>.() -> A): Either<E, A> =
effect(block).toEither()