goagen, the goa Tool


Code Generation Tool

goagen is a tool that generates various artifacts from a goa design package.

Install it with:

go install github.com/goadesign/goa/goagen

Each type of artifact is associated with a goagen command that exposes it own set of flags. Internally these commands map to “generators” that contain the logic for generating the artifacts. It works something like this:

  1. goagen parses the command line to determine the type of output desired and invokes the appropriate generator.
  2. The generator writes the code of the tool that will produce the final output to a temporary directory.
  3. The tool composed of the design package and the tool code is compiled in the temporary directory.
  4. The tool is run, it traverses the design data structures to write the final output.

Each generator is exposed via a command of the goagen tool, goagen --help lists all the available commands. These are:

  • app: generates the service boilerplate code including controllers, contexts, media types and user types.
  • main: generates a skeleton file for each resource controller as well as a default main.
  • client: generates an API client Go package and tool.
  • js: generates a JavaScript API client.
  • swagger: generates the API Swagger specification.
  • schema: generates the API Hyper-schema JSON.
  • gen: invokes a third party generator.
  • controller: generates controller scaffolding for each resource.
  • bootstrap: invokes the app, main, client and swagger generators.

Working With goagen: Scaffold vs. Generated Code

As the design evolves and goagen needs to be run multiple times it is important to understand the distinction between scaffold and generated outputs.

The scaffold code generated by the main command is generated once as a way to get started quickly. This code is meant to be edited, tested and in general handled like any other file written “manually”. By default, goagen main does not override the scaffold files if they already exist, although it can update existing scaffold files if the --regen argument is passed. The main package of the client tool generated by the client command is also scaffold.

The generated artifacts consisting of the outputs of all the other commands (and the output of the client command with the exception of the tool main package) should not and cannot be edited. The artifacts are regenerated from scratch every time goagen is run. User code uses the generated Go packages by importing them and consuming the public functions and data structures like any other Go package.

Common flags

The following flags apply to all the goagen commands:

  • --design|-d=DESIGN defines the Go package path to the service design package.
  • --out|-o=OUT specifies where to generate the files, defaults to the current directory.
  • --debug enables goagen debug. This causes goagen to print the content of the temporary files and to leave them around.
  • --help prints contextual help.

Contexts and Controllers: goagen app

The app command is arguably the most critical. It generates all the supporting code for the goa service. The command also generates a test sub-package that contain test helpers, one per controller action and view they return. These helpers make it easy to test the action implementations by invoking them with controlled values and validating the returned media types. This command supports a couple of additional flags:

  • --pkg=app specifies the name of the generated Go package, defaults to app. That’s also the name of the subdirectory that gets created to store the generated Go files.
  • --notest prevents the generation of the the test helpers.

This command always deletes and re-creates any pre-existing directory with the same name. The idea being that these files should never be edited.

Scaffolding: goagen main

The main command helps bootstrap a new goa service by generating a default main.go as well as a default (empty) implementation for each resource controller defined in the design package. By default, this command only generates the files if they don’t exist in the output directory. This command accepts two additional flags:

  • --force causes the files to be generated even if files with the same name already exist, overwriting the files with new empty scaffolding.
  • --regen causes the scaffolding to be regenerated in existing files, leaving controller implementations in place.

A typical workflow would include bootstrapping your service with goagen main, then as new resources and actions are added to the design, running goagen main --regen to generate the new empty controller implementations while retaining your existing controllers.

Note: in order for --regen to function, controller implementations MUST be placed between the comments:

// ControllerName_Action: start_implement

... your logic ...

// ControllerName_Action: end_implement

The regeneration code relies on these comments to locate non-scaffold code it should retain.

Client Package and Tool: goagen client

The client command generates both an API client package and a CLI tool. The client package implements a Client object that exposes one method for each resource action. It also exposes methods to create the corresponding http request objects without sending them. The generated code of the CLI tool leverages the package to make the API requests to the service.

The Client object is configured to use request signers that get invoked prior to sending the request if security is enabled for the Action (that is if the action DSL makes use of the Security function). The signers modify the request to include auth headers for example. goa comes with signers that implement basic auth, JWT auth, API key and a subset of OAuth2.

JavaScript: goagen js

The js command generates a JavaScript API client suitable for both client-side and server-side applications. The generated code defines an anonymous AMD module and relies on the axios promise-based JavaScript library for making the actual HTTP requests.

The generated module wraps the axios client and adds API specific functions, for example:

// List all bottles in account optionally filtering by year
// path is the request path, the format is "/cellar/accounts/:accountID/bottles"
// years is used to build the request query string.
// config is an optional object to be merged into the config built by the function prior to making the request.
// The content of the config object is described here: https://github.com/mzabriskie/axios#request-api
// This function returns a promise which raises an error if the HTTP response is a 4xx or 5xx.
client.listBottle = function (path, years, config) {
        cfg = {
                timeout: timeout,
                url: urlPrefix + path,
                method: 'get',
                params: {
                        years: years
                },
                responseType: 'json'
        };
        if (config) {
                cfg = utils.merge(cfg, config);
        }
        return client(cfg);
}

The generated client module can be loaded using requirejs:

requirejs.config({
        paths: {
                axios: '/js/axios.min',
                client: '/js/client'
        }
});
requirejs(['client'], function (client) {
        client().listBottle ("/cellar/accounts/440/bottles", 317)
                .then(function (resp) {
                        // All good, use resp
                })
                .catch(function (resp) {
                        // Woops, request failed or returned 4xx or 5xx.
                });
});

The code above assumes that the generated files client.js and axios.min.js are both served from /js. The resp value returned to the promise is an object with the following fields:

{
        // `data` is the response that was provided by the server
        data: {},

        // `status` is the HTTP status code from the server response
        status: 200,

        // `statusText` is the HTTP status message from the server response
        statusText: 'OK',

        // `headers` is the headers that the server responded with
        headers: {},

        // `config` is the config that was provided to `axios` for the request
        config: {}
}

The generator also produces an example HTML and controller that can be mounted on a goa service to quickly test the JavaScript. Simply import the js Go package in your service main and mount the controller. The example HTML is served under /js so that loading this path in a browser will trigger the generated JavaScript.

Swagger: goagen swagger

The swagger command generates a Swagger specification of the API. The command does not accept additional flags. It generates both the Swagger JSON and YAML. The generated files can be served from the API itself using a file server defined in the design with Files

JSON Schema: goagen schema

The schema command generates a Heroku-like JSON hyper-schema representation of the API. It generates both the JSON as well as a controller that can be mounted on the goa service to serve it under /schema.json.

Plugins: goagen gen

The gen command makes it possible to invoke goagen plugins. This command accepts one flag:

  • --pkg-path=PKG-PATH specifies the Go package import path to the plugin package.

Refer to the Generator Plugins section for details on goa plugins.

Scaffolding: goagen controller

The controller command generates a default (empty) implementation for each resource controller defined in the design package. By default this command only generates the files if they don’t exist in the output directory. This command accepts the following flags:

  • --force causes the files to be generated even if files with the same name already exist, overwriting the files with new empty scaffolding.
  • --regen causes the scaffolding to be regenerated in existing files, leaving controller implementations in place. See the main documentation for more details.
  • --res specifies the name of the resource and is compulsory.
  • --pkg specifies the name of the generated controller package, the behavior is as follows:
    • --out is not specified (default)
      • --pkg is not specified (default)
        • the package is generated as main. (This behavior matches the main and bootstrap commands.)
      • --pkg is specifield (bar)
        • the package is generated as bar.
    • --out is specified (foo)
      • --pkg is not specified (default)
        • the package is generated as foo.
      • --pkg is specifield (bar)
        • the package is generated as bar.
  • --app-pkg sets the name of the generated Go package containing the controllers supporting code (contexts, media types, user types etc.), defaults to app.

Workaround when Subversion .svn directories are deleted by goagen executable

When generating new code with goagen, and using Subversion < 1.7, all hidden repository metadata directories with name .svn were also deleted. Because of that, repository directory becomes unusable. For Subversion version >= 1.7 repository metadata is stored only in one root .svn directory, as by Git, and in that case this issue is not occuring. As workaround, I am calling goagen from start.bat file (on Windows):

rmdir /Q /S gentemp
mkdir gentemp\app\.svn
mkdir gentemp\app\test\.svn
mkdir gentemp\client\.svn
mkdir gentemp\swagger\.svn
mkdir gentemp\tool\cli\.svn

xcopy /s app\.svn\*.* gentemp\app\.svn
xcopy /s app\test\.svn\*.* gentemp\app\test\.svn
xcopy /s client\.svn\*.* gentemp\client\.svn
xcopy /s swagger\.svn\*.* gentemp\swagger\.svn
xcopy /s tool\cli\.svn\*.* gentemp\tool\cli\.svn

goagen bootstrap -d myprojects/myapi/design

mkdir app\.svn
xcopy /s gentemp\app\.svn\*.* app\.svn
mkdir app\.svn\tmp\prop-base
mkdir app\.svn\tmp\props
mkdir app\.svn\tmp\text-base

mkdir app\test\.svn
xcopy /s gentemp\app\test\.svn\*.* app\test\.svn
mkdir app\test\.svn\tmp\prop-base
mkdir app\test\.svn\tmp\props
mkdir app\test\.svn\tmp\text-base

mkdir client\.svn
xcopy /s gentemp\client\.svn\*.* client\.svn
mkdir client\.svn\tmp\prop-base
mkdir client\.svn\tmp\props
mkdir client\.svn\tmp\text-base

mkdir swagger\.svn
xcopy /s gentemp\swagger\.svn\*.* swagger\.svn
mkdir swagger\.svn\tmp\prop-base
mkdir swagger\.svn\tmp\props
mkdir swagger\.svn\tmp\text-base

mkdir tool\cli\.svn
xcopy /s gentemp\tool\cli\.svn\*.* tool\cli\.svn
mkdir tool\cli\.svn\tmp\prop-base
mkdir tool\cli\.svn\tmp\props
mkdir tool\cli\.svn\tmp\text-base

rmdir /Q /S gentemp