Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Fix associativity #21

Merged
merged 5 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Let's think for a moment about how we could implement this pattern without any s

The naive implementation could look like this:

```
```scala
def orderSaga(): IO[SagaError, Unit] = {
for {
_ <- collectPayments(2d, 2) orElse refundPayments(2d, 2)
Expand All @@ -54,7 +54,7 @@ full rollback logic to be triggered in Saga, whatever error occurred.

Second try, this time let's somehow trigger all compensating actions.

```
```scala
def orderSaga(): IO[SagaError, Unit] = {
collectPayments(2d, 2).flatMap { _ = >
assignLoyaltyPoints(1d, 1).flatMap { _ =>
Expand All @@ -78,7 +78,7 @@ repeating the same boilerplate code from service to service.

With `ZIO-SAGA` we could do it like so:

```
```scala
def orderSaga(): IO[SagaError, Unit] = {
import com.vladkopanev.zio.saga.Saga._

Expand All @@ -105,7 +105,7 @@ over transaction execution.
`ZIO-SAGA` provides you with functions for retrying your compensating actions, so you could
write:

```
```scala
collectPayments(2d, 2) retryableCompensate (refundPayments(2d, 2), Schedule.exponential(1.second))
```

Expand All @@ -117,15 +117,18 @@ increasing timeouts (based on `ZIO#retry` and `ZSchedule`).
Saga pattern does not limit transactional requests to run only in sequence.
Because of that `ZIO-SAGA` contains methods for parallel execution of requests.

```
```scala
val flight = bookFlight compensate cancelFlight
val hotel = bookHotel compensate cancelHotel
val bookingSaga = flight zipPar hotel
```

### Cats Compatible Sagas

[cats-saga](https://github.com/VladKopanev/cats-saga)

### See in the next releases:
- Even more powerful control over compensation execution
- Cats interop

[Link-Codecov]: https://codecov.io/gh/VladKopanev/zio-saga?branch=master "Codecov"
[Link-Travis]: https://travis-ci.com/VladKopanev/zio-saga "circleci"
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/scala/com/vladkopanev/zio/saga/Saga.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ final class Saga[-R, +E, +A] private (
def flatMap[R1 <: R, E1 >: E, B](f: A => Saga[R1, E1, B]): Saga[R1, E1, B] =
new Saga(request.flatMap {
case (a, compA) =>
f(a).request.tapError({ case (e, compB) => compB.mapError(_ => (e, IO.unit)) }).mapError {
case (e, _) => (e, compA)
}
f(a).request.bimap(
{ case (e, compB) => (e, compB *> compA) },
{ case (r, compB) => (r, compB *> compA) }
)
})

/**
Expand Down
32 changes: 32 additions & 0 deletions core/src/test/scala/com/vladkopanev/zio/saga/SagaSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,38 @@ class SagaSpec extends FlatSpec {
val actionLog = unsafeRun(sagaIO)
actionLog shouldBe Vector("flight canceled", "hotel canceled")
}

"Saga#compensate" should "allow compensation to be dependent on the result of corresponding effect" in new TestRuntime {
val failCar = IO.fail(CarBookingError())
val sagaIO = for {
actionLog <- Ref.make(Vector.empty[String])
_ <- (for {
_ <- bookHotel compensate cancelHotel(actionLog.update(_ :+ "hotel canceled"))
_ <- bookFlight compensate cancelFlight(actionLog.update(_ :+ "flight canceled"))
_ <- failCar compensate ((_: Either[SagaError, PaymentInfo]) => cancelCar(actionLog.update(_ :+ "car canceled")))
} yield ()).transact.orElse(ZIO.unit)
log <- actionLog.get
} yield log

val actionLog = unsafeRun(sagaIO)
actionLog shouldBe Vector("car canceled", "flight canceled", "hotel canceled")
}

"Saga#flatten" should "execute outer effect first and then the inner one producing the result of it" in new TestRuntime {
val sagaIO = for {
actionLog <- Ref.make(Vector.empty[String])
outer = bookFlight compensate cancelFlight(actionLog.update(_ :+ "flight canceled"))
inner = bookHotel compensate cancelHotel(actionLog.update(_ :+ "hotel canceled"))
failCar = IO.fail(CarBookingError()) compensate cancelCar(actionLog.update(_ :+ "car canceled"))

_ <- outer.map(_ => inner).flatten[Any, SagaError, PaymentInfo].flatMap(_ => failCar).transact.orElse(ZIO.unit)
log <- actionLog.get
} yield log

val actionLog = unsafeRun(sagaIO)
actionLog shouldBe Vector("car canceled", "hotel canceled", "flight canceled")
}

}

trait TestRuntime extends DefaultRuntime {
Expand Down