Skip to main content

Golang Library Development

Guiding Principles

  1. Developing a library, writing code is actually the easy part
    1. There are always unplanned tasks
    2. There are always new features
    3. There are always bugs to fix
  2. The basic ways people measure the quality of open-source libraries
    1. Basic code quality: go vet, go fmt, go lint
    2. Test coverage
    3. Documentation
    4. Number of issues opened / closed
    5. A high number of GitHub stars does not necessarily mean high quality
  3. The four essential elements of an open-source library: usability / readability / flexibility / testability

The Four Essential Elements of a Library

Usability: Think from the User's Perspective

For example, to write an HTTP GET request, create and then send it

import "net/http"

req, err := http.NewRequest(
http.MethodGet, "https://www.google.com", nil /* no body */)
if err != nil {
return err
}
client := &http.Client{}
res, err := client.Do(req)

However, this kind of code is often reused, and writing it repeatedly can be cumbersome. So why not provide a direct GET interface?

import "net/http"

client := &http.Client{}
res, err := client.Get("https://www.google.com")

Readability

Flexibility

Still using the GET example, if you want to add request logging

import "net/http"

client := &http.Client{}
res, err := client.Get("https://www.google.com")

There is a replaceable RoundTripper interface

type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
}
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}

This way, you only need to specify a loggingRoundTripper, without needing to wrap it

import "net/http"

client := &http.Client{Transport: loggingRoundTripper}
res, err := client.Get("https://www.google.com")

Testability

  • Not only must you ensure your own testability
  • You should also provide test helpers for callers

Backward Compatibility

  • Any exported entity
    • Renaming, removal
    • Modification of function parameter types
    • Adding a method to an interface

These can all lead to breaking changes, which is when semantic versioning becomes necessary.

Version Control

Semantic Versioning 2.0.0 | Semantic Versioning

MAJOR.MINOR.PATCH
  • patch: a bug fix
  • minor: a new non-breaking change
  • major: a breaking change

Stable / Unstable?

  • v < 1.0 unstable
    • Starting from 0.1.0, increment MINOR with each release, increment PATCH for bug fixes
  • v >= 1.0 stable official release 1.0

Version bumps are difficult; in a microservices architecture, it may take 6 months to a year to update all dependencies in core libraries.

^1.1 is short for >= 1.1, < 2;
~0.2 is short for >= 0.2, < 0.3

Pin to version range vs Lock exact version