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.
10 KiB
10 KiB
Code Organization Best Practices
Project Structure
Standard Go Project Layout
cv-website/
├── cmd/ # Main applications
│ └── server/
│ └── main.go # Application entry point
│
├── internal/ # Private application code
│ ├── config/ # Configuration
│ ├── handlers/ # HTTP handlers
│ ├── middleware/ # HTTP middleware
│ ├── models/ # Data models
│ │ ├── cv/ # CV data structures
│ │ └── ui/ # UI data structures
│ ├── pdf/ # PDF generation
│ ├── routes/ # Route setup
│ └── templates/ # Template management
│
├── data/ # Static data files
│ ├── cv-en.json
│ ├── cv-es.json
│ ├── ui-en.json
│ └── ui-es.json
│
├── templates/ # HTML templates
│ ├── index.html
│ └── partials/
│ ├── header.html
│ └── footer.html
│
├── static/ # Static assets
│ ├── css/
│ ├── js/
│ └── images/
│
├── tests/ # Test files
│ └── integration/
│
├── _go-learning/ # Educational documentation
│ ├── diagrams/
│ ├── patterns/
│ ├── refactorings/
│ └── best-practices/
│
├── go.mod # Go module definition
├── go.sum # Dependency checksums
├── Makefile # Build automation
└── README.md # Project documentation
Package Organization Principles
1. Use internal/ for Private Code
// ✅ GOOD: Private to this module
internal/handlers/cv.go
// ❌ BAD: Can be imported by other modules
handlers/cv.go
Why: internal/ prevents external packages from importing your code, enforcing API boundaries.
2. Group by Feature, Not Layer
// ✅ GOOD: Grouped by domain
internal/
├── handlers/
│ ├── cv.go
│ ├── cv_pages.go
│ ├── cv_htmx.go
│ ├── cv_pdf.go
│ ├── cv_helpers.go
│ ├── types.go
│ └── errors.go
// ❌ BAD: Grouped by type
internal/
├── controllers/
├── services/
├── repositories/
└── dtos/
Why: Feature-based organization makes code easier to navigate and refactor.
3. Separate Command from Library
// ✅ GOOD: Separate main package
cmd/server/main.go # Entry point, wiring
internal/handlers/ # Business logic
// ❌ BAD: Everything in main package
main.go
handlers.go
middleware.go
Why: Keeps main package small and focused on wiring, makes code reusable and testable.
File Naming Conventions
1. Descriptive, Lowercase, Underscore-Separated
// ✅ GOOD
cv_pages.go
cv_htmx.go
cv_helpers.go
cv_pages_test.go
// ❌ BAD
cvPages.go // camelCase
cv-pages.go // hyphen (not idiomatic)
cvpages.go // too short, unclear
2. Test Files Mirror Source Files
// Source files
cv_pages.go
cv_htmx.go
// Test files
cv_pages_test.go
cv_htmx_test.go
3. Group Related Functionality
// Related to CV handler
cv.go // Constructor, shared state
cv_pages.go // Page handlers
cv_htmx.go // HTMX handlers
cv_pdf.go // PDF export
cv_helpers.go // Helper functions
// Shared types and errors
types.go // Request/response types
errors.go // Error handling
Package Naming
1. Short, Concise, Lowercase
// ✅ GOOD
package handlers
package middleware
package pdf
// ❌ BAD
package cvHandlers // Don't repeat package name
package cv_handlers // No underscore
package HTTPHandlers // No capitals
2. No common, util, base
// ❌ BAD: Generic names
package util
package common
package helpers
// ✅ GOOD: Descriptive names
package validation
package templates
package pdf
3. Singular Names
// ✅ GOOD
package handler // Even if multiple handlers
package model
// ❌ BAD
package handlers // Plural (exception: when package name would conflict)
package models
Code Organization Within Files
1. Logical Ordering
// ✅ GOOD: Logical flow
package handlers
// 1. Imports
import (
"fmt"
"net/http"
)
// 2. Package-level constants/variables
const MaxRetries = 3
// 3. Types
type CVHandler struct {
tmpl *templates.Manager
}
// 4. Constructor
func NewCVHandler(tmpl *templates.Manager) *CVHandler {
return &CVHandler{tmpl: tmpl}
}
// 5. Public methods (alphabetical or logical order)
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
// ...
}
// 6. Private methods (alphabetical or logical order)
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
// ...
}
// 7. Helper functions
func validateLanguage(lang string) error {
// ...
}
2. Group Related Code
// ✅ GOOD: Related functions grouped
func (h *CVHandler) ToggleCVLength(w http.ResponseWriter, r *http.Request) {
// ...
}
func (h *CVHandler) ToggleCVIcons(w http.ResponseWriter, r *http.Request) {
// ...
}
func (h *CVHandler) ToggleCVTheme(w http.ResponseWriter, r *http.Request) {
// ...
}
3. Separate Public and Private
// Public API
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request)
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request)
// Private helpers (lowercase)
func (h *CVHandler) prepareTemplateData(lang string)
func (h *CVHandler) handleError(w http.ResponseWriter, r *http.Request, err error)
Import Organization
1. Group Imports
import (
// 1. Standard library
"context"
"fmt"
"net/http"
// 2. External packages
"github.com/chromedp/chromedp"
// 3. Internal packages
"project/internal/middleware"
"project/internal/models/cv"
)
2. Use Blank Imports Sparingly
// ✅ GOOD: Document why
import (
_ "github.com/lib/pq" // PostgreSQL driver
)
// ❌ BAD: No comment
import (
_ "github.com/lib/pq"
)
Avoiding Circular Dependencies
Problem
// package a
import "project/internal/b"
// package b
import "project/internal/a"
// Compilation error: import cycle
Solution 1: Extract Interface
// package common
type ServiceA interface {
DoA()
}
// package a
import "project/internal/common"
func NewA(b common.ServiceB) *A {
// Use interface
}
// package b
// No import of package a
Solution 2: Create Third Package
// Before: a ↔ b (circular)
// After: a → shared ← b
//
// shared/ contains types used by both
When to Split a File
Signs a File is Too Large
- More than 500 lines
- Multiple unrelated responsibilities
- Difficult to navigate
- Many scroll actions to find code
How to Split
// Before: cv.go (1000+ lines)
// - Constructor
// - Page handlers
// - HTMX handlers
// - PDF handler
// - Helper functions
// After: Split by responsibility
cv.go // Constructor, shared state
cv_pages.go // Page handlers (Home, CVContent)
cv_htmx.go // HTMX handlers (4 toggles)
cv_pdf.go // PDF export
cv_helpers.go // Helper functions
Documentation
1. Package Documentation
// Package handlers provides HTTP request handlers for the CV website.
//
// Handlers are organized by resource:
// - CVHandler: CV page rendering and HTMX updates
// - HealthHandler: Health check endpoint
//
// All handlers follow the http.HandlerFunc signature and use
// dependency injection for testability.
package handlers
2. Exported Function Documentation
// NewCVHandler creates a new CV handler with the given dependencies.
//
// The template manager is used for rendering HTML responses.
// The host parameter is used to construct absolute URLs for SEO.
//
// Example:
//
// tmpl, _ := templates.NewManager(config)
// handler := handlers.NewCVHandler(tmpl, "example.com")
func NewCVHandler(tmpl *templates.Manager, host string) *CVHandler {
return &CVHandler{
tmpl: tmpl,
host: host,
}
}
3. Complex Logic Documentation
// prepareTemplateData loads and processes all data needed for template rendering.
//
// The process involves:
// 1. Load CV and UI data from JSON files
// 2. Calculate experience durations
// 3. Split skills into columns for display
// 4. Build template data map with SEO metadata
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
// ...
}
Best Practices Checklist
Package Structure
- Use
internal/for private code - Group by feature, not layer
- Separate
cmd/from library code - Avoid circular dependencies
File Organization
- Descriptive, lowercase names
- Test files mirror source files
- Related functionality grouped
- Files < 500 lines
Code Structure
- Logical ordering (imports → types → constructor → methods)
- Public before private
- Related code grouped
- Proper documentation
Naming
- Short package names (no
util,common) - Clear, descriptive file names
- Consistent naming across project
- No redundant prefixes
Anti-Patterns
❌ Flat Structure
// BAD: Everything in root
main.go
handlers.go
middleware.go
models.go
utils.go
helpers.go
❌ Over-Nesting
// BAD: Too many levels
internal/
└── domain/
└── services/
└── cv/
└── handlers/
└── http/
└── v1/
└── endpoints/
└── cv.go
❌ God Packages
// BAD: One package does everything
package app
// 5000 lines of code handling everything
Real-World Example
This project follows these principles:
✅ Clear package boundaries
✅ Feature-based organization (handlers, models, middleware)
✅ Test files mirror source files
✅ No circular dependencies
✅ Appropriate use of internal/
✅ Well-documented public API
✅ Logical file naming and organization