Files
cv-site/doc/_go-learning/diagrams/07-template-rendering.md
T

542 lines
25 KiB
Markdown
Raw Normal View History

# 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
```
## Related Diagrams
- [System Architecture](./01-system-architecture.md) - Overall system design
- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
- [Handler Organization](./04-handler-organization.md) - Handler structure
- [Data Models](./05-data-models.md) - Data structures