2025-10-20 08:54:21 +01:00
|
|
|
# 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)
|
2025-12-06 17:51:20 +00:00
|
|
|
├── cache/ # Application-level data caching
|
2025-10-20 08:54:21 +01:00
|
|
|
├── config/ # Configuration management
|
2025-12-06 17:51:20 +00:00
|
|
|
├── constants/ # Project-wide constants
|
|
|
|
|
├── email/ # Email service (SMTP)
|
|
|
|
|
├── fileutil/ # File path utilities
|
2025-10-20 08:54:21 +01:00
|
|
|
├── handlers/ # HTTP request handlers
|
2025-12-06 17:51:20 +00:00
|
|
|
├── httputil/ # HTTP response helpers
|
2025-12-02 14:35:37 +00:00
|
|
|
├── middleware/ # HTTP middleware (security, logging, rate limiting)
|
2025-12-06 17:51:20 +00:00
|
|
|
├── models/ # Data models (cv, ui)
|
2025-12-02 14:35:37 +00:00
|
|
|
├── pdf/ # PDF generation service
|
2025-12-06 17:51:20 +00:00
|
|
|
├── routes/ # Route configuration
|
2025-12-02 14:35:37 +00:00
|
|
|
├── templates/ # Template management
|
|
|
|
|
└── validation/ # Input validation utilities
|
2025-10-20 08:54:21 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**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:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// ✅ Good: Dependencies injected
|
|
|
|
|
type CVHandler struct {
|
2025-12-02 14:35:37 +00:00
|
|
|
templates *templates.Manager
|
2025-12-06 17:51:20 +00:00
|
|
|
emailService *email.Service
|
|
|
|
|
dataCache *cache.DataCache
|
2025-10-20 08:54:21 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 17:51:20 +00:00
|
|
|
func NewCVHandler(tmpl *templates.Manager, addr string, emailSvc *email.Service, dc *cache.DataCache) *CVHandler {
|
2025-12-02 14:35:37 +00:00
|
|
|
return &CVHandler{
|
|
|
|
|
templates: tmpl,
|
2025-12-06 17:51:20 +00:00
|
|
|
emailService: emailSvc, // Can be nil for graceful degradation
|
|
|
|
|
dataCache: dc, // Startup-loaded data cache
|
2025-12-02 14:35:37 +00:00
|
|
|
}
|
2025-10-20 08:54:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ❌ 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:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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**:
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
cfg := config.Load() // Reads from env vars
|
2025-10-29 14:04:24 +00:00
|
|
|
cfg.Server.Port // Defaults to "1999"
|
2025-10-20 08:54:21 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type CVHandler struct {
|
2025-12-02 14:35:37 +00:00
|
|
|
templates *templates.Manager
|
|
|
|
|
pdfGenerator *pdf.Generator
|
|
|
|
|
emailService *services.EmailService
|
|
|
|
|
serverAddr string
|
2025-10-20 08:54:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request)
|
|
|
|
|
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request)
|
2025-12-02 14:35:37 +00:00
|
|
|
func (h *CVHandler) HandleContact(w http.ResponseWriter, r *http.Request)
|
2025-10-20 08:54:21 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Features**:
|
|
|
|
|
- Clean separation of routes
|
|
|
|
|
- Dependency injection
|
|
|
|
|
- Consistent error handling
|
|
|
|
|
- HTMX-aware responses
|
|
|
|
|
|
2025-12-06 17:51:20 +00:00
|
|
|
### Email Service (`internal/email`)
|
2025-12-02 14:35:37 +00:00
|
|
|
|
|
|
|
|
**Pattern**: Service layer with dependency injection and interface-based design
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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):
|
|
|
|
|
```bash
|
|
|
|
|
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
|
|
|
|
|
```
|
|
|
|
|
|
2025-10-20 08:54:21 +01:00
|
|
|
### Middleware (`internal/middleware`)
|
|
|
|
|
|
|
|
|
|
**Components**:
|
|
|
|
|
1. **Recovery**: Catches panics, logs stack traces
|
|
|
|
|
2. **Logger**: Structured request/response logging
|
|
|
|
|
3. **SecurityHeaders**: CSP, XSS protection, clickjacking prevention
|
2025-12-02 14:35:37 +00:00
|
|
|
4. **BrowserOnly**: Blocks non-browser requests (curl, wget, bots) for sensitive endpoints
|
|
|
|
|
5. **RateLimiter**: Per-IP rate limiting with configurable limits and time windows
|
|
|
|
|
6. **OriginChecker**: Validates request origin for CSRF protection
|
|
|
|
|
7. **CacheControl**: Dynamic cache headers based on content type
|
|
|
|
|
8. **PreferencesMiddleware**: Cookie-based user preference handling
|
2025-10-20 08:54:21 +01:00
|
|
|
|
|
|
|
|
## Security Features
|
|
|
|
|
|
|
|
|
|
### 1. Security Headers
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
appErr := NewAppError(err, "Database error", 500, true)
|
|
|
|
|
// Client sees: "Internal Server Error"
|
|
|
|
|
// Logs show: actual error details
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Performance Optimizations
|
|
|
|
|
|
|
|
|
|
### 1. Template Caching
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 1. Health check
|
2025-10-29 14:04:24 +00:00
|
|
|
curl http://localhost:1999/health
|
2025-10-20 08:54:21 +01:00
|
|
|
|
|
|
|
|
# 2. Happy path
|
2025-10-29 14:04:24 +00:00
|
|
|
curl "http://localhost:1999/?lang=en"
|
2025-10-20 08:54:21 +01:00
|
|
|
|
|
|
|
|
# 3. Error cases
|
2025-10-29 14:04:24 +00:00
|
|
|
curl "http://localhost:1999/?lang=invalid" # 400 Bad Request
|
2025-10-20 08:54:21 +01:00
|
|
|
|
|
|
|
|
# 4. HTMX requests
|
2025-10-29 14:04:24 +00:00
|
|
|
curl -H "HX-Request: true" "http://localhost:1999/cv?lang=es"
|
2025-10-20 08:54:21 +01:00
|
|
|
|
|
|
|
|
# 5. Security headers
|
2025-10-29 14:04:24 +00:00
|
|
|
curl -I http://localhost:1999/
|
2025-10-20 08:54:21 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Future: Automated Tests
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
go build -o cv-server -ldflags="-s -w" .
|
|
|
|
|
./cv-server
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Docker
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker build -t cv-server .
|
2025-10-29 14:04:24 +00:00
|
|
|
docker run -p 1999:1999 cv-server
|
2025-10-20 08:54:21 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 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
|
|
|
|
|
|
|
|
|
|
1. ✅ **Standard Project Layout**: Internal packages, clear structure
|
|
|
|
|
2. ✅ **Error Handling**: Custom error types, consistent handling
|
|
|
|
|
3. ✅ **Logging**: Structured, informative logs
|
|
|
|
|
4. ✅ **Configuration**: Environment-based with defaults
|
|
|
|
|
5. ✅ **Security**: Multiple layers of protection
|
|
|
|
|
6. ✅ **Graceful Shutdown**: Clean service termination
|
|
|
|
|
7. ✅ **Dependency Injection**: Testable, maintainable code
|
|
|
|
|
8. ✅ **Middleware Pattern**: Separation of concerns
|
|
|
|
|
9. ✅ **Template Management**: Efficient, cached rendering
|
|
|
|
|
10. ✅ **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)
|
|
|
|
|
1. **Load Balancer**: nginx, Caddy, or cloud LB
|
|
|
|
|
2. **Shared Storage**: For static files (S3, Cloud Storage)
|
|
|
|
|
3. **Health Checks**: `/health` endpoint already implemented
|
|
|
|
|
4. **Metrics**: Add Prometheus metrics
|
|
|
|
|
5. **Caching**: Redis for template cache (if very high traffic)
|
|
|
|
|
|
|
|
|
|
## Monitoring & Observability
|
|
|
|
|
|
|
|
|
|
### Current Implementation
|
|
|
|
|
- **Structured Logging**: Request/response logging
|
|
|
|
|
- **Health Check**: `/health` endpoint
|
|
|
|
|
- **Error Tracking**: Detailed error logs with stack traces
|
|
|
|
|
|
|
|
|
|
### Future Enhancements
|
|
|
|
|
```go
|
|
|
|
|
// 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.
|