Cookoo's New Syntax

Jun 17 2015

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.