69012bb1ae
New test files: - config/config_test.go (100% coverage) - constants/constants_test.go (100% coverage) - httputil/response_test.go (100% coverage) - validation/rules_test.go (91.9% coverage) - middleware/logger_test.go, security_test.go, security_logger_test.go - handlers/errors_test.go Updated documentation: - doc/27-GO-TESTING.md: Complete testing guide - doc/00-GO-DOCUMENTATION-INDEX.md: Added testing section - doc/01-ARCHITECTURE.md: Updated package structure - doc/DECISIONS.md: Added ADR-004 caching decision - PROJECT-MEMORY.md: Added Go testing section
240 lines
6.5 KiB
Markdown
240 lines
6.5 KiB
Markdown
# Architectural Decisions
|
|
|
|
This document records key architectural decisions made for this project.
|
|
|
|
## Table of Contents
|
|
|
|
- [ADR-001: No Data Caching](#adr-001-no-data-caching) *(Superseded by ADR-004)*
|
|
- [ADR-002: Static Dates Instead of Git Integration](#adr-002-static-dates-instead-of-git-integration)
|
|
- [ADR-003: CI/CD with GitHub Actions](#adr-003-cicd-with-github-actions)
|
|
- [ADR-004: Application-Level Data Caching](#adr-004-application-level-data-caching)
|
|
|
|
---
|
|
|
|
## ADR-001: No Data Caching
|
|
|
|
**Status:** Superseded by [ADR-004](#adr-004-application-level-data-caching)
|
|
**Date:** 2025-11-30
|
|
|
|
### Context
|
|
|
|
The CV data (JSON files) is loaded from disk on every request. A caching layer could reduce disk I/O and improve response times.
|
|
|
|
### Decision
|
|
|
|
**No caching will be implemented for CV data.**
|
|
|
|
### Rationale
|
|
|
|
1. **Project Size**: This is a small, personal CV website with minimal traffic
|
|
2. **Simplicity**: Caching adds complexity (cache invalidation, memory management, TTL configuration)
|
|
3. **Performance is Already Good**: JSON file loading takes <10ms, which is acceptable
|
|
4. **Hot Reload**: In development, we want fresh data on every request for testing
|
|
5. **YAGNI**: We don't need caching until we have evidence of performance issues
|
|
|
|
### Consequences
|
|
|
|
- Simple, maintainable code
|
|
- No cache invalidation bugs
|
|
- Slightly higher disk I/O (negligible for this scale)
|
|
- If traffic increases significantly, this decision can be revisited
|
|
|
|
---
|
|
|
|
## ADR-002: Static Dates Instead of Git Integration
|
|
|
|
**Status:** Accepted
|
|
**Date:** 2025-11-30
|
|
|
|
### Context
|
|
|
|
Previously, the project had a feature to dynamically fetch project start dates from git repository first commit dates using `exec.CommandContext` to run `git log` commands.
|
|
|
|
### Decision
|
|
|
|
**Git command execution has been removed. Use static dates in JSON files instead.**
|
|
|
|
### Rationale
|
|
|
|
1. **Security Risk**: Executing shell commands (even with path validation) poses injection risks
|
|
2. **Symlink Bypass**: Path validation can be bypassed with symbolic links
|
|
3. **Unnecessary Complexity**: Static dates in JSON are simpler and more maintainable
|
|
4. **Control**: Static dates give full control over what's displayed
|
|
5. **Performance**: No external process spawning
|
|
|
|
### Implementation
|
|
|
|
Instead of `gitRepoUrl` in project data, use `startDate` directly:
|
|
|
|
```json
|
|
{
|
|
"title": "My Project",
|
|
"startDate": "2024-06",
|
|
"current": true
|
|
}
|
|
```
|
|
|
|
### Consequences
|
|
|
|
- More secure codebase
|
|
- Simpler implementation
|
|
- Manual date updates required when adding new projects
|
|
- No external dependencies on git binary
|
|
|
|
---
|
|
|
|
## ADR-003: CI/CD with GitHub Actions
|
|
|
|
**Status:** Implemented
|
|
**Date:** 2025-11-30
|
|
|
|
### Context
|
|
|
|
The project needs automated testing, linting, and deployment.
|
|
|
|
### Decision
|
|
|
|
**GitHub Actions is used for CI/CD with two workflows:**
|
|
|
|
1. **test.yml** - Runs on PRs and pushes to main/develop
|
|
2. **deploy.yml** - Deploys to production on push to main
|
|
|
|
### Workflows
|
|
|
|
#### Test Workflow (`.github/workflows/test.yml`)
|
|
|
|
Triggers: `push` and `pull_request` to `main` and `develop` branches
|
|
|
|
Steps:
|
|
1. Checkout code
|
|
2. Setup Go 1.25.1
|
|
3. Install and verify dependencies
|
|
4. Run golangci-lint
|
|
5. Run unit tests with coverage
|
|
6. Generate coverage report
|
|
7. Check coverage threshold (target: 70%)
|
|
8. Upload coverage to Codecov
|
|
9. Run benchmarks
|
|
10. Build binary
|
|
11. Upload artifacts
|
|
|
|
#### Deploy Workflow (`.github/workflows/deploy.yml`)
|
|
|
|
Triggers: `push` to `main` branch or manual dispatch
|
|
|
|
Steps:
|
|
1. SSH into production server
|
|
2. Fix repository permissions
|
|
3. Stash any local changes
|
|
4. Pull latest changes
|
|
5. Restart systemd service
|
|
6. Verify health check
|
|
|
|
### Required Secrets
|
|
|
|
- `SSH_PRIVATE_KEY` - SSH private key for server access
|
|
- `SSH_HOST` - Server IP or domain
|
|
- `SSH_USER` - SSH username
|
|
- `SSH_PORT` (optional, default: 22)
|
|
- `SERVICE_NAME` (optional, default: cv)
|
|
- `REPO_PATH` (optional, default: /home/txeo/Git/yo/cv)
|
|
|
|
### Consequences
|
|
|
|
- Automated quality checks on every PR
|
|
- Consistent deployment process
|
|
- Health check verification after deployment
|
|
- Coverage tracking with Codecov
|
|
- Binary artifacts available for download
|
|
|
|
---
|
|
|
|
## ADR-004: Application-Level Data Caching
|
|
|
|
**Status:** Accepted
|
|
**Date:** 2025-12-06
|
|
**Supersedes:** [ADR-001](#adr-001-no-data-caching)
|
|
|
|
### Context
|
|
|
|
As the CV site evolved to support multiple languages and increased usage, the original decision (ADR-001) to avoid caching was reconsidered. While the site traffic remains modest, the benefits of eliminating per-request file I/O became clear:
|
|
|
|
1. **Consistency**: Every request reads the same data
|
|
2. **Performance**: Eliminates disk I/O from hot paths
|
|
3. **Reliability**: Fail-fast at startup catches data errors early
|
|
4. **Simplicity**: No cache invalidation needed (data is static)
|
|
|
|
### Decision
|
|
|
|
**Implement application-level data caching with startup-time loading.**
|
|
|
|
The `internal/cache` package provides:
|
|
- `DataCache` struct holding CV and UI data for all supported languages
|
|
- Single load at application startup
|
|
- Thread-safe read access via `sync.RWMutex`
|
|
- Language-keyed retrieval (`GetCV(lang)`, `GetUI(lang)`)
|
|
|
|
### Implementation
|
|
|
|
```go
|
|
// At startup (main.go)
|
|
dataCache, err := cache.New([]string{"en", "es"})
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize data cache: %v", err)
|
|
}
|
|
|
|
// In handlers
|
|
cv := h.dataCache.GetCV(lang)
|
|
ui := h.dataCache.GetUI(lang)
|
|
```
|
|
|
|
### Rationale
|
|
|
|
1. **Zero Per-Request I/O**: Data loaded once, served from memory
|
|
2. **Fail-Fast**: All data issues caught at startup, not runtime
|
|
3. **Thread-Safe**: `sync.RWMutex` optimized for read-heavy workloads
|
|
4. **Minimal Complexity**: Simple map-based storage, no TTL/invalidation
|
|
5. **Testable**: 95.7% test coverage, including concurrency tests
|
|
|
|
### Consequences
|
|
|
|
- **Positive:**
|
|
- Faster request handling (no disk I/O)
|
|
- Earlier error detection (startup validation)
|
|
- Consistent data across requests
|
|
- Simple, well-tested implementation
|
|
|
|
- **Considerations:**
|
|
- Requires application restart to pick up data changes
|
|
- Memory usage increases slightly (minimal - ~KB per language)
|
|
- Deep copies required when handlers mutate data
|
|
|
|
### Documentation
|
|
|
|
See [23-DATA-CACHE.md](23-DATA-CACHE.md) for complete API reference and usage patterns.
|
|
|
|
---
|
|
|
|
## How to Add New Decisions
|
|
|
|
When making significant architectural decisions, add a new section following this template:
|
|
|
|
```markdown
|
|
## ADR-XXX: Title
|
|
|
|
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
**Date:** YYYY-MM-DD
|
|
|
|
### Context
|
|
What is the issue that we're seeing that is motivating this decision?
|
|
|
|
### Decision
|
|
What is the change that we're proposing?
|
|
|
|
### Rationale
|
|
Why is this the best choice?
|
|
|
|
### Consequences
|
|
What are the results of this decision?
|
|
```
|