docs/contributing/go/packages.md
All shared Go code in SigNoz lives under pkg/. Each package represents a distinct domain concept and exposes a clear public interface. This guide covers the conventions for creating, naming, and organising packages so the codebase stays consistent as it grows.
Use short, lowercase, single-word names. No underscores or camelCase (querier, cache, authz, not query_builder or dataStore).
Names must be domain-specific. A package name should tell you what problem domain it deals with, not what data structure it wraps. Prefer alertmanager over manager, licensing over checker.
Avoid generic names like util, helpers, common, misc, or base. If you can't name it, the code probably belongs in an existing package.
Create a new package when:
authz, licensing, cache).Do not create a new package when:
A typical package looks like:
pkg/cache/
├── cache.go # Public interface + exported types
├── config.go # Configuration types if needed
├── memorycache/ # Implementation sub-package
├── rediscache/ # Another implementation
└── cachetest/ # Test helpers for consumers
Follow these rules:
Interface-first file: The file matching the package name (e.g., cache.go in pkg/cache/) should define the public interface and core exported types. Keep implementation details out of this file.
One responsibility per file: Name files after what they contain (config.go, handler.go, service.go), not after the package name. If a package merges two concerns, prefix files to group them (e.g., memory_store.go, redis_store.go in a storage package).
Sub-packages for implementations: When a package defines an interface with multiple implementations, put each implementation in its own sub-package (memorycache/, rediscache/). This keeps the parent package import-free of implementation dependencies.
Test helpers in {pkg}test/: If consumers need test mocks or builders, put them in a {pkg}test/ sub-package (e.g., cachetest/, sqlstoretest/). This avoids polluting the main package with test-only code.
Test files stay alongside source: Unit tests go in _test.go files next to the code they test, in the same package.
-er suffix convention (Reader, Writer, Closer). For multi-method interfaces, use clear nouns (Cache, Store, Provider).New<Type>(...) (e.g., NewMemoryCache()).cache.Cache, not cache.CacheInterface. Write authz.FromRole, not authz.AuthzFromRole.c, f, br).parseToken, buildQuery).PascalCase for exported constants.Group imports in three blocks separated by blank lines:
import (
// 1. Standard library
"fmt"
"net/http"
// 2. External dependencies
"github.com/gorilla/mux"
// 3. Internal
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
)
Never introduce circular imports. If package A needs package B and B needs A, extract the shared types into a third package (often under pkg/types/).
Most types belong in pkg/types/ under a domain-specific sub-package (e.g., pkg/types/ruletypes, pkg/types/authtypes).
Do not put domain logic in pkg/types/. Only data structures, constants, and simple methods.
When two packages are tightly coupled (one imports the other's constants, they cover the same domain), merge them:
memory_store.go, redis_store.go).go build ./..., go test ./<new-pkg>/..., and go vet ./....Add a doc.go with a package-level comment for any package that is non-trivial or has multiple consumers. Keep it to 1–3 sentences:
// Package cache provides a caching interface with pluggable backends
// for in-memory and Redis-based storage.
package cache
util or common.cache.go) defines the public interface. Implementation details go elsewhere.pkg/types/ when needed.{pkg}test/ sub-package, not in the main package.go build ./..., go test ./<your-pkg>/..., and go vet ./....