| title | sub_title | author | theme | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Introduction to Go |
A Masterclass |
MachShip |
|
Go is a statically typed, compiled language designed at Google
for building simple, reliable, and efficient software.
Key traits:
- Fast compilation
- First-class concurrency
- Garbage collected
- Minimalist design philosophy
┌───────────────┐
│ I know how │
│ to configure ├───────┐
│ http handler │ │
└───────────────┘ │
┌───────────────┐ │
│ I know how │ │
│ to configure ├───────┤
│ the logger │ │ ┌───────────────┐
└───────────────┘ │ │ │
┌───────────────┐ ├─────►│ Configure App │
│ I also want │ │ │ │
│ to configure ├───────┤ └───────────────┘
│ http handlers │ │
└───────────────┘ │
┌───────────────┐ │
│ I know how │ │
│ to configure ├───────┘
│ the logger, │
│ also │
└───────────────┘
Go distributes code in modules.
.
├── AGENTS.md
├── README.md
├── go.mod
├── go.sum
├── health.go
├── logger.go
├── telemetry.go
.
.
.
Contains:
go.mod*.gofiles- optionally, many packages
Code is organised into packages.
package main
import (
"context"
"log/slog"
"github.com/ms/o11y"
)
const ...
var ...
type ...
func main() { ... }- Limited to one per directory
- Only exported members are capitalised
Go is 'batteries-included' has a full list of primitives,
some platform dependent.
boolint/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64byte/uintptrfloat32/float64complex64/complex128string/runeany/nilfuncmap[Type]Type1[123]Type/[]Type
The Go standard library (stdlib) is also 'batteries-included'.
func Name() {
fmt.Println("side effects only :O")
}
func Name(a Type, b AnotherType) ReturnType { /* ... */ }
func Name(a, b Type, c AnotherType) { /* ... */ }
func Name() (a ReturnType, b AnotherReturnType) {
/* ... */
}Go uses structs to compose types from other types.
The o11y package contains:
package o11y
type Health struct {
ready bool
checkers []func(context.Context)
}...which can be imported into another package:
import "github.com/machship/o11y"
var h o11y.Health
health := o11y.Health{}
var healthPtr *o11y.Health = &healthEvery type has a zero value
| Type | Zero |
|---|---|
bool |
false |
int |
0 |
float64 |
0.0 |
string |
"" |
[]Type |
nil |
func |
nil |
Health |
{ ready: false, checkers: nil } |
*Health |
nil |
package o11y
type Health struct {
ready bool
checkers []func(context.Context)
}Almost always start with New and return a pointer.
Should always be infallible.
func NewHealth(...) *Health {
return &Health{}
}func NewHealth(
ready bool,
checkers ...func(context.Context),
) *Health {
return &Health{
ready: ready,
checkers: checkers,
}
}type Config struct {
Ready bool
}
func NewHealth(config Config) *Health ...func NewHealth(options ...func(*Health)) *Health {
h := &Health{}
for _, opt := range options {
opt(h)
}
return h
}
func WithChecker(fn func(context.Context)) func(*Health) {
return func(h *Health) {
h.checkers = append(h.checkers, fn)
}
}h := o11y.NewHealth(
o11y.WithChecker(isDatabaseUp),
o11y.WithChecker(isRouterReady),
)func WithOptions(options ...func(*Health)) func(*Health) {
return func(h *Health) {
for _, opt := range options {
opt(h)
}
}
}
func NewHealth(options ...func(*Health)) *Health {
h := &Health{}
WithOptions(options...)(h)
return h
}commonOptions := o11y.WithOptions(
o11y.WithChecker(isDatabaseUp),
o11y.WithChecker(isRouterReady),
)Type alias give a alternative name to a type.
type ReadyState = boolType alias give a alternative name to a type.
type ReadyState = boolA newtype defines a new type with an underlying type.
type Checker func(context.Context)
type Option func(*Health)The underlying type and newtype can be converted from one to the other but they are completely different types.
func WithChecker(fn Checker) Option {
return func(h *Health) {
h.AddChecker(fn)
}
}
func WithOptions(options ...Option) Option {
return func(h *Health) {
for _, opt := range options {
opt(h)
}
}
}
func NewHealth(options ...Option) *Health {
h := &Health{}
WithOptions(options)(h)
return h
}Structs also encapsulate data and behaviour.
func (h *Health) AddChecker(fn Checker) {
h.checkers = append(h.checkers, fn)
}
func (h Health) Ready() bool {
return h.ready
}Hint:
Use pointer receivers (*Health) for mutation,
value receivers for read-only.
Go interfaces are satisfied implicitly. No implements keyword.
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}func (h *Health) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
switch r.Method {
case http.MethodGet:
handleGet(h, w, r)
case http.MethodOptions:
handleOptions(h, w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}Any type with ServeHTTP(ResponseWriter, *Request) is an http.Handler.
type Option interface {
Apply(*Health)
}
type Options []Option
func (opts Options) Apply(h *Health) {
for _, opt := range opts {
opt.Apply(h)
}
}
type OptionFunc func(*Health)
func (fn OptionFunc) Apply(h *Health) {
fn(h)
}
func NewHealth(options ...Option) *Health {
h := &Health{}
Options(options).Apply(h)
return h
}type Config struct {
Ready bool
Checkers []Checker
}
func (c Config) Apply(h *Health) {
h.ready = c.Ready
h.AddChecker(c.Checkers...)
}Sometimes you need to pass or construct an intermediary value, validate it and then construct your final value and discard the rest.
func WithComplexThing(thing Complex) (Option, error) {
if err := validate(thing); err != nil {
return nil, err
}
return func(h *Health) { /* ... */ }, nil
}type app struct {
config Config
health *o11y.Health
}
type appConfig struct {
*app
healthOptions []o11y.Option
}
type Option interface {
Apply(*appConfig)
}
func New(opts ...Option) *app {
builder := &appConfig{app: &app{}}
Options(opts).Apply(builder)
app.health = o11y.NewHealth(builder.healthOptions...)
return builder.app
}Official:
- go.dev/tour - Interactive tutorial
- go.dev/doc/effective_go - Best practices
- pkg.go.dev - Package documentation
This codebase:
github.com/machship/o11y- Observability patterns- Real-world examples of concurrency, generics, interfaces
Community:
- Awesome Go - Curated packages
- Go by Example - Code snippets
Questions?
Code examples from: github.com/machship/o11y
- Telemetry management with OpenTelemetry
- Generic health checks
- Concurrent resource cleanup
- Middleware patterns
Start coding: The best way to learn Go is to write Go.