Cookoo's New Syntax
Cookoo is a Chain-of-Command (CoCo) library for Go. It's good for building web services. We used it to build all of the Revolv RESTS endpoints. It's also great for building tools that handle a sequence of steps. That's why we used it in Glide.
Yesterday's release of Cookoo 1.2.0 makes some major forward-looking changes in the library. This post highlights the two major ones. The first is Cookoo's new route declaration syntax, and the second one is Cookoo's ability to now define structs for commands.
Goodbye Chaining, Hello Declarations
The main piece of feedback I've heard on Cookoo recently is that the method of defining routes through method chaining is hard to read. This is the way a Cookoo route looks when defined as a chain:
reg.Route("foo", "A test route").
Does(AnotherCommand, "fakeCommand").
Using("param").WithDefault("value").
Using("something").From("cxt:foo")
That's not too bad. But imagine routes that have five, ten, or more commands.
That's dozens of sequential lines that start with Does
and Using
. It gets
hard to visually scan. So the proposal was to switch to a declarative syntax
similar to the popular Codegangsta CLI
library. This new form of route definition looks like this:
reg.AddRoute(cookoo.Route{
Name: "foo",
Help: "A test route",
Does: cookoo.Tasks{
cookoo.Cmd{
Name: "fakeCommand",
Fn: AnotherCommand,
Using: []cookoo.Param{
{ Name: "param", WithDefault: "value},
{ Name: "something", From: "cxt:foo"},
},
},
},
})
Thile the new format is more verbose, the feedback we've received suggests that most people find this much easier to read, even for larger chains.
So while the old chaining method is still 100% supported by the library, so is this new method.
Experimental Support for Struct-based Command Definitions
Cookoo Command functions require a fair amount of type assertion. That's something we'd like to avoid. And so we're trying an experiment.
The net/http
library gives you the option of running a server either with
a function, or with a type that satisfies a certain interface. We decided
to borrow that idea for Cookoo.
Cookoo 1.2 introduces a new interface:
type CommandDefinition interface {
Run(c Context) (interface{}, Interrupt)
}
This can be used in lieu of a cookoo.Command
by implementing the interface
with a struct that declares the parameters your command takes:
type Person struct {
FirstName string
LastName string
}
func (p *Person) Run(c cookoo.Context) (interface{}, cookoo.Interrupt) {
fmt.Printf("Your name is %s %s", p.FirstName, p.LastName)
return nil, nil
}
You can then execute this struct like this:
reg, route, cxt := cookoo.Cookoo()
reg.AddRoute(Route{
Name: "example",
Does: cookoo.Tasks{
CmdDef{
Name: "person",
Def: &Person{},
Using: []Param{
{Name: "FirstName", DefaultValue:"Matt"},
{Name: "LastName", DefaultValue:"Butcher"},
},
},
},
})
route.HandleRequest("example", cxt, true)
In this example, Cookoo uses the prototype of Person to create a new Person command definition, properly convert the parameters, and then execute the command.
These are a couple of the big new changes in Cookoo 1.2. But there are plenty of smaller ones, all designed to make development easier.