Go Course
Melbourne, May-June 2019
Cameron Hutchison
Daniel Cantos
Joshua Carpeggiani
Julia Ogris
Keilin Olsen
Ryan O'Kane
Sai Kiran Gummaraj
Cameron Hutchison
Daniel Cantos
Joshua Carpeggiani
Julia Ogris
Keilin Olsen
Ryan O'Kane
Sai Kiran Gummaraj
Find Slides at go-course.org (source on GitHub)
3
All talks take place at the Charles Goode Auditorium.
Attendance is optional.
The last 30 minutes of each talk are reserved for Q&A and help.
Please join #go-course on anzengineering.slack.com
Instructors
go-instructors@anz.com
@camh, Cameron.Hutchison@anz.com
@Dan, Daniel.Cantos@anz.com
@joshcarp, Joshua.Carpeggiani@anz.com
@julia, Julia.Ogris@anz.com
@keilin, Keilin.Olsen@anz.com
@ryanokane, Ryan.O'Kane@anz.com
@saikirian, SaiKiran.Gummaraj@anz.com
2007: Robert Griesemer, Rob Pike, and Ken Thompson start working on Go at Google
2008: Russ Cox (go mod) and Ian Taylor (gcc frontend) join
2009: Go goes Open Source
2012: Go version 1.0
(Source: Go and the Zen of Python)
10Hello world in Go
package main import ( "fmt" ) func main() { fmt.Println("Hello, Melbourne βοΈπΈπβ€οΈ") }
Execute with
language:bash go run hello.go
Go favours simplicity and directness resulting in some purposeful omissions:
gofmt
, goimports
, godoc
, go
mod
, go
test
, golangci-lint
, coverageHere is a great read on Rob Pike's blog about "Why Go" - (Less is Exponentially More)
13ββ build.gradle βββ gradle βΒ Β βββ wrapper βΒ Β βββ gradle-wrapper.jar βΒ Β βββ gradle-wrapper.properties βββ gradlew βββ gradlew.bat βββ settings.gradle βββ src βββ main βΒ Β βββ java βΒ Β βΒ Β βββ com βΒ Β βΒ Β βββ anz βΒ Β βΒ Β βββ demo βΒ Β βΒ Β βββ pingserver βΒ Β βΒ Β βββ PingserverApplication.java βΒ Β βΒ Β βββ controller βΒ Β βΒ Β βββ PingController.java <--------- THAT'S THE ONE βΒ Β βββ resources βΒ Β βββ application.properties βββ test ..... βββ PingserverApplicationTests.java
Source on GitHub
package com.anz.dcx.serverdemo.controller; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class PingController { @GetMapping(path = "/ping", produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody String ping() throws Exception { Thread.sleep(2000L); return "pong"; } }
Execute with
./gradlew clean bootRun
Source on GitHub
language:none βββ go.mod βββ go.sum βββ main.go
Execute with
language:bash go run main.go
package main import ( "fmt" "log" "net/http" "time" ) func main() { http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { time.Sleep(2000 * time.Millisecond) fmt.Fprint(w, "pong") }) addr := ":9090" fmt.Println("Starting webserver on port", addr) log.Fatal(http.ListenAndServe(addr, nil)) }
ab
commandab -n 19000 -c 19000 -s 200 -r localhost:8080/ping # Java ab -n 19000 -c 19000 -s 200 -r localhost:9090/ping # go
Java median: 102.005 sec Java 95%: 135.280 sec Go median: 14.954 sec Go 95%: 15.061 sec
Continuous Deployment != Continuous Delivery
"Continuous Deployment is a strategy for software releases wherein any code commit that passes the automated testing phase is automatically released into the production environment."
TechTarget definition
"Continuous deployment requires a highly developed culture of monitoring, being on call, and having the capacity to recover quickly."
Marko Anastasov, Semaphore Engineering Blog
master
branch on GitHub
Quality leads to the confidence to automatically deploy, so we need
ways to increase and maintain quality.
golangci-lint
run
language:none Settings -> Options: [ ] Allow merge commits [x] Allow squash merging [ ] Allow rebase merging Settings -> Branches -> [Add rule|Edit] `master`: [x] Require pull request reviews before merging [x] Dismiss stale pull request approvals when new commits are pushed [x] Require status checks to pass before merging [x] Require branches to be up to date before merging [x] Include administrators
chris.beams.io/posts/git-commit/
language:none $ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009" e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build. 2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests) 147709f Tweaks to package-info.java files 22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils 7f96f57 polishing
versus
language:none $ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014" 5ba3db6 Fix failing CompositePropertySourceTests 84564a0 Rework @PropertySource early parsing logic e142fd1 Add tests for ImportSelector meta-data 887815f Update docbook dependency and generate epub ac8326d Polish mockito usage
chris.beams.io/posts/git-commit/
git
rebase
-i
COMMIT_HASH
to rework your commits if necessarygit
add
-p
for two separate concerns addressed in the one file
Don't be afraid!
Try it out on a new, throwaway branch.
language:none git log --oneline 58b250e WIP d558e36 Add coverage and pre-pr checklist to README.md c9724be Fix copyright holder in LICENCE aa3b728 Added Apache 2.0 LICENSE 8a2fd40 Initial commit
Drop the latest commit, reword Added...
to Add
and fold in the Fix...
commit with:
language:none git rebase -i 8a2fd40
and update commits in your editor with:
language:none reword aa3b728 Added Apache 2.0 LICENSE fixup c9724be Fix copyright holder in LICENCE pick d558e36 Add coverage and pre-pr checklist to README.md drop 58b250e WIP
Rebase will sequentially take the commits from feature
and reapply them to master
:
language:none A---B---C feature / D---E---F---G master
will become
language:none A'--B'--C' feature / D---E---F---G master
Use the following commands:
language:none git checkout master git pull upstream master # or just `git pull` if `master` is set to track `upstream/master` git checkout feature git rebase master git push -f
For an existing PR you can alternatively click Update
branch
on Github.
This will merge upstream/master
into origin/feature
.
Before adding any commits to feature
locally run:
language:bash git pull origin feature
[WIP]
if adjustments are neededgit
rebase
-i
COMMIT_HASH
and force push[WIP]
tag when all of the above are met
Notes:
Keep your exported identifiers to as few as possible
Document them
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // alias for uint8 rune // alias for int32 // represents a Unicode code point float32 float64 complex64 complex128
language:none 0 for numeric types false for the boolean type "" for strings nil for pointers
Fork
in the top right corner of github.com/anz-bank/go-courseUSERNAME
is your GitHub username:language:none git clone https://github.com/USERNAME/go-course.git cd go-course
upstream
remote pointing to anz-bank/go-course:language:bash git remote add upstream https://github.com/anz-bank/go-course.git git remote update git branch -u upstream/master master git pull
Start work on a new branch lab1
with:
git checkout master git pull upstream master # with upstream/master tracking just use `git pull` git checkout -b lab1
Write, test and commit code code, then:
git push -u origin
feature
branch of your own fork (remote origin
)master
on anz-bank/go-course (remote upstream
)Follow the PR checklist before submitting
When ready, submit and add the Ready
for
review
tag
If master
has advanced after feature
branch and PR creation you have two options:
Rebase feature branch
language:none git checkout master git pull upstream master # or just `git pull` if `master` is set to track `upstream/master` git checkout feature git rebase master git push -f
or
Update branch on GitHub
For an existing PR you can alternatively click Update
branch
on Github.
This will merge upstream/master
into origin/feature
.
Before adding any commits to feature
locally run:
language:bash git pull origin feature
lab1
)Ready
for
review
Approved
by
colleague
or Changes
requested
Merge
me
Ready
for
review
and add Under
review
Approved
by
colleague
or Changes
requested
and remove Under
review
Dave Cheney - table driven tests
5201_fib/USERNAME
func fib(n int)
fib(7)
in main
to print1 1 2 3 5 8 13
02_bubble/USERNAME
int
slice s
using Bubble sort:func bubble(s []int) []int
fmt.Println(bubble([]int{3,
2,
1,
5}))
in main
to print:[1 2 3 5]
03_letters/USERNAME
func letters(s string) map[rune]int
"{key}:{val}"
. Use package sort:func sortLetters(m map[rune]int) []string
fmt.Println(strings.Join(sortLetters(letters("aba")),
"\n"))
in main
to print:a:2 b:1
04_numeronym/USERNAME
func numeronyms(vals ...string) []string
fmt.Println(numeronyms("accessibility",
"Kubernetes",
"abc"))
in main
to print:[a11y K8s abc]
"The bigger the interface, the weaker the abstraction." - Rob Pike
59"Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to βborrowβ pieces of an implementation by embedding types within a struct or interface." - Effective Go
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ReadWriter is the interface that combines the Reader and Writer interfaces. type ReadWriter interface { Reader Writer }
"When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one." - Effective Go
Embedding types introduces the possiblity for naming conflicts. Go follows some simple rules to help resolve this:
X
will hide any other field or method X
found in a more deeply nested part of the type05_stringer/USERNAME
IPAddr
type implement fmt.Stringer
to print the address as a dotted quadfmt.Println(IPAddr{127,
0,
0,
1})
in main
to print:127.0.0.1
// scaler.go type scaler interface { scale(float64) } type rect struct { a, b int } func (r *rect) scale(s float64) { r.a = int(float64(r.a) * s) r.b = int(float64(r.b) * s) } type circle struct { r int } func (c *circle) scale(s float64) { c.r = int(float64(c.r) * s) }
type scalerSuite struct { suite.Suite scaler scaler } func (s *scalerSuite) TestScaler() { r := assert.New(s.T()) initial := fmt.Sprintf("%#v", s.scaler) s.scaler.scale(2.0) scaled := fmt.Sprintf("%#v", s.scaler) s.scaler.scale(0.5) scaledBack := fmt.Sprintf("%#v", s.scaler) fmt.Printf("%s\n%s\n%s\n", initial, scaled, scaledBack) r.Equal(initial, scaledBack) r.NotEqual(initial, scaled) } func TestStorer(t *testing.T) { suite.Run(t, &scalerSuite{scaler: &circle{2}}) suite.Run(t, &scalerSuite{scaler: &rect{1, 1}}) }
06_puppy/USERNAME
(see hints)Puppy
struct containing ID
, Breed
, Colour
, Value
.Storer
interface with CRUD methods for Puppy
MapStore
implementation of Storer
backed by a map
SyncStore
implementation of Storer
backed by a sync.Mapmain
Storer
interface and run in suite with both implementationserror
is a builtin interface (source: error)type error interface { Error() string }
errors.New()
:var ErrPermission = errors.New("permission denied")
fmt.Errorf
:return fmt.Errorf("archive/tar: missed writing %d bytes", nb)
type AddrError struct { Err string Addr string } func (e *AddrError) Error() string { if e == nil { return "<nil>" } s := e.Err if e.Addr != "" { s = "address " + e.Addr + ": " + s } return s }
log.Println(err)
error
as the second return value"Errors are values." - Rob Pike
74defer
delays the execution of a function until the surrounding function returnsdefer
is called the parameters to the call are evaluated and saveddefer <function call>
panic
and recover
are builtin functionspanic
and recover
behave similar to throw/catch
in other languagespanic
:func panic(interface{})
panic
unwinds the stack, calling deferred functions up to the top-level function of the executing goroutinerecover
(see next slide) can intercept this unwindingrecover
, the program is terminated and the error condition is reportedrecover
:func recover() interface{}
recover
allows a program to handle panic called in another functionpanic
rarely and judiciously in your own codepanic
across package boundaries07_errors/USERNAME
06_puppy/USERNAME
Error
with fields Message
and Code
Storer
interface for all methods to also return error
language:none - Value < 0 - ID not found in Read, Update and Delete
Storer
implementation using LevelDBDefinition
Go provides:
go <function-call>
chan
keywordch := make(chan int) // ch is an unbuffered channel of `int` element type
ch := make(chan int, 10) // ch is a buffered channel of size 10 and type `int`
close()
functionch <- v // send v to the channel ch v := <-ch // receive from ch and assign it to v
v, ok := <-ch // ok is false if channel is closed and no more data is available
for v := range ch { fmt.Println(v) }
var ch1, ch2 chan int select { case ch1 <- 1: fmt.Println("1 sent on ch1") case x, ok := <-ch2: if ok { fmt.Printf("%v received on ch2", x) } else { fmt.Println("ch2 is closed") } default: fmt.Println("no communication") }
For non-trivial size projects follow the layout suggested by golang-standards
Abridged version:
language:none βββ README.md βββ go.mod βββ go.sum βββ cloudbuild.yaml βββ .gitignore βββ .golangci.yml βββ pkg βΒ Β βββ bar βΒ Β βΒ Β βββ bar.go βΒ Β βΒ Β βββ baz.go βΒ Β βΒ Β βββ bar_test.go βΒ Β βββ foo βΒ Β βββ foo.go βΒ Β βββ foo_test.go βββ cmd βΒ Β βββ fooserver βΒ Β βββ main.go βββ docs βββ vendor
07_errors/USERNAME
08_project/USERNAME
containinglanguage:none βββ README.md βββ pkg βΒ Β βββ puppy βΒ Β βββ types.go βΒ Β βββ types_test.go βΒ Β βββ errors.go βΒ Β βββ errors_test.go βΒ Β βββ store βΒ Β βββ storer.go βΒ Β βββ .... store files and tests, e.g. mapstore.go βββ cmd Β Β βββ puppy-server Β Β βββ main.go
README.md
language:bash go help go version go build ./... go test ./... go install ./... go mod init go mod vendor go mod tidy go clean -modcache go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
Lint with
golangci-lint run
and configure the linter in .golangci.yml
Use gofmt
or goimport
as part of your editor/IDE setup
golang.org/pkg/
- fmt
- strings
- time
- encoding/json
- net/http
- regexp
- sort
- database/sql
- os
- path
- bytes
- ...
Marshal
converts a value into a JSON byte arrayUnmarshal
converts a JSON-encoded byte array into a valueEncoder
marshals values as JSON to a stream (io.Writer
)Decoder
unmarshals JSON-encoded values from a stream (io.Reader
)func Marshal(v interface{}) ([]byte, error) { /* ... */ } func Unmarshal(data []byte, v interface{}) error { /* ... */ }
type Car struct { Manufacturer string `json:"manufacturer"` Model string `json:"model"` Year int64 `json:"year,omitempty"` }
nil
09_json/USERNAME
containing a copy of upstream master 08_project/USERNAME
-d
FILE
with long form --data
FILE
using kingpin.v2. FILE should contain an array of puppies in JSON format. Parse this file and store its contents.
testify - testing toolkit
chi - lightweight router for Go HTTP services
kingpin.v2 - command line and flag parser
logrus - structured logger
pq - Postgres driver for the database/sql
redigo - lightweight client for Redis
mgo - MongoDB driver for Go
goracle - Oracle driver for the database/sql
jsonschema - JSON schema compilation and validation
go-jose - JWT utils
...
Full sample code on Go Play Space -
not executable to due 3rd party dependency chi
:
basic sample
sample with error handling
type TeapotHandler interface { handlePost(w http.ResponseWriter, r *http.Request) handleGet(w http.ResponseWriter, r *http.Request) } func SetupRoutes(r chi.Router, h TeapotHandler) { r.Post("/api/teapot", h.handlePost) r.Get("/api/teapot/{id}", h.handleGet) } func main() { r := chi.NewRouter() h := NewMapTeapotHandler() SetupRoutes(r, h) http.ListenAndServe(":7735", r) }
type Teapot struct { ID int Colour string Temperature int } // MapTeapotHandler implements TeapotHandler type MapTeapotHandler struct { store map[int]Teapot // Race condition maxID int } func NewMapTeapotHandler() *MapTeapotHandler { return &MapTeapotHandler{store: map[int]Teapot{}} }
// MapTeapotHandler implements TeapotHandler func (mt *MapTeapotHandler) handleGet(w http.ResponseWriter, r *http.Request) { id, _ := strconv.Atoi(chi.URLParam(r, "id")) teapot, _ := mt.store[id] render.JSON(w, r, teapot) } func (mt *MapTeapotHandler) handlePost(w http.ResponseWriter, r *http.Request) { var teapot Teapot render.DecodeJSON(r.Body, &teapot) mt.maxID++ // Race condition teapot.ID = mt.maxID mt.store[mt.maxID] = teapot render.JSON(w, r, teapot) }
10_rest/USERNAME
containing a copy of upstream master 09_json/USERNAME
pkg/puppy/rest.go
implementing:GET /api/puppy/{id} POST /api/puppy/ Payload: Puppy JSON without ID PUT /api/puppy/{id} Payload: Puppy JSON without ID DELETE /api/puppy/{id}
-p
PORT
with long flag --port
PORT
to command line flags-s
STORE
with long flag --store
STORE
with accepted values:map, sync, db
11_notify/USERNAME
containing a copy of upstream master 10_rest/USERNAME
cmd/lostpuppy-service/main.go
running single endpoint:POST /api/lostpuppy/ Payload: { id: PUPPY_ID }
HTTP status 201 for even IDs HTTP status 500 for odd IDs
Delete
method to notify lostpuppy-service in a goroutine and log response code asynchronously.
Rob Pike's Talk (Towards Go 2)
Dave Cheney Blog (Past Present and Future of Go)
Please take a moment to give anonymous feedback at bit.ly/gc-f6k.
Thank you! π
113Cameron Hutchison
Daniel Cantos
Joshua Carpeggiani
Julia Ogris
Keilin Olsen
Ryan O'Kane
Sai Kiran Gummaraj