nomisRev / Saga

Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.
https://nomisrev.github.io/Saga
Apache License 2.0
64 stars 4 forks source link

Higher order sagas #165

Closed pinguinjkeke closed 1 year ago

pinguinjkeke commented 1 year ago

Thanks for a cool library, but we often get indentation hell

saga {
    saga({
       CoroutineScope(Dispatchers.IO).run {
            retry(5) {
                async {
                    doSomething()
                }
            }
        }
    }) {
        // same levels of nesting on rollback
    }
}

It will be good to have an ability to somehow wrap saga for:

What do you think?

nomisRev commented 1 year ago

Hey @pinguinjkeke,

Thanks for the feedback! That pattern seems dangerous :/ In your snippet both the async and CoroutineScope will leak. To solve that I think this should be written as:

saga {
    saga({
       withContext(Dispatchers.IO)  {
            retry(5) { doSomething() }
        }
    }) {
        // same levels of nesting on rollback
    }
}

It doesn't entirely resolve you problem, but it already makes the indentation a bit smaller. I see you also have indentation configured to 4-spaces, which I am personally not a fan of especially in Kotlin which can be quite heavy on DSLs. The goal of these monadic DSLs is that they can be safely nested, but that indeed brings some callback-hell style code into play.

Albeit that with callback-hell you can never "escape" the callback, but in this case at some point you'll get the result on the lefthand side like regular imperative code. So this can typically be "solved" in Kotlin by applying some best-practices when working with suspend but doesn't entirely solve this issue of nesting.

For example:

saga {
  val result = doSomething()
}

suspend fun SagaEffect.doSomething(): A = saga({
  withContext(Dispatchers.IO) {
    retry(5) { doSomething() }
  }
}) {  // same levels of nesting on rollback  }

To come back to your original question of "higher order", it will introduce a lot of specialised code in every type similar to Saga for example CircuitBreaker has these similar concerns of CoroutineScope, withContext and retry. The benefit of Kotlin, and its suspend DSLs` is that these can all be nested and composed without having to build separate and duplicated APIs. Which in turn also increases the API surface of every library.

Not sure if my reply helps you in any way, but I think this is out of the scope of this library. Feel free to reply, or ask any questions you might have.

pinguinjkeke commented 1 year ago

Thanks for the detailed answer.