d95c62bad4
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.
542 lines
25 KiB
Markdown
542 lines
25 KiB
Markdown
# 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
|