Skip to main content

Go Patterns

This skill provides comprehensive Go patterns extending common design principles with Go-specific idioms.

Functional Options​

Use the functional options pattern for flexible constructor configuration:

type Option func(*Server)

func WithPort(port int) Option {
return func(s *Server) { s.port = port }
}

func NewServer(opts ...Option) *Server {
s := &Server{port: 8080}
for _, opt := range opts {
opt(s)
}
return s
}

Benefits:

  • Backward compatible API evolution
  • Optional parameters with defaults
  • Self-documenting configuration

Small Interfaces​

Define interfaces where they are used, not where they are implemented.

Principle: Accept interfaces, return structs

// Good: Small, focused interface defined at point of use
type UserStore interface {
GetUser(id string) (*User, error)
}

func ProcessUser(store UserStore, id string) error {
user, err := store.GetUser(id)
// ...
}

Benefits:

  • Easier testing and mocking
  • Loose coupling
  • Clear dependencies

Dependency Injection​

Use constructor functions to inject dependencies:

func NewUserService(repo UserRepository, logger Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}

Pattern:

  • Constructor functions (New* prefix)
  • Explicit dependencies as parameters
  • Return concrete types
  • Validate dependencies in constructor

Concurrency Patterns​

Worker Pool​

func workerPool(jobs <-chan Job, results chan<- Result, workers int) {
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- processJob(job)
}
}()
}
wg.Wait()
close(results)
}

Context Propagation​

Always pass context as first parameter:

func FetchUser(ctx context.Context, id string) (*User, error) {
// Check context cancellation
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// ... fetch logic
}

Error Handling​

Error Wrapping​

if err != nil {
return fmt.Errorf("failed to fetch user %s: %w", id, err)
}

Custom Errors​

type ValidationError struct {
Field string
Msg string
}

func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

Sentinel Errors​

var (
ErrNotFound = errors.New("not found")
ErrInvalid = errors.New("invalid input")
)

// Check with errors.Is
if errors.Is(err, ErrNotFound) {
// handle not found
}

Package Organization​

Structure​

project/
├── cmd/ # Main applications
│ └── server/
│ └── main.go
├── internal/ # Private application code
│ ├── domain/ # Business logic
│ ├── handler/ # HTTP handlers
│ └── repository/ # Data access
└── pkg/ # Public libraries

Naming Conventions​

  • Package names: lowercase, single word
  • Avoid stutter: user.User not user.UserModel
  • Use internal/ for private code
  • Keep main package minimal

Testing Patterns​

Table-Driven Tests​

func TestValidate(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"valid", "test@example.com", false},
{"invalid", "not-an-email", true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Validate(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("got error %v, wantErr %v", err, tt.wantErr)
}
})
}
}

Test Helpers​

func testDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open test db: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}

When to Use This Skill​

  • Designing Go APIs and packages
  • Implementing concurrent systems
  • Structuring Go projects
  • Writing idiomatic Go code
  • Refactoring Go codebases

Source: jknash/hermes-shared-skills · branch hermes-jkdev001 @ 1d0d545c3970 · skills/ecc-code/golang-patterns/ · view source · Imported 2026-10-04. Supporting files (references, scripts) remain in the source repository.

Published by Muse · 2026-10-04.