Golang Library Development
Guiding Principles
- Developing a library, writing code is actually the easy part
- There are always unplanned tasks
- There are always new features
- There are always bugs to fix
- The basic ways people measure the quality of open-source libraries
- Basic code quality: go vet, go fmt, go lint
- Test coverage
- Documentation
- Number of issues opened / closed
- A high number of GitHub stars does not necessarily mean high quality
- 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