Files

Go Patterns Used in This Project

This directory contains documentation on the Go design patterns and idioms used throughout the CV website project.

Pattern Catalog

  1. Middleware Pattern - HTTP middleware chain for cross-cutting concerns
  2. Handler Pattern - Organized HTTP handler structure
  3. Context Pattern - Request-scoped values using context
  4. Error Wrapping - Structured error handling with wrapping
  5. Dependency Injection - Constructor-based dependency injection
  6. Template Pattern - Cached template management
  7. Singleton Pattern - Single instance managers (template, config)
  8. Factory Pattern - Error and response constructors

Pattern Categories

Structural Patterns

  • Middleware Pattern - Composable request processing
  • Singleton Pattern - Single instance coordination
  • Dependency Injection - Decoupled component initialization

Behavioral Patterns

  • Handler Pattern - Request routing and handling
  • Context Pattern - Request-scoped data propagation
  • Template Pattern - Flexible rendering engine

Error Handling Patterns

  • Error Wrapping - Context-rich error chains
  • Typed Errors - Domain-specific error types
  • Factory Pattern - Consistent error creation

Pattern Usage Map

┌────────────────────────────────────────────────────────────┐
│                    Pattern Usage Map                        │
└────────────────────────────────────────────────────────────┘

main.go
├─→ Singleton Pattern        (config, template manager)
├─→ Dependency Injection     (handler construction)
└─→ Middleware Pattern       (chain setup)

internal/handlers/
├─→ Handler Pattern          (method organization)
├─→ Error Wrapping           (error handling)
├─→ Factory Pattern          (error/response creation)
└─→ Context Pattern          (preference access)

internal/middleware/
├─→ Middleware Pattern       (http.Handler wrapping)
├─→ Context Pattern          (value storage)
└─→ Error Wrapping           (panic recovery)

internal/templates/
├─→ Singleton Pattern        (manager instance)
├─→ Template Pattern         (rendering strategy)
└─→ Dependency Injection     (config injection)

internal/models/
├─→ Factory Pattern          (model loading)
└─→ Error Wrapping           (validation errors)

When to Use Each Pattern

Middleware Pattern

✓ Cross-cutting concerns (logging, auth, CORS) ✓ Request/response modification ✓ Chain-of-responsibility needs ✗ Business logic (use handlers instead)

Handler Pattern

✓ HTTP request handling ✓ Route-specific logic ✓ Organizing endpoints by resource ✗ Generic utilities (use packages instead)

Context Pattern

✓ Request-scoped values (user, preferences) ✓ Cancellation signals ✓ Deadlines and timeouts ✗ Function parameters (use explicit params)

Error Wrapping

✓ Adding context to errors ✓ Preserving error chains ✓ Debug information ✗ Simple errors (use errors.New)

Dependency Injection

✓ Decoupling components ✓ Testing with mocks ✓ Configuration flexibility ✗ Simple functions (use direct calls)

Template Pattern

✓ Flexible rendering ✓ HTML generation ✓ Hot reload in development ✗ JSON APIs (use direct encoding)

Singleton Pattern

✓ Shared resources (DB, cache) ✓ Configuration managers ✓ Template engines ✗ Stateless utilities (use packages)

Factory Pattern

✓ Complex object creation ✓ Consistent initialization ✓ Error construction ✗ Simple structs (use literals)

Anti-Patterns to Avoid

Global State

// BAD: Mutable global variable
var globalConfig Config

// GOOD: Pass as dependency
func NewHandler(config *Config) *Handler

Panic for Flow Control

// BAD: Using panic for expected errors
if err != nil {
    panic(err)
}

// GOOD: Return errors
if err != nil {
    return fmt.Errorf("operation failed: %w", err)
}

Ignoring Errors

// BAD: Ignoring error
_ = json.Unmarshal(data, &result)

// GOOD: Handle error
if err := json.Unmarshal(data, &result); err != nil {
    return fmt.Errorf("unmarshal: %w", err)
}

Context in Structs

// BAD: Storing context in struct
type Handler struct {
    ctx context.Context
}

// GOOD: Pass context as first parameter
func (h *Handler) Handle(ctx context.Context, w, r)

Naked Returns

// BAD: Naked return with named results
func process() (result string, err error) {
    result = "foo"
    return  // Confusing!
}

// GOOD: Explicit return
func process() (string, error) {
    result := "foo"
    return result, nil
}

Learning Path

For developers new to these patterns:

  1. Start with: Handler Pattern, Error Wrapping
  2. Then learn: Middleware Pattern, Context Pattern
  3. Advanced: Dependency Injection, Template Pattern
  4. Master: Singleton Pattern, Factory Pattern

Resources

Pattern Evolution

This project evolved through these pattern adoptions:

Phase 1: Basic Structure

  • Simple handlers
  • No middleware
  • Manual cookie reading

Phase 2: Middleware Introduction

  • PreferencesMiddleware added
  • Cookie handling centralized
  • Context pattern adopted

Phase 3: Type Safety

  • Request/response types
  • Validation tags
  • Typed errors

Phase 4: Error Handling

  • Error wrapping throughout
  • Domain error types
  • Centralized error handler

Phase 5: Testing

  • Dependency injection for testability
  • Mock-friendly interfaces
  • Benchmark tests