Files
cv-site/doc/_go-learning/refactorings/003-handler-split.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

374 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Refactoring #3: Handler Split - From Monolith to Focused Files
**Date**: 2024-11-20
**Type**: Code Organization, Maintainability
## Problem Statement
After implementing shared utilities and validation (Refactoring #2), the handler file remained problematic:
- **Single Monolithic File**: `internal/handlers/cv.go` was 1,001 lines
- **Mixed Concerns**: Page rendering, PDF export, HTMX toggles, and helpers all in one file
- **Difficult Navigation**: Finding specific functionality required scrolling through hundreds of lines
- **Poor Separation**: No clear boundaries between different types of handlers
## Solution
Split the monolithic handler into focused files by responsibility:
1. **cv.go** (29 lines) - CVHandler struct + constructor only
2. **cv_pages.go** (290 lines) - Page rendering handlers
3. **cv_pdf.go** (153 lines) - PDF export handler
4. **cv_htmx.go** (218 lines) - HTMX toggle handlers
5. **cv_helpers.go** (385 lines) - Helper functions
## Architecture
### Before (Monolithic)
```
internal/handlers/cv.go (1,001 lines)
├── CVHandler struct
├── NewCVHandler()
├── Home() (page handler)
├── CVContent() (page handler)
├── DefaultCVShortcut() (page handler)
├── ExportPDF() (PDF handler)
├── ToggleLength() (HTMX handler)
├── ToggleIcons() (HTMX handler)
├── SwitchLanguage() (HTMX handler)
├── ToggleTheme() (HTMX handler)
├── splitSkills() (helper)
├── calculateYearsOfExperience() (helper)
├── calculateDuration() (helper)
├── processProjectDates() (helper)
├── findProjectRoot() (helper)
├── validateRepoPath() (helper)
├── getGitRepoFirstCommitDate() (helper)
├── prepareTemplateData() (helper)
├── getPreferenceCookie() (helper)
└── setPreferenceCookie() (helper)
```
### After (Focused Files)
```
internal/handlers/
├── cv.go (29 lines)
│ ├── CVHandler struct
│ └── NewCVHandler()
├── cv_pages.go (290 lines)
│ ├── Home() - Full CV page
│ ├── CVContent() - HTMX content swap
│ └── DefaultCVShortcut() - Shortcut PDF URLs
├── cv_pdf.go (153 lines)
│ └── ExportPDF() - PDF generation with options
├── cv_htmx.go (218 lines)
│ ├── ToggleLength() - Short/long toggle
│ ├── ToggleIcons() - Show/hide icons
│ ├── SwitchLanguage() - EN/ES switching
│ └── ToggleTheme() - Default/clean theme
└── cv_helpers.go (385 lines)
├── Skills helpers:
│ └── splitSkills()
├── Date/Duration helpers:
│ ├── calculateYearsOfExperience()
│ ├── calculateDuration()
│ └── processProjectDates()
├── Git helpers:
│ ├── findProjectRoot()
│ ├── validateRepoPath()
│ └── getGitRepoFirstCommitDate()
├── Template helpers:
│ └── prepareTemplateData()
└── Cookie helpers:
├── getPreferenceCookie()
└── setPreferenceCookie()
```
## Benefits
### 1. Single Responsibility Principle (SRP)
Each file now has ONE clear purpose:
**cv.go** - Defines the handler structure
```go
// CVHandler handles CV-related requests
// Methods are split across multiple files for better organization:
// - cv_pages.go: Page rendering (Home, CVContent, DefaultCVShortcut)
// - cv_pdf.go: PDF export (ExportPDF)
// - cv_htmx.go: HTMX toggles (ToggleLength, ToggleIcons, SwitchLanguage, ToggleTheme)
// - cv_helpers.go: Helper functions (skills, dates, git, templates, cookies)
type CVHandler struct {
templates *templates.Manager
pdfGenerator *pdf.Generator
serverAddr string
}
```
### 2. Improved Discoverability
**Easy to find functionality:**
- Need to modify page rendering? → `cv_pages.go`
- PDF generation issue? → `cv_pdf.go`
- HTMX toggle not working? → `cv_htmx.go`
- Helper function bug? → `cv_helpers.go`
### 3. Reduced Cognitive Load
**Before**: Navigate 1,001 lines to understand one feature
**After**: Open the relevant ~150-400 line file
### 4. Better Code Organization
**cv_helpers.go** groups helpers by category with clear section markers:
```go
// ==============================================================================
// SKILLS HELPERS
// ==============================================================================
// splitSkills splits skill categories between left and right sidebars
func splitSkills(skills []cvmodel.SkillCategory) (left, right []cvmodel.SkillCategory) {
// ...
}
// ==============================================================================
// DATE/DURATION HELPERS
// ==============================================================================
// calculateYearsOfExperience calculates years since April 1, 2005
func calculateYearsOfExperience() int {
// ...
}
```
### 5. Parallel Development
Multiple developers can now work on different handler concerns without conflicts:
- Developer A: Adds new HTMX toggle → edits `cv_htmx.go`
- Developer B: Modifies PDF export → edits `cv_pdf.go`
- Developer C: Adds page handler → edits `cv_pages.go`
No merge conflicts!
### 6. Testability
Each file can have focused tests:
- `cv_pages_test.go` - Page rendering tests
- `cv_pdf_test.go` - PDF generation tests
- `cv_htmx_test.go` - HTMX toggle tests
- `cv_helpers_test.go` - Helper function tests
### 7. Documentation Clarity
Each file's purpose is immediately clear from its name and can have targeted documentation.
## Implementation Details
### Why These Groupings?
**cv_pages.go** - All handlers that render full pages or page sections
- `Home()` - Complete HTML page
- `CVContent()` - HTMX content swap
- `DefaultCVShortcut()` - Special PDF shortcut URLs
**cv_pdf.go** - PDF generation is complex enough to warrant its own file
- Handles multiple query parameters (lang, length, icons, version)
- Manages PDF generation with chromedp
- Complex filename generation logic
**cv_htmx.go** - All HTMX interactivity handlers
- Similar patterns (toggle states, cookies, out-of-band swaps)
- All follow same structure: read state → toggle → save → render
**cv_helpers.go** - All supporting functions
- Organized by category with section markers
- Pure functions (no HTTP request/response handling)
- Reusable across handlers
### Go Package Benefits
All files are in the same package (`package handlers`), so:
- ✅ Methods can be split across files (Go allows this!)
- ✅ Helper functions accessible without imports
- ✅ No circular dependency issues
- ✅ Same namespace, better organization
## Code Metrics
### File Sizes
| File | Lines | Purpose | Complexity |
|------|-------|---------|------------|
| cv.go | 29 | Struct + constructor | Very Low |
| cv_pages.go | 290 | Page rendering | Medium |
| cv_pdf.go | 153 | PDF export | Medium |
| cv_htmx.go | 218 | HTMX toggles | Low |
| cv_helpers.go | 385 | Helper functions | Low-Medium |
| **Total** | **1,075** | | **Average** |
### Reduction Achievement
- **Original**: 1 file × 1,001 lines = **1,001 lines**
- **New**: 5 files × 215 lines avg = **1,075 lines**
- **Net Change**: +74 lines (+7.4%)
The slight increase is due to:
- Comments documenting each file's purpose
- Section markers in cv_helpers.go for better organization
- More descriptive comments at file level
**Trade-off**: +74 lines for dramatically improved maintainability and organization.
### Maintainability Index
**Before**:
- 1,001 lines to search through
- 19 functions mixed together
- No clear organization
**After**:
- 29-385 lines per file
- 3-9 functions per file (focused)
- Clear organization by responsibility
## Testing
### All Tests Pass
```bash
$ go test ./...
ok github.com/juanatsap/cv-site/internal/fileutil 0.432s
ok github.com/juanatsap/cv-site/internal/handlers 0.789s
ok github.com/juanatsap/cv-site/internal/lang 0.326s
ok github.com/juanatsap/cv-site/internal/models/cv 0.463s
ok github.com/juanatsap/cv-site/internal/models/ui 0.315s
```
### Verification
1. **Build**: ✅ `go build` succeeds
2. **Tests**: ✅ All unit tests pass
3. **Server**: ✅ Server starts and renders pages
4. **Endpoints**: ✅ All HTTP endpoints functional
## Why This Approach?
### Alternative Considered: Separate Packages
Could we split into separate packages?
```
internal/
├── handlers/pages/
├── handlers/pdf/
├── handlers/htmx/
└── handlers/helpers/
```
**Why NOT:**
- Creates circular dependencies (pages need helpers, helpers need CVHandler)
- More complex imports
- Breaks Go's "methods on types" pattern (can't split CVHandler methods across packages)
**Why Single Package:**
- ✅ Methods can be defined in any file
- ✅ Helpers accessible without imports
- ✅ Single namespace, no confusion
- ✅ Go's design encourages this pattern
### Go Best Practices
This approach follows **Go best practices**:
1. **Package organization by feature, not by layer**
- All CV handler code stays in `handlers` package
- Files split by sub-feature (pages, PDF, HTMX, helpers)
2. **Methods split across files**
- Go allows defining methods on a type in any file in the same package
- CVHandler methods spread across multiple files naturally
3. **Clear file naming**
- Prefix indicates grouping: `cv_pages.go`, `cv_pdf.go`, `cv_htmx.go`
- Easy to find related functionality
## Interview Talking Points
### 1. Code Organization
"I refactored a 1,001-line monolithic handler into 5 focused files (29-385 lines each), improving discoverability and maintainability while following Go's single-package-multiple-files pattern."
### 2. Single Responsibility Principle
"Each file now has one clear purpose: cv_pages handles page rendering, cv_pdf manages PDF export, cv_htmx handles interactivity, and cv_helpers provides reusable functions."
### 3. Maintainability Over Brevity
"I accepted a 7.4% line increase to gain dramatically improved organization. The trade-off of 74 extra lines for better maintainability was worth it."
### 4. Go Package Patterns
"I kept all files in one package to avoid circular dependencies and leverage Go's ability to split methods across files, rather than forcing artificial package boundaries."
### 5. Parallel Development
"The split enables multiple developers to work on different handler concerns without conflicts, improving team velocity."
### 6. Progressive Refactoring
"This is refactoring #3 in a series: #1 separated domain models, #2 added shared utilities and validation, #3 organized handlers. Each step builds on the previous, improving the codebase incrementally."
## Future Improvements
1. **Extract Duplicate Logic**: `Home()` and `CVContent()` have similar data preparation - could use `prepareTemplateData()`
2. **Handler Tests**: Add focused tests for each handler file
3. **Middleware Extraction**: Cookie handling could become middleware
4. **Request/Response Types**: Define structs for common request/response patterns
5. **Error Handling**: Centralize error response formatting
## Related Documentation
- [Refactoring #1: CV/UI Model Separation](./001-cv-model-separation.md)
- [Refactoring #2: Shared Utilities & Validation](./002-shared-utilities-validation.md)
- [Server Design: Why Goroutines?](../architecture/server-design.md)
## Commit Message
```
refactor: Split monolithic handler into focused files
Split internal/handlers/cv.go (1,001 lines) into 5 focused files:
Structure:
- cv.go (29 lines) - CVHandler struct + constructor
- cv_pages.go (290 lines) - Page handlers (Home, CVContent, DefaultCVShortcut)
- cv_pdf.go (153 lines) - PDF export handler (ExportPDF)
- cv_htmx.go (218 lines) - HTMX toggle handlers (Length, Icons, Language, Theme)
- cv_helpers.go (385 lines) - Helper functions (skills, dates, git, templates, cookies)
Benefits:
- Single Responsibility: Each file has one clear purpose
- Improved Discoverability: Easy to find specific functionality
- Reduced Cognitive Load: 200-400 lines per file vs 1,001
- Parallel Development: No conflicts when editing different concerns
- Better Organization: Clear section markers and grouping
- Maintainability: Trade +74 lines (+7.4%) for better organization
Testing:
- All Go tests pass (fileutil, handlers, lang, cv, ui)
- Server builds and runs correctly
- All HTTP endpoints functional
- No breaking changes
Documentation:
- Create _go-learning/refactorings/003-handler-split.md
- Document architecture, benefits, and trade-offs
- Explain WHY single package vs separate packages
```