What I do
- focus on Go code and architecture
When to use me
Use this when you are reading or writing Go code
Quick start
Use gopls to navigate the code.
Package and type organization
Group related types together using a single type (...) block. Use iota for
enumerated constants. Keep unexported types lowercase; only export what callers
need.
type (
matchBehavior int
matchMode int
)
const (
matchBehaviorAll matchBehavior = iota
matchBehaviorAny
matchModeExact matchMode = iota
matchModeRegexp
)
type (
spec struct {
name string
matchBehavior matchBehavior
matchMode matchMode
reg *registry
}
predicate struct {
kv map[string][]valueMatcher
matchBehavior matchBehavior
cache sync.Map
}
)
Precompute in Create/New, not in the hot path
CreateFilter and Create run at route-build time. Precompute everything
costly there (compile regexps, build maps, normalize config). The hot path
(Request, Response, Match) must be allocation-free on the common case.
func (s *spec) Create(args []any) (routing.Predicate, error) {
if len(args) == 0 || len(args)%2 != 0 {
return nil, predicates.ErrInvalidPredicateParameters
}
kv := make(map[string][]valueMatcher)
for i := 0; i < len(args); i += 2 {
key, keyOk := args[i].(string)
value, valueOk := args[i+1].(string)
if !keyOk || !valueOk {
return nil, predicates.ErrInvalidPredicateParameters
}
re, err := regexp.Compile(value)
if err != nil {
return nil, predicates.ErrInvalidPredicateParameters
}
kv[key] = append(kv[key], regexMatcher{regexp: re})
}
return &predicate{kv: kv}, nil
}
Cache expensive Create results to survive frequent route table rebuilds
Route tables rebuild every few seconds. Cache CreateFilter/Create results
keyed by config args to avoid recreating identical instances and leaking memory.
Use sync.Mutex around the map; check-then-insert is fine for idempotent
objects.
type spec struct {
mu sync.Mutex
filterMap map[string]*myFilter
}
func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) {
key, err := keyFromArgs(args)
if err != nil {
return nil, err
}
s.mu.Lock()
if f, ok := s.filterMap[key]; ok {
s.mu.Unlock()
return f, nil
}
s.mu.Unlock()
// ... build f ...
s.mu.Lock()
s.filterMap[key] = f
s.mu.Unlock()
return f, nil
}
Cache hot-path lookups with sync.Map
For repeated lookups in Match/Request/Response (e.g. parsed tokens,
validated values), use sync.Map — it avoids a single global mutex bottleneck
under heavy read concurrency. Periodically clear it rather than tracking
per-entry TTLs; infrequent full clears are cheap.
type predicate struct {
kv map[string][]valueMatcher
cache sync.Map // wire-string → bool
}
// in a background goroutine started at spec construction time:
func (r *registry) clean() {
tick := time.NewTicker(time.Hour)
defer tick.Stop()
for {
select {
case <-r.quit:
return
case <-tick.C:
r.mu.Lock()
for _, p := range r.predicateMap {
p.cache.Clear()
}
r.mu.Unlock()
}
}
}
Avoid allocations in string parsing
Prefer strings.Index / strings.Cut over strings.Split to avoid
allocating a slice. Use iterator-style helpers (or strings.SplitSeq in
go1.24+) to range over parts without materializing them.
// zero-allocation split iteration (backport of strings.SplitSeq)
func splitSeq(s, sep string) func(yield func(string) bool) {
return func(yield func(string) bool) {
for {
i := strings.Index(s, sep)
if i < 0 {
break
}
if !yield(s[:i]) {
return
}
s = s[i+len(sep):]
}
yield(s)
}
}
// usage
for part := range splitSeq(header, ",") {
token, value, found := strings.Cut(strings.TrimSpace(part), "=")
_ = token; _ = value; _ = found
}
Skip work when input hasn't changed
For dataclients and watchers, record the last successful input (e.g. file bytes, ETag) and return early without parsing when nothing changed.
func (c *WatchClient) loadUpdates() watchResponse {
content, err := os.ReadFile(c.fileName)
if err != nil { ... }
if bytes.Equal(content, c.lastContent) {
return watchResponse{} // no change
}
r, err := eskip.Parse(string(content))
if err != nil {
c.lastContent = nil
return watchResponse{err: err}
}
c.lastContent = content
...
}
Error handling
Wrap errors with context using fmt.Errorf("...: %w", err). Return sentinel
errors (predicates.ErrInvalidPredicateParameters) from Create/CreateFilter
for invalid config so callers can distinguish config errors from runtime errors.
Never swallow errors in the hot path.
req, endpointMetrics, err := p.mapRequest(ctx, requestContext)
if err != nil {
return nil, &proxyError{err: fmt.Errorf("could not map backend request: %w", err)}
}