September 13, 2019

1769 words 9 mins read

gunk/gunk

gunk/gunk

Modern frontend and syntax for Protocol Buffers

repo name gunk/gunk
repo link https://github.com/gunk/gunk
homepage
language Go
size (curr.) 982 kB
stars (curr.) 288
created 2018-01-04
license MIT License

Gunk GoDoc Build Status

Gunk is a modern frontend and syntax for Protocol Buffers.

Quickstart | Installing | Syntax | Configuring | About | Releases

Overview

Gunk provides a modern project-based workflow along with a Go-derived syntax for defining types and services for use with Protocol Buffers. Gunk is designed to integrate cleanly with existing protoc based build pipelines, while standardizing workflows in a way that is familiar/accessible to Go developers.

Quickstart

Create a working directory for a project:

$ mkdir -p ~/src/example && cd ~/src/example

Install gunk and place the following Gunk definitions in example/util.gunk:

package util

// Util is a utility service.
type Util interface {
	// Echo returns the passed message.
	Echo(Message) Message
}

// Message contains an echo message.
type Message struct {
	// Msg is a message from a client.
	Msg string `pb:"1"`
}

Create the corresponding project configuration in example/.gunkconfig:

[generate go]

[generate js]
import_style=commonjs
binary

Then, generate protocol buffer definitions/code:

$ ls -A
.gunkconfig  util.gunk

$ gunk generate

$ ls -A
all.pb.go  all_pb.js  .gunkconfig  util.gunk

As seen above, gunk generated the corresponding Go and JavaScript protobuf code using the options defined in the .gunkconfig.

End-to-end Example

A end-to-end example gRPC server implementation, using Gunk definitions is available for review.

Debugging protoc commands

Underlying commands executed by gunk can be viewed with the following:

$ gunk generate -x
protoc-gen-go
protoc --js_out=import_style=commonjs,binary:/home/user/example --descriptor_set_in=/dev/stdin all.proto

Installing

The gunk command-line tool can be installed via Release, via Homebrew, via Scoop or via Go:

Installing via Release

  1. Download a release for your platform
  2. Extract the gunk or gunk.exe file from the .tar.bz2 or .zip file
  3. Move the extracted executable to somewhere on your $PATH (Linux/macOS) or %PATH% (Windows)

Installing via Homebrew (macOS)

gunk is available in the gunk/gunk tap, and can be installed in the usual way with the brew command:

# add tap
$ brew tap gunk/gunk

# install gunk
$ brew install gunk

Installing via Scoop (Windows)

gunk can be installed using Scoop:

# install scoop if not already installed
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')

scoop install gunk

Installing via Go

gunk can be installed in the usual Go fashion:

# install gunk
$ go get -u github.com/gunk/gunk

Protobuf Dependency and Caching

The gunk command-line tool uses the protoc command-line tool. gunk can be configured to use protoc at a specified path. If it isn’t available, gunk will download the latest protobuf release to the user’s cache, for use. It’s also possible to pin a specific version, see the section on protoc configuration.

Protocol Types and Messages

Gunk provides an alternate, Go-derived syntax for defining protocol buffers. As such, Gunk definitions are a subset of the Go programming language. Additionally, a special +gunk annotation is recognized by gunk, to allow the declaration of protocol buffer options:

package message

import "github.com/gunk/opt/http"

// Message is a Echo message.
type Message struct {
	// Msg holds a message.
	Msg  string `pb:"1" json:"msg"`
	Code int    `pb:"2" json:"code"`
}

// Util is a utility service.
type Util interface {
	// Echo echoes a message.
	//
	// +gunk http.Match{
	// 	Method:	"POST",
	// 	Path:	"/v1/echo",
	// 	Body:	"*",
	// }
	Echo(Message) Message
}

Scalars

Gunk’s Go-derived syntax uses the canonical Go scalar types of the proto3 syntax, defined by the protocol buffer project:

Proto3 Type Gunk Type
double float64
float float32
int32 int
int32 int32
int64 int64
uint32 uint
uint32 uint32
uint64 uint64
bool bool
string string
bytes []byte

Note: Variable-length scalars will be enabled in the future using a tag parameter.

Messages

Gunk’s Go-derived syntax uses Go’s struct type declarations for declaring messages, and require a pb:"<field_number>" tag to indicate the field number:

type Message struct {
	FieldA string `pb:"1"`
}

type Envelope struct {
	Message Message  `pb:"1" json:"msg"`
}

There are additional tags (for example, the json: tag above), that will be recognized by gunk format, and passed on to generators, where possible.

Note: When using gunk format, a valid pb:"<field_number>" tag will be automatically inserted if not declared.

Services

Gunk’s Go-derived syntax uses Go’s interface syntax for declaring services:

type SearchService interface {
	Search(SearchRequest) SearchResponse
}

The above is equivalent to the following protobuf syntax:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

Enums

Gunk’s Go-derived syntax uses Go const’s for declaring enums:

type MyEnum int

const (
	MYENUM MyEnum = iota
	MYENUM2
)

Note: values can also be fixed numeric values or a calculated value (using iota).

Maps

Gunk’s Go-derived syntax uses Go map’s for declaring map fields:

type Project struct {
	ProjectID string `pb:"1" json:"project_id"`
}

type GetProjectResponse struct {
	Projects map[string]Project `pb:"1"`
}

Repeated Values

Gunk’s Go-derived syntax uses Go’s slice syntax ([]) for declaring a repeated field:

type MyMessage struct {
	FieldA []string `pb:"1"`
}

Message Streams

Gunk’s Go-derived syntax uses Go chan syntax for declaring streams:

type MessageService interface {
	List(chan Message) chan Message
}

The above is equivalent to the following protobuf syntax:

service MessageService {
  rpc List(stream Message) returns (stream Message);
}

Protocol Options

Protocol buffer options are standard messages (ie, a struct), and can be attached to any service, message, enum, or other other type declaration in a Gunk file via the doccomment preceding the type, field, or service:

// MyOption is an option.
type MyOption struct {
	Name string `pb:"1"`
}

// +gunk MyOption {
// 	Name: "test",
// }
type MyMessage struct {
	/* ... */
}

Project Configuration Files

Gunk uses a top-level .gunkconfig configuration file for managing the Gunk protocol definitons for a project:

# Example .gunkconfig for Go, grpc-gateway, Python and JS
[generate go]
out=v1/go
plugins=grpc

[generate]
out=v1/go
command=protoc-gen-grpc-gateway
logtostderr=true

[generate python]
out=v1/python

[generate js]
out=v1/js
import_style=commonjs
binary

Project Search Path

When gunk is invoked from the command-line, it searches the passed package spec (or current working directory) for a .gunkconfig file, and walks up the directory hierarchy until a .gunkconfig is found, or the project’s root is encountered. The project root is defined as the top-most directory containing a .git subdirectory, or where a go.mod file is located.

Format

The .gunkconfig file format is compatible with Git config syntax, and in turn is compatible with the INI file format:

[generate]
command=protoc-gen-go

[generate]
out=v1/js
protoc=js

Section [protoc]

The path where to check for (or where to download) the protoc binary can be configured. The version can also be pinned.

Parameters

  • version - the version of protoc to use. If unspecified, defaults to the latest release available. Otherwise, gunk will either download the specified version, or check that the version of protoc at the specified path matches what was configured.

  • path - the path to check for the protoc binary. If unspecified, defaults appropriate user cache directory for the user’s OS. If no file exists at the path, gunk will attempt to download protoc.

Section [generate[ <type>]]

Each [generate] or [generate <type>] section in a .gunkconfig corresponds to a invocation of the protoc-gen-<type> tool.

Parameters

Each name[=value] parameter defined within a [generate] section will be passed as a parameter to the protoc-gen-<type> tool, with the exception of the following special parameters that override the behavior of the gunk generate tool:

  • command - overrides the protoc-gen-* command executable used by gunk generate. The executable must be findable on $PATH (Linux/macOS) or %PATH% (Windows), or may be the full path to the executable. If not defined, then command will be protoc-gen-<type>, when <type> is the value in [generate <type>].

  • protoc - overrides the <type> value, causing gunk generate to use the protoc value in place of <type>.

  • out - overrides the output path of protoc. If not defined, output will be the same directory as the location of the .gunk files.

All other name[=value] pairs specified within the generate section will be passed as plugin parameters to protoc and the protoc-gen-<type> generators.

Short Form

The following .gunkconfig:

[generate go]

[generate js]
out=v1/js

is equivalent to:

[generate]
command=protoc-gen-go

[generate]
out=v1/js
protoc=js

Third-Party Protobuf Options

Gunk provides the +gunk annotation syntax for declaring protobuf options, and specially recognizes some third-party API annotations, such as Google HTTP options, including all builtin/standard protoc options for code generation:

// +gunk java.Package("com.example.message")
// +gunk java.MultipleFiles(true)
package message

import (
	"github.com/gunk/opt/http"
	"github.com/gunk/opt/file/java"
)

type Util interface {
	// +gunk http.Match{
	// 	Method:	"POST",
	// 	Path:	"/v1/echo",
	// 	Body:	"*",
	// }
	Echo()
}

Further documentation on available options can be found at the Gunk options project.

Formatting Gunk Files

Gunk provides the gunk format command to format .gunk files (akin to gofmt):

$ gunk format /path/to/file.gunk
$ gunk format <pathspec>

Converting Existing Protobuf Files

Gunk provides the gunk convert command that will converting existing .proto files (or a directory) to the Go-derived Gunk syntax:

$ gunk convert /path/to/file.proto
$ gunk convert /path/to/protobuf/directory

If your .proto is referencing another .proto from another directory, you can add import_path in the global section of your .gunkconfig. If you don’t provide import_path it will only search in the root directory.

import_path=relative/path/to/protobuf/directory

The path to provide is relative from the .gunkconfig location.

Furthermore, the referenced files must contain:

option go_package="path/of/go/package";

The resulting .gunk file will contain the import path as defined in go_package:

import (
	name "path/of/go/package"
)

About

Gunk is developed by the team at Brankas, and was designed to streamline API design and development.

History

From the beginning of the company, the Brankas team defined API types and services in .proto files, leveraging ad-hoc Makefile’s, shell scripts, and other non-standardized mechanisms for generating Protocol Buffer code.

As development exploded in 2017 (and beyond) with continued addition of backend microservices/APIs, more code repositories and projects, and team members, it became necessary to standardize tooling for the organization as well as reduce the cognitive load of developers (who for the most part were working almost exclusively with Go) when declaring gRPC and REST services.

Naming

The Gunk name has a cheeky, backronym “Gunk Unified N-terface Kompiler”, however the name was chosen because it was possible to secure the GitHub gunk project name, was short, concise, and not used by other projects.

Additionally, “gunk” is an apt description for the “gunk” surrounding protocol definition, generation, compilation, and delivery.

Contributing

Issues, Pull Requests, and other contributions are greatly welcomed and appreciated! Get started with building and running gunk:

# clone source repository
$ git clone https://github.com/gunk/gunk.git && cd gunk

# force GO111MODULES
$ export GO111MODULE=on

# build and run
$ go build && ./gunk

Dependency Management

Gunk uses Go modules for dependency management, and as such requires Go 1.11+. Please run go mod tidy before submitting any PRs:

$ export GO111MODULE=on
$ cd gunk && go mod tidy
comments powered by Disqus