I have a pattern to process web service requests using chained partial functions (this is a chain of responsibility pattern, I think?). In my example, let’s say there are two parameters for the request, a string Id and a date. There’s a verification step involving the id, a verification step checking the date, and finally some business logic that use both. So I have them implemented like so:
object Controller {
val OK = 200
val BAD_REQUEST = 400
type ResponseGenerator = PartialFunction[(String, DateTime), (String, Int)]
val errorIfInvalidId:ResponseGenerator = {
case (id, _) if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}
val errorIfFutureDate:ResponseGenerator = {
case (_, date) if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
}
val businessLogic:ResponseGenerator = {
case (id, date) => {
// ... do stuff
("Success!", OK)
}
}
def handleRequest(id:String, date:DateTime) = {
val chained = errorIfInvalidId orElse errorIfFutureDate orElse businessLogic
val result: (String, Int) = chained(id, date)
// make some sort of a response out of the message and status code
// e.g. in the Play framework...
Status(result._2)(result._1)
}
}
I like this pattern because it’s very expressive – you can easily grasp what the controller method logic is just by looking at the chained functions. And, I can easily mix and match different verification steps for different requests.
The problem is that as I try to expand this pattern it starts to break down. Suppose my next controller takes an id I want to validate, but does not have the date parameter, and maybe it has some new parameter of a third type that does need validation. I don’t want to keep expanding that tuple to (String, DateTime, Other) and have to pass in a dummy DateTime or Other. I want to have partial functions that accept different types of arguments (they can still return the same type). But I can’t figure out how to compose them.
For a concrete question – suppose the example validator methods are changed to look like this:
val errorIfInvalidId:PartialFunction[String, (String, Int)] = {
case id if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}
val errorIfInvalidDate:PartialFunction[DateTime, (String, Int)] = {
case date if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
}
Can I still chain them together? It seems like I should be able to map the tuples to them, but I can’t figure out how.
I’m a big fan of using scalaz’s Validation for things like this. It gives you quite a bit of control over what you want to do with errors and how to handle them. Here’s an example using you’re controller:
You can move the different validation functions off into their own world and then compose them anytime you want with the for comprehension in scala.