Files
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

25 KiB

Template Rendering Diagram

Template System Architecture

┌──────────────────────────────────────────────────────────────┐
│                Template System Architecture                   │
└──────────────────────────────────────────────────────────────┘

internal/templates/
├── manager.go          Template manager (caching, rendering)
└── functions.go        Custom template functions

templates/
├── index.html          Main page template
├── partials/           Reusable components
│   ├── header.html
│   ├── footer.html
│   ├── cv_content.html
│   ├── experience.html
│   ├── education.html
│   ├── skills.html
│   └── languages.html
└── layouts/            Layout templates
    └── base.html

Template Manager

┌──────────────────────────────────────────────────────────────┐
│            Template Manager (internal/templates/manager.go)   │
├──────────────────────────────────────────────────────────────┤
│  type Manager struct {                                        │
│      templates  map[string]*template.Template                │
│      config     *config.TemplateConfig                       │
│      mu         sync.RWMutex  // Thread-safe access          │
│  }                                                            │
│                                                               │
│  type TemplateConfig struct {                                │
│      Dir         string  // templates/                       │
│      PartialsDir string  // templates/partials/              │
│      HotReload   bool    // Reload on every render           │
│  }                                                            │
│                                                               │
│  Methods:                                                     │
│  ├─ NewManager(config) (*Manager, error)                     │
│  │   └─→ Initialize and load all templates                  │
│  │                                                            │
│  ├─ Render(w, name, data) error                              │
│  │   └─→ Execute template with data                         │
│  │                                                            │
│  ├─ loadTemplates() error                                    │
│  │   └─→ Parse and cache all templates                      │
│  │                                                            │
│  └─ reloadIfNeeded() error                                   │
│      └─→ Reload templates if hot reload enabled             │
└──────────────────────────────────────────────────────────────┘

Template Loading Flow

┌────────────────────────────────────────────────────────────┐
│                  Template Loading Flow                      │
└────────────────────────────────────────────────────────────┘

Application Start
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  NewManager(config)                                          │
│  (internal/templates/manager.go)                            │
│                                                              │
│  1. Create manager                                           │
│     m := &Manager{                                          │
│         templates: make(map[string]*template.Template),     │
│         config:    config,                                  │
│     }                                                        │
│                                                              │
│  2. Load all templates                                       │
│     if err := m.loadTemplates(); err != nil {               │
│         return nil, err                                     │
│     }                                                        │
│                                                              │
│  3. Return manager                                           │
│     return m, nil                                           │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  loadTemplates()                                             │
│                                                              │
│  1. Scan template directory                                  │
│     files, err := filepath.Glob(config.Dir + "/*.html")     │
│                                                              │
│  2. For each template file:                                  │
│     ├─ Create new template                                  │
│     │  tmpl := template.New(name)                           │
│     │                                                        │
│     ├─ Add custom functions                                 │
│     │  tmpl.Funcs(customFunctions())                        │
│     │                                                        │
│     ├─ Parse main template                                  │
│     │  tmpl.ParseFiles(file)                                │
│     │                                                        │
│     ├─ Parse partials                                       │
│     │  tmpl.ParseGlob(config.PartialsDir + "/*.html")       │
│     │                                                        │
│     └─ Cache template                                       │
│        m.templates[name] = tmpl                             │
│                                                              │
│  3. Log loaded templates                                     │
│     log.Printf("Loaded %d templates", len(m.templates))     │
└─────────────────────────────────────────────────────────────┘

Template Rendering Flow

┌────────────────────────────────────────────────────────────┐
│                  Template Rendering Flow                    │
└────────────────────────────────────────────────────────────┘

Handler calls Render()
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Manager.Render(w, "index.html", data)                       │
│  (internal/templates/manager.go)                            │
│                                                              │
│  1. Lock for reading                                         │
│     m.mu.RLock()                                            │
│     defer m.mu.RUnlock()                                    │
│                                                              │
│  2. Hot reload check                                         │
│     if m.config.HotReload {                                 │
│         m.mu.RUnlock()                                      │
│         m.mu.Lock()                                         │
│         m.loadTemplates()  // Reload all templates          │
│         m.mu.Unlock()                                       │
│         m.mu.RLock()                                        │
│     }                                                        │
│                                                              │
│  3. Get template from cache                                  │
│     tmpl, ok := m.templates[name]                           │
│     if !ok {                                                 │
│         return fmt.Errorf("template not found: %s", name)   │
│     }                                                        │
│                                                              │
│  4. Execute template                                         │
│     err := tmpl.Execute(w, data)                            │
│     if err != nil {                                          │
│         return fmt.Errorf("template execution: %w", err)    │
│     }                                                        │
│                                                              │
│  5. Return                                                   │
│     return nil                                              │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Template Execution                                          │
│                                                              │
│  1. Parse template directives                                │
│     {{.CV.Personal.Name}}                                   │
│     {{range .CV.Experience}}...{{end}}                      │
│     {{template "partials/header.html" .}}                   │
│                                                              │
│  2. Execute custom functions                                 │
│     {{formatDate .StartDate}}                               │
│     {{join .Highlights ", "}}                               │
│     {{lower .CVLanguage}}                                   │
│                                                              │
│  3. Include partials                                         │
│     {{template "partials/cv_content.html" .}}               │
│     {{template "partials/experience.html" .}}               │
│                                                              │
│  4. Generate HTML                                            │
│     Write to http.ResponseWriter                            │
└─────────────────────────────────────────────────────────────┘

Template Hierarchy

┌────────────────────────────────────────────────────────────┐
│                   Template Hierarchy                        │
└────────────────────────────────────────────────────────────┘

index.html (Main Template)
    │
    ├─→ {{template "partials/header.html" .}}
    │   └─→ Navigation, language toggle, theme toggle
    │
    ├─→ {{template "partials/cv_content.html" .}}
    │   │
    │   ├─→ {{template "partials/experience.html" .}}
    │   │   └─→ {{range .CV.Experience}}
    │   │       ├─ Company, position, dates
    │   │       ├─ {{.Duration}} (calculated)
    │   │       └─ {{range .Highlights}}
    │   │
    │   ├─→ {{template "partials/education.html" .}}
    │   │   └─→ {{range .CV.Education}}
    │   │       ├─ Institution, degree, field
    │   │       └─ Dates, GPA, honors
    │   │
    │   ├─→ {{template "partials/skills.html" .}}
    │   │   └─→ {{range .SkillsColumns}}
    │   │       └─ {{range .}}
    │   │           ├─ Skill name
    │   │           ├─ Level badge
    │   │           └─ Icon (if enabled)
    │   │
    │   └─→ {{template "partials/languages.html" .}}
    │       └─→ {{range .CV.Languages}}
    │           ├─ Language name
    │           └─ Proficiency level
    │
    └─→ {{template "partials/footer.html" .}}
        └─→ PDF export button, copyright

Template Data Structure

┌────────────────────────────────────────────────────────────┐
│              Template Data Structure                        │
└────────────────────────────────────────────────────────────┘

Data passed to templates:

map[string]interface{}{
    // CV Data
    "CV": &cvmodel.CV{
        Personal: cvmodel.Personal{
            Name:     "John Doe",
            Title:    "Senior Software Engineer",
            Email:    "john@example.com",
            Location: "San Francisco, CA",
        },
        Experience: []cvmodel.Experience{
            {
                Company:    "Tech Corp",
                Position:   "Senior Engineer",
                StartDate:  "2020-01",
                EndDate:    "",
                Current:    true,
                Duration:   "3 years 2 months", // Calculated
                Highlights: []string{...},
            },
        },
        Education: []cvmodel.Education{...},
        Skills:    cvmodel.Skills{...},
        Languages: []cvmodel.Language{...},
    },

    // UI Strings
    "UI": &uimodel.UI{
        Sections: uimodel.Sections{
            Summary:    "Professional Summary",
            Experience: "Work Experience",
            Education:  "Education",
            Skills:     "Technical Skills",
            Languages:  "Languages",
        },
        Buttons:  uimodel.Buttons{...},
        Messages: uimodel.Messages{...},
    },

    // User Preferences
    "Preferences": &middleware.Preferences{
        CVLength:    "long",
        CVIcons:     "show",
        CVLanguage:  "es",
        CVTheme:     "default",
        ColorTheme:  "light",
    },

    // Processed Data
    "SkillsColumns": [][]cvmodel.Skill{
        []cvmodel.Skill{...},  // Column 1
        []cvmodel.Skill{...},  // Column 2
        []cvmodel.Skill{...},  // Column 3
    },

    // SEO Metadata
    "PageTitle": "John Doe - Senior Software Engineer",
    "MetaDescription": "Professional CV of John Doe...",
    "CanonicalURL": "http://localhost:8080/",
    "OGImage": "http://localhost:8080/static/images/og-image.png",
}

Custom Template Functions

┌────────────────────────────────────────────────────────────┐
│              Custom Template Functions                      │
│              (internal/templates/functions.go)              │
└────────────────────────────────────────────────────────────┘

template.FuncMap{
    // String manipulation
    "lower": strings.ToLower,
    "upper": strings.ToUpper,
    "title": strings.Title,

    // Date formatting
    "formatDate": func(date string) string {
        if date == "" {
            return "Present"
        }
        t, _ := time.Parse("2006-01", date)
        return t.Format("Jan 2006")
    },

    // Array operations
    "join": strings.Join,
    "split": strings.Split,

    // Math
    "add": func(a, b int) int {
        return a + b
    },
    "multiply": func(a, b int) int {
        return a * b
    },

    // Conditional helpers
    "eq": func(a, b interface{}) bool {
        return a == b
    },
    "ne": func(a, b interface{}) bool {
        return a != b
    },

    // HTML safety
    "safe": func(s string) template.HTML {
        return template.HTML(s)
    },
}

Usage in templates:

{{formatDate .StartDate}}
// "2020-01" → "Jan 2020"

{{join .Highlights ", "}}
// ["foo", "bar"] → "foo, bar"

{{if eq .CVLength "long"}}
    <!-- Show long content -->
{{end}}

{{.Description | safe}}
// Render HTML without escaping

Template Conditionals

┌────────────────────────────────────────────────────────────┐
│                Template Conditionals                        │
└────────────────────────────────────────────────────────────┘

Show/Hide based on CV length:
{{if eq .Preferences.CVLength "long"}}
    <!-- Show full details -->
    <div class="experience-highlights">
        {{range .Highlights}}
            <li>{{.}}</li>
        {{end}}
    </div>
{{end}}

Show/Hide based on icons preference:
{{if eq .Preferences.CVIcons "show"}}
    <i class="icon-{{.Icon}}"></i>
{{end}}

Conditional classes:
<div class="cv-section {{if eq .Preferences.CVTheme "minimal"}}minimal{{end}}">
    ...
</div>

Language-specific content:
{{if eq .Preferences.CVLanguage "es"}}
    <span>Experiencia Profesional</span>
{{else}}
    <span>Professional Experience</span>
{{end}}

Current vs. past experience:
{{if .Current}}
    <span class="badge current">Present</span>
{{else}}
    <span>{{formatDate .EndDate}}</span>
{{end}}

Template Performance

┌────────────────────────────────────────────────────────────┐
│                Template Performance                         │
└────────────────────────────────────────────────────────────┘

Performance Characteristics:

┌─────────────────────────────────────────────────────────┐
│  Operation              Time        Notes                │
├─────────────────────────────────────────────────────────┤
│  Template Loading       ~50ms       On app start        │
│  ├─ Parse templates     ~40ms       Compile Go templates│
│  └─ Cache templates     ~10ms       Store in map        │
│                                                          │
│  Template Rendering     ~45ms       Per request         │
│  ├─ Template lookup     ~10ns       Map access          │
│  ├─ Template execute    ~40ms       Main cost           │
│  ├─ Partial includes    ~5ms        Include partials    │
│  └─ Function calls      ~100μs      Custom functions    │
│                                                          │
│  Hot Reload             ~50ms       If enabled          │
│  └─ Reload all          ~50ms       Parse again         │
└─────────────────────────────────────────────────────────┘

Optimization Strategies:
1. Template Caching
   └─→ Pre-compile templates at startup
       Serve from memory cache

2. Hot Reload (Development Only)
   └─→ Reload on every request for dev
       Disable in production for speed

3. Minimize Partials
   └─→ Balance reusability vs. overhead
       Each partial adds ~1ms

4. Pre-calculate Data
   └─→ Calculate durations in handler
       Split skills before rendering

5. Use Buffer Pool
   └─→ Reuse buffers for rendering
       Reduce allocations

Template Error Handling

┌────────────────────────────────────────────────────────────┐
│               Template Error Handling                       │
└────────────────────────────────────────────────────────────┘

Error Types:

1. Template Not Found
   Error: template "foo.html" not found
   Cause: Template doesn't exist in cache
   Fix:   Create template file, reload

2. Parse Error
   Error: template: index.html:42: unexpected "}"
   Cause: Syntax error in template
   Fix:   Check template syntax

3. Execution Error
   Error: template: executing "index.html": map has no entry for key "Foo"
   Cause: Missing data in template data map
   Fix:   Ensure all required data passed

4. Function Error
   Error: template: function "unknownFunc" not defined
   Cause: Custom function not registered
   Fix:   Register function in FuncMap

Error Flow:

Template Error
     │
     ├─→ Logged with stack trace
     │   log.Printf("[ERROR] Template: %v", err)
     │
     ├─→ Wrapped in DomainError
     │   TemplateError(err)
     │
     └─→ Sent as 500 response
         {
           "success": false,
           "error": {
             "code": "TEMPLATE_ERROR",
             "message": "Failed to render page"
           }
         }

Hot Reload Flow

┌────────────────────────────────────────────────────────────┐
│                     Hot Reload Flow                         │
│                   (Development Mode)                        │
└────────────────────────────────────────────────────────────┘

Developer edits template
     │
     ▼
Next request arrives
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Render() called                                             │
│                                                              │
│  if m.config.HotReload {                                    │
│      // Reload all templates                                │
│      m.mu.Lock()                                            │
│      m.loadTemplates()                                      │
│      m.mu.Unlock()                                          │
│  }                                                           │
│                                                              │
│  // Use fresh templates                                     │
│  tmpl := m.templates[name]                                  │
│  tmpl.Execute(w, data)                                      │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Page rendered with updated template
(No server restart needed)

⚠️  Hot reload disabled in production for performance