Files
cv-site/doc/_go-learning/diagrams/04-handler-organization.md
T
juanatsap d95c62bad4 refactor: remove outdated server design documentation
Remove 557-line server-design.md from _go-learning/architecture - content is now covered in updated architecture documentation with real implementation examples and test coverage.
2025-12-02 20:25:05 +00:00

22 KiB

Handler Organization Diagram

Handler File Structure

internal/handlers/
├── cv.go              Constructor, shared state
├── cv_pages.go        Full page renders (Home, CVContent)
├── cv_htmx.go         HTMX partial updates (4 toggles)
├── cv_pdf.go          PDF export endpoint
├── cv_helpers.go      Shared utilities (prepareTemplateData, etc.)
├── types.go           Request/response types, validation
├── errors.go          Error handling, domain errors
├── cv_pages_test.go   Tests for page handlers
├── cv_htmx_test.go    Tests for HTMX handlers
└── benchmarks_test.go Benchmark tests

File Responsibilities

┌──────────────────────────────────────────────────────────────┐
│                        cv.go                                  │
│                  (Constructor & State)                        │
├──────────────────────────────────────────────────────────────┤
│  type CVHandler struct {                                     │
│      tmpl *templates.Manager  // Template renderer           │
│      host string              // For absolute URLs           │
│  }                                                            │
│                                                               │
│  func NewCVHandler(tmpl, host) *CVHandler                    │
│      └─→ Constructor for handler initialization              │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                      cv_pages.go                              │
│                  (Full Page Renders)                          │
├──────────────────────────────────────────────────────────────┤
│  func (h *CVHandler) Home(w, r)                              │
│      └─→ GET /                                               │
│          ├─ Get preferences from context                     │
│          ├─ Validate language parameter                      │
│          ├─ Prepare full template data                       │
│          └─ Render: index.html (full page)                   │
│                                                               │
│  func (h *CVHandler) CVContent(w, r)                         │
│      └─→ GET /cv                                             │
│          ├─ Get preferences from context                     │
│          ├─ Prepare template data                            │
│          └─ Render: partials/cv_content.html                 │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                      cv_htmx.go                               │
│                  (HTMX Partial Updates)                       │
├──────────────────────────────────────────────────────────────┤
│  func (h *CVHandler) ToggleCVLength(w, r)                    │
│      └─→ GET /toggle/length?current=short                    │
│          ├─ Get current preferences                          │
│          ├─ Toggle: short ↔ long                            │
│          ├─ Save cookie: cv-length                           │
│          └─ Render: partials/cv_content.html                 │
│                                                               │
│  func (h *CVHandler) ToggleCVIcons(w, r)                     │
│      └─→ GET /toggle/icons?current=show                      │
│          ├─ Toggle: show ↔ hide                             │
│          ├─ Save cookie: cv-icons                            │
│          └─ Render: partials/cv_content.html                 │
│                                                               │
│  func (h *CVHandler) ToggleCVTheme(w, r)                     │
│      └─→ GET /toggle/theme?current=default                   │
│          ├─ Toggle: default ↔ minimal                       │
│          ├─ Save cookie: cv-theme                            │
│          └─ Render: partials/cv_content.html                 │
│                                                               │
│  func (h *CVHandler) ToggleLanguage(w, r)                    │
│      └─→ GET /toggle/language?current=en                     │
│          ├─ Toggle: en ↔ es                                 │
│          ├─ Save cookie: cv-language                         │
│          └─ Render: index.html (full page for i18n)          │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                      cv_pdf.go                                │
│                   (PDF Export)                                │
├──────────────────────────────────────────────────────────────┤
│  func (h *CVHandler) ExportPDF(w, r)                         │
│      └─→ POST /export/pdf                                    │
│          ├─ Parse JSON request body                          │
│          ├─ Validate: lang, length, icons, version           │
│          ├─ Render HTML to buffer                            │
│          ├─ Generate PDF via chromedp                        │
│          └─ Send PDF response with download header           │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                    cv_helpers.go                              │
│                 (Shared Utilities)                            │
├──────────────────────────────────────────────────────────────┤
│  func (h *CVHandler) prepareTemplateData(lang) map           │
│      └─→ Shared data preparation for all handlers            │
│          ├─ Load CV data: cvmodel.LoadCV(lang)               │
│          ├─ Load UI strings: uimodel.LoadUI(lang)            │
│          ├─ Calculate durations for experiences              │
│          ├─ Split skills into columns                        │
│          ├─ Add SEO metadata                                 │
│          └─ Return: complete data map                        │
│                                                               │
│  func (h *CVHandler) getFullURL(path) string                 │
│      └─→ Build absolute URLs for SEO/PDF                     │
│          └─ Return: http://host/path                         │
│                                                               │
│  func validateLanguage(lang) error                           │
│      └─→ Validate language parameter                         │
│          └─ Check: lang in ["en", "es"]                      │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                       types.go                                │
│              (Request/Response Types)                         │
├──────────────────────────────────────────────────────────────┤
│  // Request Types                                             │
│  type PDFExportRequest struct {                              │
│      Lang    string `json:"lang" validate:"required,oneof=en es"` │
│      Length  string `json:"length" validate:"required,oneof=short long"` │
│      Icons   string `json:"icons" validate:"required,oneof=show hide"` │
│      Version string `json:"version" validate:"required,oneof=with_skills clean"` │
│  }                                                            │
│                                                               │
│  // Response Types                                            │
│  type APIResponse struct {                                   │
│      Success bool        `json:"success"`                    │
│      Data    interface{} `json:"data,omitempty"`             │
│      Error   *ErrorInfo  `json:"error,omitempty"`            │
│      Meta    *MetaInfo   `json:"meta,omitempty"`             │
│  }                                                            │
│                                                               │
│  type ErrorInfo struct {                                     │
│      Code    string `json:"code"`                            │
│      Message string `json:"message"`                         │
│      Field   string `json:"field,omitempty"`                 │
│  }                                                            │
│                                                               │
│  type MetaInfo struct {                                      │
│      Timestamp time.Time `json:"timestamp"`                  │
│      RequestID string    `json:"request_id,omitempty"`       │
│  }                                                            │
│                                                               │
│  // Constructor Functions                                     │
│  func NewAPIResponse(data interface{}) *APIResponse          │
│  func NewErrorResponse(code, message string) *APIResponse    │
│  func NewPDFExportRequest() *PDFExportRequest                │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                      errors.go                                │
│                 (Error Handling)                              │
├──────────────────────────────────────────────────────────────┤
│  // Error Codes                                               │
│  type ErrorCode string                                       │
│  const (                                                      │
│      ErrCodeInvalidLanguage   = "INVALID_LANGUAGE"           │
│      ErrCodeInvalidLength     = "INVALID_LENGTH"             │
│      ErrCodeInvalidIcons      = "INVALID_ICONS"              │
│      ErrCodePDFGeneration     = "PDF_GENERATION"             │
│      ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED"        │
│      // ... 8 more error codes                               │
│  )                                                            │
│                                                               │
│  // Domain Error Type                                         │
│  type DomainError struct {                                   │
│      Code       ErrorCode                                    │
│      Message    string                                       │
│      Err        error                                        │
│      StatusCode int                                          │
│      Field      string                                       │
│  }                                                            │
│                                                               │
│  // Error Constructors                                        │
│  func InvalidLanguageError(lang) *DomainError                │
│  func InvalidLengthError(length) *DomainError                │
│  func PDFGenerationError(err) *DomainError                   │
│  // ... 10 more constructors                                 │
│                                                               │
│  // Error Handler                                             │
│  func (h *CVHandler) HandleError(w, r, err)                  │
│      └─→ Centralized error handling                          │
│          ├─ Log error with code                              │
│          ├─ Build error response                             │
│          └─ Send JSON error                                  │
└──────────────────────────────────────────────────────────────┘

Handler Dependencies

┌────────────────────────────────────────────────────────────┐
│                    Handler Dependencies                     │
└────────────────────────────────────────────────────────────┘

CVHandler
    ├─→ internal/templates (template rendering)
    │   └─→ Manager.Render(w, name, data)
    │
    ├─→ internal/models/cv (CV data)
    │   └─→ LoadCV(lang) (*CV, error)
    │
    ├─→ internal/models/ui (UI strings)
    │   └─→ LoadUI(lang) (*UI, error)
    │
    ├─→ internal/middleware (preferences)
    │   ├─→ GetPreferences(r) *Preferences
    │   ├─→ GetLanguage(r) string
    │   ├─→ IsLongCV(r) bool
    │   └─→ SetPreferenceCookie(w, name, value)
    │
    ├─→ internal/pdf (PDF generation)
    │   └─→ GeneratePDF(html, options) ([]byte, error)
    │
    └─→ encoding/json (JSON parsing)
        └─→ json.NewDecoder(r.Body).Decode(&req)

Handler Call Flow

┌────────────────────────────────────────────────────────────┐
│              Typical Handler Call Flow                     │
└────────────────────────────────────────────────────────────┘

Request arrives
     │
     ▼
┌─────────────────────┐
│  Middleware Chain   │
│  (preferences set)  │
└─────────────────────┘
     │
     ▼
┌─────────────────────┐
│  Handler Method     │
│  (cv_pages.go)      │
└─────────────────────┘
     │
     ├─→ middleware.GetPreferences(r)
     │   └─→ Extract from request context
     │
     ├─→ validateLanguage(lang)
     │   └─→ Check valid language
     │
     ├─→ h.prepareTemplateData(lang)
     │   │   (cv_helpers.go)
     │   │
     │   ├─→ cvmodel.LoadCV(lang)
     │   │   └─→ Read data/cv-{lang}.json
     │   │
     │   ├─→ uimodel.LoadUI(lang)
     │   │   └─→ Read data/ui-{lang}.json
     │   │
     │   ├─→ calculateDurations()
     │   │   └─→ For each experience
     │   │
     │   └─→ splitSkillsIntoColumns()
     │       └─→ Distribute evenly
     │
     └─→ h.tmpl.Render(w, "index.html", data)
         └─→ Execute template with data

Handler Testing Structure

┌────────────────────────────────────────────────────────────┐
│                   Handler Tests                            │
└────────────────────────────────────────────────────────────┘

cv_pages_test.go
    ├─ TestHome
    │   ├─ Valid requests (en, es)
    │   ├─ Invalid language
    │   ├─ With preferences
    │   └─ Default fallback
    │
    └─ TestCVContent
        ├─ Valid language
        ├─ With preferences
        └─ Error handling

cv_htmx_test.go
    ├─ TestToggleCVLength
    │   ├─ short → long
    │   ├─ long → short
    │   └─ Cookie setting
    │
    ├─ TestToggleCVIcons
    │   ├─ show → hide
    │   └─ hide → show
    │
    ├─ TestToggleCVTheme
    │   └─ default ↔ minimal
    │
    └─ TestToggleLanguage
        └─ en ↔ es

benchmarks_test.go
    ├─ BenchmarkHome
    ├─ BenchmarkCVContent
    ├─ BenchmarkToggleCVLength
    ├─ BenchmarkToggleCVIcons
    ├─ BenchmarkToggleCVTheme
    ├─ BenchmarkToggleLanguage
    ├─ BenchmarkExportPDF
    ├─ BenchmarkPrepareTemplateData
    ├─ BenchmarkValidateLanguage
    ├─ BenchmarkErrorResponse
    └─ BenchmarkNewAPIResponse

Handler Pattern Summary

┌────────────────────────────────────────────────────────────┐
│            Handler Organization Principles                  │
└────────────────────────────────────────────────────────────┘

1. SEPARATION BY RESPONSIBILITY
   ├─ Pages: Full page renders
   ├─ HTMX: Partial updates
   ├─ PDF: Export functionality
   └─ Helpers: Shared utilities

2. TYPE SAFETY
   ├─ Structured request types
   ├─ Structured response types
   └─ Validation tags

3. ERROR HANDLING
   ├─ Domain-specific errors
   ├─ Error codes
   └─ Centralized error handler

4. TESTABILITY
   ├─ Unit tests per file
   ├─ Integration tests
   └─ Benchmark tests

5. DEPENDENCY INJECTION
   ├─ Template manager injected
   ├─ No global state
   └─ Easy to mock

6. MIDDLEWARE INTEGRATION
   ├─ Preferences from context
   ├─ Helper functions
   └─ Clean separation

Performance Profile

Handler Performance Characteristics:

┌─────────────────────────────────────────────────────────┐
│  Handler                 Time        Allocations        │
├─────────────────────────────────────────────────────────┤
│  Home()                  ~50 ms      ~1200 allocs       │
│  CVContent()             ~45 ms      ~1100 allocs       │
│  ToggleCVLength()        ~45 ms      ~1100 allocs       │
│  ToggleCVIcons()         ~45 ms      ~1100 allocs       │
│  ToggleCVTheme()         ~45 ms      ~1100 allocs       │
│  ToggleLanguage()        ~50 ms      ~1200 allocs       │
│  ExportPDF()             ~1000 ms    ~5000 allocs       │
├─────────────────────────────────────────────────────────┤
│  prepareTemplateData()   ~2 ms       ~50 allocs         │
│  validateLanguage()      ~10 ns      0 allocs           │
└─────────────────────────────────────────────────────────┘

Memory Profile:
- Most allocations in template rendering (~90%)
- JSON parsing minimal (<1%)
- Helper functions optimized (zero-alloc where possible)