herding-cats
herding-cats copied to clipboard
Scalaz Promise-based library for working with Apache Zookeeper
About
herding-cats is a Scala library for working with Apache Zookeeper. It uses
Scalaz (6.x) extensively, in particular Scalaz Promises to eliminate structuring your code as a
series of callbacks (i.e.: inversion of control). The result is a much cleaner way to work with ZooKeeper.
Status
Still under heavy development, but potentially useful.
API docs
Usage
Interaction with ZooKeeper is done monadically, in Scala parlance this means using
for comprehensions. Here's an example use:
object Main {
com.corruptmemory.herding_cats._
import scalaz._
import Scalaz._
def printer(data:String,data1:String) {
println("Data: %s".format(data))
println("Data1: %s".format(data1))
}
def foo:Unit = withZK[Unit]("/test/control",ZK("127.0.0.1:2181",5000),()) {
(zk:ZK) =>
val reader = zk.reader[Unit]
val path = reader.path("/foo")
val path1 = reader.path("/bar")
for {
data <- path.data[String]()
data1<- path1.data[String](false)
} yield printer(data,data1)
}
def main(args:Array[String]) {
foo
}
}
Let's look at this a bit closer:
def foo:Unit = withZK[Unit]("/test/control",ZK("127.0.0.1:2181",5000),()) {
withZK is how you connect to a ZooKeeper cluster. The first argument is a control node. A control node
is an ephemeral node that is used internally by herding-cats, at the moment it contains either a 0 meaning
watches are ignored or a 1 meaning watches are enabled. The exact content of this node is likely to change over
time, but there you go. An additional usage is to "tickle" your handler in the event that a watched node gets deleted,
sadly deleting a watched node does not trigger a watch, but deleting the control node will re-execute your handler
function (i.e. the body), typically returning your configuration to a healthy state. The type Unit is the type
returned by the handler function. The call to withZK does not exit but keeps re-invoking the body whenever a
watched node (or the control node) changes. By default any node you access is added to the watch list.
The next argument to withZK is:
ZK("127.0.0.1:2181",5000)
This guy produces a connection factory to a ZooKeeper cluster. The first argument to ZK can contain multiple endpoints
separated by commas as documented in the [ZooKeeper constructor](http://zookeeper.apache.org/doc/r3.3.3/api/org/apache/zookeeper/ZooKeeper.html#ZooKeeper(java.lang.String, int, org.apache.zookeeper.Watcher)).
Example: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002". Endpoints are of the form {host}:{port}. The second argument
to ZK is the connection timeout in milliseconds.
The third argument to withZK is the initial state that is manipulated and updated by the handler, in this case the
initial state is unit, (). This state is captured by withZK after each invocation of the body and send back in on
subsequent invocations.
Finally, you pass a function (the body) to withZK, the type signature of the body is: ZK => ZKState[S,Unit] where a ZK is a
"connection" generated by the connection factory. The ZKState type wraps a bunch of stuff, you can see the definition here. What this is saying is that your getting a State monad that has as its state a promise
that returns values of type Result. A Result is a type alias over a Scalaz Validation. In English: a ZKState value consumes a state S and
promises to either succeed with returning a new value and state (via a promise, this is the bit that handles the async stuff) or you get a nice clean
failure that you can do tests on. One failure value of particular interest is Shutdown, if the function returns that value then withZK will exit and
clean up.
Let's look at the body:
val reader = zk.reader[Unit]
val path = reader.path("/foo")
val path1 = reader.path("/bar")
for {
data <- path.data[String]()
data1<- path1.data[String](false)
} yield printer(data,data1)
From a ZK object you can get a reader or you can create a writer within a scope (more TBD on the writer scope as it relates to disabling watches). From
a reader you can get a path value that enables access to the data in a node identified on the given path. The most common operation being to
get the data contained in a node (but there are functions for getting the "stat" and children of a node). Data stored in ZooKeeper is essentially binary, so you
can supply arbitrary serializers for getting data into and out of a node
(see serialization.scala). There are also wrappers around sbinary. The above example is using simple literal String serialization/deserialization. The second example from path1 is indicating
that is does not wish to watch the node at path "/bar". Again the Unit type in: val reader = zk.reader[Unit] is the type of the "state" to be transformed by
the body.
Recipes
License
Licensed under Apache 2.0, see LICENSE.txt for details.