We were trying to solve a general workflow/rollback problem at work the other day, and @psnively and I were discussing alternatives. We both agreed that scalaz-camel would be a promising way to route through the workflow, but we had different ideas about how to rollback changes in the event of a failure.
My idea was to compose a scalaz-camel rollback route as we went through the "good" route of the workflow. I spent much of Friday working on this, and there is still some hope of success. Paul's idea was to build a queue of functions to call to perform a rollback as we went through the "good" route, and in the event of a failure, start pulling functions off the queue and executing them.
As usual, Paul's idea has a bit more of that elegant simplicity that makes it so appealing. So, I spent some time on Saturday (sprint ends on Tuesday you know) tinkering around with building a queue of functions. This is my first real deep foray into stuffing functions into a list, and I found it to be mentally challenging, but in the end, pretty easy to do.
The biggest challenges I ran into were probably due to over-thinking the problem, and not knowing enough about syntax. The main hurdle was to get the function into the list with a full set of params without actually evaluating the function until some later time. It turns out this is pretty easy if you know a couple little tricks.
The first thing to know is that you can use a "call-by-name" parameter do delay evaluation until the expression is called. To specify a parameter as call-by-name, you just prefix the parameter type with =>. This is really handy if your parameter is a function! The second piece of the puzzle was () => Any which is a function of zero arguments that returns something of type Any.
With those two things, you can construct a list to contain the rollback functions like:
That's a list of zero argument functions that return Any. In this case, the Any is going to be my rollback function to evaluate. For the sake of discussion, let's say my rollback methods are:
Now that we have some methods to work with, we need to turn them into functions. We can use the _ to do that:
Now we have three functions that carry out the activity defined by the methods. Pretty cool. The next order of business is to get the parameters associated with the functions without evaluating the function. Here's where the param: => Any comes in. We can define a wrapper method like this:
That method takes a call-by-name parameter of type Any, and returns a zero argument function that returns type Any. The covers any function we would ever use for rollback. To wrap our functions with parameters and add them to our list, we just call the wrapper method like this:
Then if we hit a snag and have to roll back our changes, we can just do:
Notice that we're mapping each function in the list to f(). The () calls each of the zero arg functions which evaluate and return our rollback function. Super cool!
Putting it all together, my little test case looks like this:
We still want to integrate a strategy with scalaz-camel routes, since it's a nice way to control the flow of execution, so maybe there's another post coming.

My idea was to compose a scalaz-camel rollback route as we went through the "good" route of the workflow. I spent much of Friday working on this, and there is still some hope of success. Paul's idea was to build a queue of functions to call to perform a rollback as we went through the "good" route, and in the event of a failure, start pulling functions off the queue and executing them.
As usual, Paul's idea has a bit more of that elegant simplicity that makes it so appealing. So, I spent some time on Saturday (sprint ends on Tuesday you know) tinkering around with building a queue of functions. This is my first real deep foray into stuffing functions into a list, and I found it to be mentally challenging, but in the end, pretty easy to do.
The biggest challenges I ran into were probably due to over-thinking the problem, and not knowing enough about syntax. The main hurdle was to get the function into the list with a full set of params without actually evaluating the function until some later time. It turns out this is pretty easy if you know a couple little tricks.
The first thing to know is that you can use a "call-by-name" parameter do delay evaluation until the expression is called. To specify a parameter as call-by-name, you just prefix the parameter type with =>. This is really handy if your parameter is a function! The second piece of the puzzle was () => Any which is a function of zero arguments that returns something of type Any.
With those two things, you can construct a list to contain the rollback functions like:
val rollback = List[() => Any]()
That's a list of zero argument functions that return Any. In this case, the Any is going to be my rollback function to evaluate. For the sake of discussion, let's say my rollback methods are:
def m1(s1:String) = println("string = " + s1)
def m2(s1: String, i1:Int) = println("string = " + s1 + ", int = " + i1)
def m3(s1: String, i1: Int)(c1: String) = println("string = " + s1 + ", int = " + i1 + ", curried = " + c1)
Now that we have some methods to work with, we need to turn them into functions. We can use the _ to do that:
val f1 = m1 _
val f2 = m2 _
val f3 = m3 _
Now we have three functions that carry out the activity defined by the methods. Pretty cool. The next order of business is to get the parameters associated with the functions without evaluating the function. Here's where the param: => Any comes in. We can define a wrapper method like this:
def wrapper(function: => Any): () => Any = () => function
That method takes a call-by-name parameter of type Any, and returns a zero argument function that returns type Any. The covers any function we would ever use for rollback. To wrap our functions with parameters and add them to our list, we just call the wrapper method like this:
var undoQueue = wrapper(f3("function3", 3)("curried 3")) :: rollback
undoQueue = wrapper(f2("function2", 2)) :: undoQueue
undoQueue = wrapper(f1("function1")) :: undoQueue
Then if we hit a snag and have to roll back our changes, we can just do:
undoQueue.map(f => f())
Notice that we're mapping each function in the list to f(). The () calls each of the zero arg functions which evaluate and return our rollback function. Super cool!
Putting it all together, my little test case looks like this:
def test3 = {
val rollback = List[() => Any]()
println("turning methods into functions")
val f1 = m1 _
val f2 = m2 _
val f3 = m3 _
println("adding functions to queue")
var undoQueue = wrapper(f3("function3", 3)("curried 3")) :: rollback
undoQueue = wrapper(f2("function2", 2)) :: undoQueue
undoQueue = wrapper(f1("function1")) :: undoQueue
println("evaluating functions")
undoQueue.map(f => f())
}
def wrapper(function: => Any): () => Any = () => function
def m1(s1:String) = println("string = " + s1)
def m2(s1: String, i1:Int) = println("string = " + s1 + ", int = " + i1)
def m3(s1: String, i1: Int)(c1: String) = println("string = " + s1 + ", int = " + i1 + ", curried = " + c1)
We still want to integrate a strategy with scalaz-camel routes, since it's a nice way to control the flow of execution, so maybe there's another post coming.

No comments:
Post a Comment
Note: Only a member of this blog may post a comment.