When Matt Butcher introduced me to the chain-of-command pattern for a controller, some years ago, I was intrigued. It provided a unique way to re-use code in a controller while enabling things to link together in a manner that reminded me of tools like Yahoo Pipes.
When we learned how useful the Go programming language was and how productive we could be with it, we decided to create a chain-of-command framework for Go. That framework is cookoo and it just had its first major release.
I find the easiest way to get into something new like this is to take a look at some code.
package main
import (
"github.com/Masterminds/cookoo"
"github.com/Masterminds/cookoo/web"
)
func main() {
registry, router, context := cookoo.Cookoo()
registry.Route("GET /", "The Homepage").
Does(MyMessage, "msg").
Does(web.Flush, "out").
Using("contentType").WithDefault("text/plain").
Using("content").From("cxt:msg")
web.Serve(registry, router, context)
}
This web example is more complicated than it needs to be in order to show how chains work. For the route "GET /"
, the commands MyMessage
and web.Flush
will happen in sequence.
First MyMessage
executes when the route is matched. Cookoo adds the return value from MyMessage
to the context with the key of "msg"
. Then web.Flush
executes. The Params
of "contentType"
and "content"
will be passed into web.Flush
. A default value is passed into "contentType"
. For "content"
, the value on the context
in the key "msg"
is passed in.
The registry
contains the mapping of commands, including their inputs and outputs, to route. Routes can be console commands, URL paths, and so one. The router
does the matching between the input to the application and the routes. The context
is passed along the chain and is available in each command. It provides easy access to logging, data sources that are long lived and shared between requests, and so on.
Let’s take a look at an example command.
// MyMessage builds a simple text message and put it in the context.
func MyMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
tpl := "Route %s executed on %s"
now := time.Now().Format("Jan 2, 2006 at 3:04pm (MST)")
name := c.Get("route.Name", "unknown").(string)
msg := fmt.Sprintf(tpl, name, now)
return msg, nil
}
The shared Context
and the Params
for this command are passed in and it expects a return value and an Interrupt
. The Interrupt
is used for errors, changing routes, and so on.
These are the base building blocks for building something with cookoo. There is a lot more you can do. If you’re interested in learning more, checkout the documentation or tutorials.