Jul 7, 2018

[Go][note] Go for Industrial Programming take away

Reference:
https://peter.bourgon.org/go-for-industrial-programming/


Structuring your code and repository

  • cmd/ subdirectory to hold multiple package main binaries.
  • pkg/ to hold golang packages while other are non-golang languages
  • Seperate package as user perspective, not engineering perspective.

Program configuration

  • Flags are the best way to configure your program.
  • Only main func is allow to determine flag's usage.

The component graph

  • easy to read vs. easy to write (Prefer former)
  • Theory of modern Go:
    • No global state, ever
    • Explicit dependencies
    • No package level variables
    • No func init

e.g no good:
func BuildContainer() *dig.Container {  
    container := dig.New()
    container.Provide(NewConfig)
    container.Provide(ConnectDatabase)
    container.Provide(NewPersonRepository)
    container.Provide(NewPersonService)
    container.Provide(NewServer)
    return container
}

func main() {  
    container := BuildContainer()
    if err := container.Invoke(func(server *Server) {
        server.Run()
    }); err != nil {
        panic(err)
    }
}
good:
func main() {  
    cfg := GetConfig()
    db, err := ConnectDatabase(cfg.URN)
    if err != nil {
        panic(err)
    }
    repo := NewPersonRepository(db)
    service := NewPersonService(cfg.AccessToken, repo)
    server := NewServer(cfg.ListenAddr, service)
    server.Run()
}


Goroutine lifecycle management

  • Never start a goroutine without knowing how it will stop.
  • Goroutine:
    • Tend to be structural
    • Managing long-running things with indistinct termination semantics
    • Gorotine often started at the beginning of a program.(Same in C, C++/POSIX)

reference package errgroup : https://godoc.org/golang.org/x/sync/errgroup


Golang's 'future' (aka. future in C++11)
e.g

future := make(chan int, 1)
go func() { future <- process() }()
result := <-future


Scatter-gather

// Scatter
c := make(chan result, 10)
for i := 0; i < cap(c); i++ {
    go func() {
        val, err := process()
        c <- result{val, err}
    }()
}

// Gather
var total int
for i := 0; i < cap(c); i++ {
    res := <-c
    if res.err != nil {
        total += res.val
    }
}


Good observability is simply more important than good testing, because good observability enables smart organizations to focus on fast deployment and rollback, optimizing for mean time to recovery (MTTR) instead of mean time between failure (MTBF).


Metrics, logging, and tracing are emergent patterns of consumption of observability data.


Metrics

Metrics are counters, gauges, and histograms, whose observations are statistically combined and reported to a system that allows fast, real-time exporation of aggregate system behavior. Metrics power dashboards and alerts.


Logging

Logs are streams of discrete events reported at full fidelity to a collection system, for later analysis, reporting, and debugging. Good logs are structured and permit flexible post-hoc manipulation.
Logs are abstract streams, not concrete files, so avoid loggers that write or rotate files on disk; that’s the responsibility of another process, or your orchestration platform. And logging can quickly dominate runtime costs of a system, so be careful and judicious in how you produce and emit logs. Capture everything relevant in the request path, but do so thoughtfully, using patterns like decorators or middlewares.


Tracing

Tracing deals with all request-scoped performance data, especially as that data crosses process and system boundaries in a distributed system. Tracing systems organize metadata into tree structures, enabling deep-dive triage sessions into specific anomalies or events.


Testing

if you have good production observability, as much as 80–90% of your testing effort should be focused toward unit tests.
In Go, we know that good unit tests are table-driven, and leverage the fact that your components accept interfaces and return structs to provide fake or mock implementations of dependencies, and test only the thing being tested.

Gunning for 100% test coverage is almost certainly counterproductive, but 50% seems like a good low watermark; and avoid introducing testing DSLs or “helper packages” unless and until your team gets explicit, concrete value from them.

Context

Only use context.Value for data that can't be passed through your program in any other way.
In practice, this means only using context.Value for request-scoped information, like request IDs, user authentication tokens, and so on—information that only gets created with or during a request.


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.