Architecture updates: - Add EmailService documentation with config and flow diagram - Update CVHandler struct to show all dependencies - Add new middleware components (BrowserOnly, RateLimiter, etc.) - Update package structure to include services, pdf, validation New unit tests for HandleContact (9 tests): - Valid submission - Missing email/message validation - Honeypot bot protection - Timing-based bot protection (too fast) - Invalid HTTP method (405) - Invalid email format - Message too short - Spanish language support Includes MockEmailService for isolated testing.
11 KiB
Architecture Documentation
Overview
This CV website is built following Go best practices with a focus on:
- Simplicity: Clean, readable code
- Maintainability: Well-structured packages
- Reliability: Proper error handling and logging
- Performance: Efficient template caching
- Security: Multiple layers of protection
Architecture Patterns
1. Package Structure (Internal Directory Pattern)
cv/
├── main.go # Application entry point
└── internal/ # Private packages (cannot be imported by other projects)
├── config/ # Configuration management
├── handlers/ # HTTP request handlers
├── middleware/ # HTTP middleware (security, logging, rate limiting)
├── models/ # Data models and business logic
├── pdf/ # PDF generation service
├── services/ # Business services (email, etc.)
├── templates/ # Template management
└── validation/ # Input validation utilities
Benefits:
- Clear separation of concerns
- Encapsulation (internal packages cannot be imported externally)
- Easy to test and maintain
- Scalable structure
2. Dependency Injection
Handlers and services receive their dependencies through constructors:
// ✅ Good: Dependencies injected
type CVHandler struct {
templates *templates.Manager
emailService *services.EmailService
}
func NewCVHandler(tmpl *templates.Manager, addr string, email *services.EmailService) *CVHandler {
return &CVHandler{
templates: tmpl,
emailService: email, // Can be nil for graceful degradation
}
}
// ❌ Bad: Global state
var globalTemplates *template.Template
Benefits:
- Testability (easy to mock dependencies)
- Flexibility (swap implementations)
- Explicit dependencies
3. Error Handling Strategy
Three-tier error handling:
// 1. Domain errors (AppError)
type AppError struct {
Err error // Original error
Message string // User-friendly message
StatusCode int // HTTP status
Internal bool // Hide details from client
}
// 2. Error constructors for common cases
NotFoundError("Resource not found")
BadRequestError("Invalid input")
InternalError(err)
// 3. Centralized error handler
HandleError(w, r, err)
Benefits:
- Consistent error responses
- Proper logging
- Security (internal errors hidden)
- Client-specific responses (JSON, HTML, HTMX)
4. Middleware Chain
Onion-like middleware wrapping:
Request → Recovery → Logger → Security → Handler → Response
↑ ↑ ↑ ↑
Panics Logging Headers Business Logic
Implementation:
handler := middleware.Recovery(
middleware.Logger(
middleware.SecurityHeaders(mux),
),
)
Benefits:
- Separation of cross-cutting concerns
- Reusable components
- Easy to add/remove middleware
- Predictable request flow
Component Details
Configuration Management (internal/config)
Pattern: Environment-based configuration with sensible defaults
cfg := config.Load() // Reads from env vars
cfg.Server.Port // Defaults to "1999"
cfg.Template.HotReload // Auto-detects development mode
Features:
- Environment variable support
- Type-safe configuration
- Development/production modes
- Sensible defaults
Template Management (internal/templates)
Pattern: Template manager with hot-reload support
manager := templates.NewManager(cfg)
manager.Render("index.html") // Hot-reloads in dev mode
Features:
- Centralized template loading
- Custom template functions
- Hot-reload in development
- Thread-safe caching
- Graceful error handling
HTTP Handlers (internal/handlers)
Pattern: Handler structs with methods
type CVHandler struct {
templates *templates.Manager
pdfGenerator *pdf.Generator
emailService *services.EmailService
serverAddr string
}
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request)
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request)
func (h *CVHandler) HandleContact(w http.ResponseWriter, r *http.Request)
Features:
- Clean separation of routes
- Dependency injection
- Consistent error handling
- HTMX-aware responses
Email Service (internal/services)
Pattern: Service layer with dependency injection and interface-based design
type EmailService struct {
config *EmailConfig
}
type EmailConfig struct {
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPassword string
FromEmail string
ToEmail string
}
func NewEmailService(config *EmailConfig) *EmailService
func (e *EmailService) SendContactForm(data *ContactFormData) error
Features:
- TLS support (port 465 implicit SSL, port 587 STARTTLS)
- Multipart email formatting (HTML + plain text)
- Input validation with header injection prevention
- Reply-To header support for easy responses
- Graceful degradation (nil service skips email sending)
Email Flow:
Contact Form → HandleContact → EmailService.SendContactForm
↓
Validation → Build HTML/Text Body → Connect SMTP → Send
Configuration (via environment variables):
SMTP_HOST=smtp.dreamhost.com
SMTP_PORT=465
SMTP_USER=info@example.com
SMTP_PASSWORD=secret
SMTP_FROM_EMAIL=info@example.com
CONTACT_EMAIL=recipient@example.com
Middleware (internal/middleware)
Components:
- Recovery: Catches panics, logs stack traces
- Logger: Structured request/response logging
- SecurityHeaders: CSP, XSS protection, clickjacking prevention
- BrowserOnly: Blocks non-browser requests (curl, wget, bots) for sensitive endpoints
- RateLimiter: Per-IP rate limiting with configurable limits and time windows
- OriginChecker: Validates request origin for CSRF protection
- CacheControl: Dynamic cache headers based on content type
- PreferencesMiddleware: Cookie-based user preference handling
Security Features
1. Security Headers
X-Frame-Options: SAMEORIGIN // Prevent clickjacking
X-Content-Type-Options: nosniff // Prevent MIME sniffing
X-XSS-Protection: 1; mode=block // XSS protection
Content-Security-Policy: ... // Restrict resource loading
Referrer-Policy: strict-origin-... // Control referrer info
2. Request Timeouts
server := &http.Server{
ReadTimeout: 15 * time.Second, // Prevent slow clients
WriteTimeout: 15 * time.Second, // Prevent slow responses
IdleTimeout: 120 * time.Second, // Keep-alive timeout
}
3. Error Information Hiding
appErr := NewAppError(err, "Database error", 500, true)
// Client sees: "Internal Server Error"
// Logs show: actual error details
Performance Optimizations
1. Template Caching
// Templates parsed once at startup
templates := template.New("").ParseGlob("*.html")
// Hot-reload only in development
if cfg.Template.HotReload {
templates.ParseGlob("*.html") // Re-parse on each request
}
2. Static File Caching
// Development: 1 hour cache
// Production: 1 day cache
Cache-Control: public, max-age=86400
3. HTTP/2 Support
Go's http.Server automatically supports HTTP/2 when using HTTPS.
Graceful Shutdown
// 1. Listen for signals
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
// 2. Shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
server.Shutdown(ctx)
Features:
- Finish in-flight requests
- 30-second grace period
- Forced shutdown if timeout exceeded
HTMX Integration Patterns
1. Partial Content Rendering
// Full page: /
// Partial: /cv?lang=es (returns only content div)
if r.Header.Get("HX-Request") != "" {
// HTMX request - return partial
renderPartial()
} else {
// Regular request - return full page
renderFull()
}
2. Language Switching
<button hx-get="/cv?lang=es"
hx-target="#cv-content"
hx-swap="innerHTML">
🇪🇸 Español
</button>
Benefits:
- No JavaScript frameworks needed
- Progressive enhancement
- Server-rendered content
- Better SEO
Testing Strategy
Manual Testing
# 1. Health check
curl http://localhost:1999/health
# 2. Happy path
curl "http://localhost:1999/?lang=en"
# 3. Error cases
curl "http://localhost:1999/?lang=invalid" # 400 Bad Request
# 4. HTMX requests
curl -H "HX-Request: true" "http://localhost:1999/cv?lang=es"
# 5. Security headers
curl -I http://localhost:1999/
Future: Automated Tests
func TestCVHandler_Home(t *testing.T) {
// Setup
cfg := &config.TemplateConfig{Dir: "../../templates"}
tmpl, _ := templates.NewManager(cfg)
handler := handlers.NewCVHandler(tmpl)
// Execute
req := httptest.NewRequest("GET", "/?lang=en", nil)
w := httptest.NewRecorder()
handler.Home(w, req)
// Assert
assert.Equal(t, 200, w.Code)
}
Deployment Options
1. Standalone Binary
go build -o cv-server -ldflags="-s -w" .
./cv-server
2. Docker
docker build -t cv-server .
docker run -p 1999:1999 cv-server
3. Cloud Platforms
Recommended:
- Fly.io:
fly launch(auto-detects Dockerfile) - Railway: Connect GitHub, auto-deploy
- Google Cloud Run: Serverless containers
- AWS ECS/Fargate: Container orchestration
Best Practices Applied
- ✅ Standard Project Layout: Internal packages, clear structure
- ✅ Error Handling: Custom error types, consistent handling
- ✅ Logging: Structured, informative logs
- ✅ Configuration: Environment-based with defaults
- ✅ Security: Multiple layers of protection
- ✅ Graceful Shutdown: Clean service termination
- ✅ Dependency Injection: Testable, maintainable code
- ✅ Middleware Pattern: Separation of concerns
- ✅ Template Management: Efficient, cached rendering
- ✅ Production-Ready: Timeouts, health checks, monitoring hooks
Scaling Considerations
Current State: Single Instance
- Perfect for CV website (low traffic)
- ~1000s of requests/second capability
- Minimal resource usage
Future: Multi-Instance (if needed)
- Load Balancer: nginx, Caddy, or cloud LB
- Shared Storage: For static files (S3, Cloud Storage)
- Health Checks:
/healthendpoint already implemented - Metrics: Add Prometheus metrics
- Caching: Redis for template cache (if very high traffic)
Monitoring & Observability
Current Implementation
- Structured Logging: Request/response logging
- Health Check:
/healthendpoint - Error Tracking: Detailed error logs with stack traces
Future Enhancements
// Metrics
prometheus.InstrumentHandler("/", handler)
// Distributed Tracing
opentelemetry.Trace(handler)
// Error Monitoring
sentry.CaptureException(err)
Conclusion
This architecture provides:
- ✅ Clean, maintainable code
- ✅ Production-ready error handling
- ✅ Security best practices
- ✅ Performance optimizations
- ✅ Easy deployment options
- ✅ Room for future growth
Perfect for a professional CV website with potential to scale.