0% found this document useful (0 votes)
6 views143 pages

Go Best Practise

The document discusses best practices for designing Go programs, emphasizing the importance of a well-structured development environment, repository organization, and adherence to Go's formatting and style guidelines. It covers configuration management, package naming conventions, and the use of channels for concurrency, while also highlighting common pitfalls to avoid. The content is aimed at improving the overall quality and maintainability of Go applications.

Uploaded by

xxu39727
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views143 pages

Go Best Practise

The document discusses best practices for designing Go programs, emphasizing the importance of a well-structured development environment, repository organization, and adherence to Go's formatting and style guidelines. It covers configuration management, package naming conventions, and the use of channels for concurrency, while also highlighting common pitfalls to avoid. The content is aimed at improving the overall quality and maintainability of Go applications.

Uploaded by

xxu39727
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 143

Successful Go program design

Six years on
R OB OT S
Successful Go program design
Six years on
My background

–∞ +∞
My background

–∞
C + + +∞
My background

–∞
C + + 2009 +∞
My background

–∞
C + + 2009 Go +∞
My background

–∞
C + + 2009 Go +∞
My background
• github.com/peterbourgon/diskv

• developers.soundcloud.com/blog/go-at-soundcloud

• github.com/soundcloud/roshi

• github.com/weaveworks/scope

• github.com/go-kit/kit
1. Dev environment
Dev environment
• $GOPATH

• Single global $GOPATH – still the easiest/best

• Per-project $GOPATH – OK for binaries, see getgb.io

• Two-entry $GOPATH – OK for strict internal/external separation

• Put $GOPATH/bin in your $PATH


Dev environment
• $GOPATH

• Single global $GOPATH – still the easiest/best

• Per-project $GOPATH – OK for binaries, see getgb.io

• Two-entry $GOPATH – OK for strict internal/external separation

• Put $GOPATH/bin in your $PATH


TOP
TIP
2. Repo structure
Repo structure

• Private/internal – go nuts: own GOPATH, custom build tools, etc.

• Public/OSS – please play nice with go get

• Command || library – base dir + subdirs for other packages

• Command && library – which is primary? Optimize for use…


Repo structure
github.com/peterbourgon/foo/
main.go
main_test.go
handlers.go
handlers_test.go
compute.go
compute_test.go
lib/
foo.go
foo_test.go
bar.go
bar_test.go
Repo structure
github.com/peterbourgon/foo/
main.go
main_test.go
handlers.go pac k ag e main
handlers_test.go
compute.go
compute_test.go
lib/
foo.go
foo_test.go
package foo
bar.go
bar_test.go
Repo structure
github.com/peterbourgon/foo/
main.go
main_test.go
handlers.go pac k ag e main
handlers_test.go
compute.go
compute_test.go
lib/
foo.go TOP
foo_test.go
package foo TIP
bar.go
bar_test.go
Repo structure
github.com/peterbourgon/foo/
main.go
main_test.go
handlers.go pac k ag e main
github.com/t handlers_test.go
senart/vegeta compute.go
compute_test.go
lib/
foo.go
foo_test.go
package foo
bar.go
bar_test.go
Repo structure
github.com/peterbourgon/foo/
foo.go
foo_test.go
bar.go
bar_test.go
cmd/
foo/
main_test.go
handlers.go
handlers_test.go
compute.go
compute_test.go
Repo structure
github.com/peterbourgon/foo/
foo.go
foo_test.go
bar.go
package foo
bar_test.go
cmd/
foo/
main_test.go
handlers.go pac k ag e main
handlers_test.go
compute.go
compute_test.go
Repo structure
github.com/peterbourgon/foo/
foo.go
foo_test.go
bar.go
package foo
bar_test.go
cmd/
foo/
github.com/c
onstabulary/ main_test.go
gb handlers.go c k ag e main
pa
handlers_test.go
compute.go
compute_test.go
3. Formatting and style
Formatting and style
• Go has strong opinions – abide by them

• Format (gofmt) on save – no excuses

• github.com/golang/go/wiki/CodeReviewComments

• bit.ly/GoCodeReview

• talks.golang.org/2014/names.slide

• bit.ly/GoNames
Formatting and style
• Go has strong opinions – abide by them

• Format (gofmt) on save – no excuses

• github.com/golang/go/wiki/CodeReviewComments

• bit.ly/GoCodeReview TOP
TIP
• talks.golang.org/2014/names.slide

• bit.ly/GoNames
4. Configuration
Configuration
• Configuration bridges environment and process domains

• Make it explicit!

• package flag – though I wish it were less esoteric...

• os.Getenv – too subtle, too implicit; avoid

• Env vars + flags – see the value, but document in usage!


Configuration
• Configuration bridges environment and process domains

• Make it explicit!

• package flag – though I wish it were less esoteric...

• TOP
os.Getenv – too subtle, too implicit; avoid TIP

• Env vars + flags – see the value, but document in usage!


Example program
package main

import (
"log"

"github.com/peterbourgon/foo/common"
)

func main() {
log.Print(common.HelloWorld)
}
Package naming
package main

import (
"log"

"github.com/peterbourgon/foo/consts"
)

func main() {
log.Print(consts.HelloWorld)
}
Package naming
package main

import (
"log"

"github.com/peterbourgon/foo/greetings"
)

func main() {
log.Print(greetings.HelloWorld)
}
Package naming
package main

import (
"log"

TOP
"github.com/peterbourgon/foo/greetings"
TIP
)

func main() {
log.Print(greetings.HelloWorld)
}
Dot import
package main

import (
"log"

. "github.com/peterbourgon/foo/greetings"
)

func main() {
log.Print(HelloWorld)
}
Dot import
package main

import (
"log"

. "github.com/peterbourgon/foo/greetings"
)

func main() {
log.Print(HelloWorld)
}
Dot import
package main

import (
"log"

. "github.com/peterbourgon/foo/greetings"
TOP
)
TIP

func main() {
log.Print(HelloWorld)
}
Flags
var stdout = flag.Bool("stdout", false, "log to stdout")

func init() {
flag.Init()
}

func main() {
if *stdout {
log.SetOutput(os.Stdout)
}
log.Print(greetings.HelloWorld)
}
Flags

func main() {
var stdout = flag.Bool("stdout", false, "log to stdout")
flag.Init()

if *stdout {
log.SetOutput(os.Stdout)
}
log.Print(greetings.HelloWorld)
}
Flags

func main() {
var stdout = flag.Bool("stdout", false, "log to stdout")
flag.Init()
TOP
if *stdout { TIP
log.SetOutput(os.Stdout)
}
http://bit.ly/GoFlags
log.Print(greetings.HelloWorld)
}
Construction
func main() {
var (
stdout = flag.Bool("stdout", false, "log to stdout")
fooKey = flag.String("fooKey", "", "access key for foo")
)
flag.Init()

foo, err := newFoo(*fooKey)


if err != nil {
log.Fatal(err)
}
defer foo.close()
Construction
foo, err := newFoo(
*fooKey,
bar,
baz,
100 * time.Millisecond,
nil,
)
if err != nil {
log.Fatal(err)
}
defer foo.close()
Construction
cfg := fooConfig{}
cfg.Bar = bar
cfg.Baz = baz
cfg.Period = 100 * time.Millisecond
cfg.Output = nil

foo, err := newFoo(*fooKey, cfg)


if err != nil {
log.Fatal(err)
}
defer foo.close()
Construction
cfg := fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Output: nil,
}
foo, err := newFoo(*fooKey, cfg)
if err != nil {
log.Fatal(err)
}
defer foo.close()
Construction
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Output: nil,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Construction
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz, TOP
Period: 100 * time.Millisecond, TIP
Output: nil,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Usable defaults
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Output: nil,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Usable defaults

func (f *foo) process() {


if f.Output != nil {
fmt.Fprintf(f.Output, "beginning\n")
}
// ...
}
Usable defaults

func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
// ...
}
Usable defaults
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Output: ioutil.Discard,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Usable defaults
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Output: ioutil.Discard,
TOP
TIP
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Smart constructors

func newFoo(..., cfg fooConfig) *foo {


if cfg.Output == nil {
cfg.Output = ioutil.Discard
}
// ...
}
Smart constructors

foo, err := newFoo(*fooKey, fooConfig{


Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Smart constructors

foo, err := newFoo(*fooKey, fooConfig{


Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
TOP
})
TIP
if err != nil {
log.Fatal(err)
}
defer foo.close()
Cross-referential components

type bar struct {


baz *baz
// ...
}

type baz struct {


bar *bar
// ...
}
Cross-referential components

type bar struct {


baz *baz bar := newBar(...)
// ... baz := newBaz(...)
}
bar.baz = baz
type baz struct { baz.bar = bar
bar *bar
// ... // :(
}
Cross-referential components

• Combine

• Split

• Externalize communication
Combine
type bar struct {
baz *baz
// ...


} type barbaz struct {
// ...
type baz struct { }
bar *bar
// ...
}
Split
type bar struct {
a *atom
monad
// ... a := &atom{...}


} m := newMonad(...)

type baz struct { bar := newBar(a, m, ...)


atom baz := newBaz(a, m, ...)
m *monad
// ...
}
Split
type bar struct {
a *atom
monad
// ... a := &atom{...}


} m := newMonad(...)

type baz struct { bar := newBar(a, m, ...)


atom baz := newBaz(a, m, ...)
m *monad
// ...
}
Externalize communication
type bar struct {
toBaz chan<- event
// ...
c := make(chan event)


}

bar := newBar(c, ...)


type baz struct {
baz := newBaz(c, ...)
fromBar <-chan event
// ...
}
Externalize communication
type bar struct {
toBaz chan<- event
// ...
c := make(chan event)


}

bar := newBar(c, ...)


type baz struct {
baz := newBaz(c, ...)
fromBar <-chan event
// ...
}
X. Concurrency patterns
Channels are bad?
Channels are bad?

N O
Channels are fine

• Sharing memory between goroutines – use a mutex

• Orchestrating goroutines – use channels

• "Channels orchestrate; mutexes serialize."

• go-proverbs.github.io
Good uses for a channel

semaphore := make(chan struct{}, 3)


for i := 0; i < 1000; i++ {
go func() {
semaphore <- struct{}{}
defer func() { <-semaphore }()
// process
}()
}
Good uses for a channel
resultc := make(chan int, n)

// Scatter
for i := 0; i < n; i++ {
go func() {
resultc <- process()
}()
}

// Gather
for i := 0; i < n; i++ {
fmt.Println(<-resultc)
}
Good uses for a channel
func (f *foo) loop() {
func (f *foo) set(k, v string) {
for {
f.setc <- setReq{k, v}
select {
}
case req := <-f.setc:
f.m[req.k] = req.v
func (f *foo) get(k string) string {
req := getReq{k, make(chan string)}
case req := <-f.getc:
f.getc <- req
req.res <- f.m[req.k]
return <-req.res
}
case <-f.quitc:
return
func (f *foo) stop() {
}
close(f.quitc)
}
}
}
Good uses for a channel
func (f *foo) set(k, v string) {
f.actionc <- func() {
f.m[k] = v func (f *foo) loop() {
} for {
} select {
case fn := <-f.actionc:
func (f *foo) get(k string) (v string) { fn()
done := make(chan struct{})
f.actionc <- func() { case <-f.quitc:
v = f.m[k] return
close(done) }
} }
<-done }
return v
}
Good uses for a channel
func (f *foo) set(k, v string) {
f.actionc <- func() {
f.m[k] = v func (f *foo) loop() {
} for {
} select {
case fn := <-f.actionc:
func (f *foo) get(k string) (v string) { fn()
TOP
done := make(chan struct{})
TIP
f.actionc <- func() { case <-f.quitc:
v = f.m[k] return
close(done) }
} }
<-done }
return v
}
Bad uses for a channel

type foo struct {


m map[string]string
setc chan setReq
getc chan getReq
quitc chan struct{}
}
Bad uses for a channel

type foo struct {


m map[string]string
mtx sync.RWMutex
}
Bad uses for a channel

func iterator() (<-chan string) {


// ...
}
Bad uses for a channel

func iterator(cancel <-chan struct{}) (<-chan string) {


// ...
}
Bad uses for a channel

func iterator() (results <-chan string, cancel chan<- struct{}) {


// ...
}
Bad uses for a channel

func iterator(results chan<- string, cancel <-chan struct{}) {


// ...
}
Bad uses for a channel

func iterator(f func(item) error) {


// ...
}
Bad uses for a channel

func iterator(f func(item) error) { TOP


// ... TIP
}
Construction

foo, err := newFoo(*fooKey, fooConfig{


Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Be explicit

foo, err := newFoo(*fooKey, fooConfig{


Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Be explicit

foo, err := newFoo(*fooKey, fooConfig{


Bar: bar,
Baz: baz, DEPENDENCIES
Period: 100 * time.Millisecond,
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
MAKE
TOP
TIP DEPENDENCIES TOP
TIP

EXPLICIT
Dependencies

func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
result := f.Bar.compute()
log.Printf("bar: %v", result)
// ...
}
Dependencies

func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
result := f.Bar.compute()
log.Printf("bar: %v", result)
// ...
}
Dependencies

Not a dependency func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
Dependency result := f.Bar.compute()
c y log.Printf("bar: %v", result)
Depen den
// ...
}
Dependencies

func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
result := f.Bar.compute()
f.Logger.Printf("bar: %v", result)
// ...
}
Dependencies

func (f *foo) process() {


fmt.Fprintf(f.Output, "beginning\n")
result := f.Bar.compute()
TOP
f.Logger.Printf("bar: %v", result)
TIP
// ...
}
Dependencies
foo, err := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
Logger: log.NewLogger(dst, ...),
})
if err != nil {
log.Fatal(err)
}
defer foo.close()
Dependencies

func newFoo(..., cfg fooConfig) *foo {


if cfg.Output == nil {
cfg.Output = ioutil.Discard
}
if cfg.Logger == nil {
cfg.Logger = log.NewLogger(ioutil.Discard, ...)
}
// ...
}
MAKE
TOP
TIP DEPENDENCIES TOP
TIP

EXPLICIT
5. Logging and instrumentation
Logging
• More expensive than you think

• Actionable info only – read by humans or consumed by machines

• Avoid many levels – info+debug is fine

• Use structured logging – key=val

• Loggers are dependencies, not globals!


Instrumentation
• Cheaper than you think

• Instrument every significant component of your system

• Resource – Utilization, Saturation, Error count (USE, Brendan Gregg)

• Endpoint – Request rate, Error rate, Duration (RED, Tom Wilkie)

• Use Prometheus

• Metrics are dependencies, not globals!


Logging and instrumentation

• blog.raintank.io/logs-and-metrics-and-graphs-oh-my

• bit.ly/GoLogsAndMetrics

• peter.bourgon.org/blog/2016/02/07/logging-v-instrumentation.html

• bit.ly/GoLoggingVsInstrumentation
Global state

• log.Print uses a fixed, global log.Logger

• http.Get uses a fixed, global http.Client

• database/sql uses a fixed, global driver registry

• func init exists only to have side effects on package-global state


Global state

• log.Print uses a fixed, global log.Logger

• http.Get uses a fixed, global http.Client

• database/sql uses a fixed, global driver registry

• func init exists only to have side effects on package-global state


Eliminate implicit global deps

func foo() {
resp, err := http.Get("http://zombo.com")
// ...
}
Eliminate implicit global deps

func foo(client *http.Client) {


resp, err := client.Get("http://zombo.com")
// ...
}
Eliminate implicit global deps

func foo(doer Doer) {


req, _ := http.NewRequest("GET", "http://zombo.com", nil)
resp, err := doer.Do(req)
// ...
}
Eliminate global state
var registry = map[string]*http.Client{}

func init() {
func exec(driver string) {
registry["default"] = &http.Client{}
client := registry[driver]
}
if client == nil {
client = registry["default"]
func main() {
}
if cond {
// ...
registry[key] = otherClient
}
}
// ...
exec(driver)
}
Eliminate global state
func init() {
registry["default"] = &http.Client{}
}
func exec(driver string) {
client := registry[driver]
func main() {
if client == nil {
var registry = map[string]*http.Client{}
client = registry["default"]
// ...
}
if cond {
// ...
registry[key] = otherClient
}
}
// ...
exec(driver)
}
Eliminate global state
func init() {
//
}
func exec(driver string) {
client := registry[driver]
func main() {
if client == nil {
registry := map[string]*http.Client{}
client = registry["default"]
registry["default"] = &http.Client{}
}
// ...
// ...
if cond {
}
registry[key] = otherClient
}
// ...
exec(driver)
}
Eliminate global state
func init() {
//
}
func exec(driver string) {
client := registry[driver]
func main() {
if client == nil {
registry := map[string]*http.Client{
client = registry["default"]
"default": &http.Client{},
}
}
// ...
// ...
}
if cond {
registry[key] = otherClient
}
// ...
exec(driver)
}
Eliminate global state
TOP
TIP
func exec(driver string) {
client := registry[driver]
func main() {
if client == nil {
registry := map[string]*http.Client{
client = registry["default"]
"default": &http.Client{},
}
}
// ...
// ...
}
if cond {
registry[key] = otherClient
}
// ...
exec(driver)
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(driver string) {
}
client := registry[driver]
// ...
if client == nil {
if cond {
client = registry["default"]
registry[key] = otherClient
}
}
// ...
// ...
}
exec(driver)
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(
}
driver string,
// ...
registry map[string]*http.Client,
if cond {
) {
registry[key] = otherClient
client := registry[driver]
}
if client == nil {
// ...
client = registry["default"]
exec(driver, registry)
}
}
// ...
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(
}
client *http.Client,
// ...
) {
if cond {
client := registry[driver]
registry[key] = otherClient
if client == nil {
}
client = registry["default"]
// ...
}
exec(driver, registry)
// ...
}
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(
}
client *http.Client,
// ...
) {
if cond {
// ...
registry[key] = otherClient
}
}
// ...
client := registry[driver]
if client == nil {
client = registry["default"]
}
exec(driver, registry)
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(
}
client *http.Client,
// ...
) {
if cond {
// ...
registry[key] = otherClient
}
}
// ...
client := registry[driver]
if client == nil {
client = registry["default"]
}
exec(client)
}
Eliminate global state
func main() {
registry := map[string]*http.Client{
"default": &http.Client{},
func exec(client *http.Client) {
}
// ...
// ...
}
if cond {
registry[key] = otherClient
}
// ...
client := registry[driver]
if client == nil {
client = registry["default"]
}
exec(client)
}
Eliminate global state
func main() {
client := &http.DefaultClient{}

func exec(client *http.Client) {


// ...
// ...
}
if cond {
registry[key] = otherClient
}
// ...
client := registry[driver]
if client == nil {
client = registry["default"]
}
exec(client)
}
Eliminate global state
func main() {
client := &http.DefaultClient{}

func exec(client *http.Client) {


// ...
// ...
}
if cond {
client = otherClient
}
// ...
client := registry[driver]
if client == nil {
client = registry["default"]
}
exec(client)
}
Eliminate global state
func main() {
client := &http.DefaultClient{}

func exec(client *http.Client) {


// ...
// ...
}
if cond {
client = otherClient
}
// ...

exec(client)
}
Eliminate global state
func main() {
client := &http.DefaultClient{}
// ...
func exec(client *http.Client) {
if cond {
// ...
client = otherClient
}
}
// ...
exec(client)
}
6. Testing
Testing

• Testing is programming – nothing special

• package testing continues to be well-suited to the task

• TDD/BDD packages bring new, unfamiliar DSLs and structures

• You already have a language for writing tests – called Go


Design for testing

• Write code in functional style

• Take dependencies explicitly, as parameters

• Avoid depending on or mutating global state!

• Make heavy use of interfaces


Design for testing
func process(db *database) (result, error) {
rows, err := db.Query("SELECT foo")
if err != nil { func main() {
return result{}, err db := newDatabase()
} r, err := process(db)
defer rows.Close() }
var r result
if err := rows.Scan(&r); err != nil {
return result{}, err
}
return r, nil
}

119
Design for testing
func process(db *database) (result, error) {
rows, err := db.Query("SELECT foo")
if err != nil { func main() {
return result{}, err db := newDatabase()
} r, err := process(db)
defer rows.Close() }
var r result
if err := rows.Scan(&r); err != nil { type queryer interface {
return result{}, err Query(s string) (rows, error)
} }
return r, nil
}

120
Design for testing
func process(q queryer) (result, error) {
rows, err := db.Query("SELECT foo")
if err != nil { func main() {
return result{}, err db := newDatabase()
} r, err := process(db)
defer rows.Close() }
var r result
if err := rows.Scan(&r); err != nil { type queryer interface {
return result{}, err Query(s string) (rows, error)
} }
return r, nil
}

121
Design for testing

type fakeQueryer struct{}

func (q fakeQueryer) Query(s string) (rows, error) {


return []row{"fakerow"}, nil
}

122
Design for testing
func TestProcess(t *testing.T) {
q := fakeQueryer{}
have, err := process(q)
if err != nil {
t.Fatal(err)
}
want := result{"fakedata"} // or whatever
if want != have {
t.Errorf("process: want %v, have %v", want, have)
}
}

123
Design for testing
func process(q queryer) (result, error) {
rows, err := db.Query("SELECT foo")
if err != nil { func main() {
return result{}, err db := newDatabase()
} r, err := process(db)
defer rows.Close() }
var r result
if err := rows.Scan(&r); err != nil { type queryer interface {
return result{}, err Query(s string) (rows, error)
} }
return r, nil
}
TOP
TIP

124
TOP
Design for testing
TIP
func process(q queryer) (result, error) {
rows, err := db.Query("SELECT foo")
if err != nil { func main() {
return result{}, err db := newDatabase()
} r, err := process(db)
defer rows.Close() }
var r result
if err := rows.Scan(&r); err != nil { type queryer interface {
return result{}, err Query(s string) (rows, error)
} }
return r, nil
}

125
7. Dependency management
Dependency management

• Vendoring is still the solution

• GO15VENDOREXPERIMENT is the future – use it

• The tools have gotten a lot better


Dependency management

• github.com/FiloSottile/gvt – minimal, copies manually

• github.com/dpw/vendetta – minimal, via git submodules

• github.com/Masterminds/glide – maximal, manifest + lock file

• github.com/constabulary/gb – go tool replacement for binaries


Dependency management

• github.com/FiloSottile/gvt – minimal, copies manually TOP


TIP
• github.com/dpw/vendetta – minimal, via git submodules

• github.com/Masterminds/glide – maximal, manifest + lock file

• github.com/constabulary/gb – go tool replacement for binaries


Caveat for libraries... !

• Dependency management is a concern of the binary author

• Libraries with vendored deps are very difficult to use

• In general, libraries should not vendor dependencies

• If your library has hermetically-sealed deps – proceed with caution


Caveat for libraries... !

• Dependency management is a concern of the binary author

• Libraries with vendored deps are very difficult to use


TOP
TIP
• In general, libraries should not vendor dependencies

• If your library has hermetically-sealed deps – proceed with caution


8. Build and deploy
Build

• Prefer go install to go build

• If you produce a binary, your responsibilities have grown

• Don't be afraid of new approaches to manage complexity – gb

• Since Go 1.5 cross-compilation is built-in – no need for extra tools


Deploy

• We have it relatively easy

• If you deploy in containers – FROM scratch

• Think carefully before choosing a platform or orchestration system

• An elegant monolith is very productive


Deploy

• We have it relatively easy


TOP
TIP
• If you deploy in containers – FROM scratch

• Think carefully before choosing a platform or orchestration system

• An elegant monolith is very productive


Summary
Top Tips
• Put $GOPATH/bin in your $PATH

• Name github.com/yourname/foo/lib as "package foo"

• Name things well – bit.ly/GoNames

• Avoid using os.Getenv by itself for configuration

• Name packages for what they provide, not what they contain
Top Tips
• Never use the dot import

• Define and scope flags in func main

• Use struct literal initialization

• Avoid nil checks with no-op implementations

• Make the zero value useful, especially with config objects


Top Tips
• Consider modeling actor pattern (for/select) as a single chan of funcs

• Model iterators as functions that take callbacks

• MAKE DEPENDENCIES EXPLICIT

• Loggers are dependencies

• Init smells really, really bad


Top Tips
• Define client-side interfaces to represent consumer contracts

• Take dependencies as interfaces

• Use gvt, vendetta, glide, or gb to manage vendoring for your binary

• Probably don't use vendoring for your library

• If you deploy in containers – FROM scratch


Go kit

A toolkit for microservices


github.com/go-kit/kit
1 year · 41 contributors
http://weave.works
Thank you! Questions?
@peterbourgon

You might also like