协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
cloud-development【开发与工程】Develops cloud-native Go applications with context propagation, graceful shutdown, health checks, and configuration management for production.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: cloud-development description: Develops cloud-native Go applications with context propagation, graceful shutdown, health checks, and configuration management for production. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: go role: implementation scope: implementation output-format: code content-types:
Senior cloud engineer building production-grade Go services that start, serve, and stop cleanly in containerized environments. This skill covers HTTP server lifecycle management, context propagation, health checks, signal handling, and configuration for cloud deployment.
SIGINT and SIGTERM, draining connections before exitcontext.Context as first parameterhttp.Server with explicit timeouts (read, write, idle)Use this skill when:
Avoid this skill for:
Design the Server Lifecycle — Define startup, serving, and shutdown phases with explicit lifecycle management. Checkpoint: Server can be started, stopped, and restarted without resource leaks.
Implement Graceful Shutdown — Handle SIGINT/SIGTERM to drain in-flight requests and close connections.
Checkpoint: Shutdown completes within a configured deadline; stuck requests are cancelled.
Add Health and Readiness Probes — Separate liveness (is the process alive?) from readiness (can it serve traffic?). Checkpoint: Liveness checks are fast and never block; readiness depends on dependency health.
Propagate Context Through All Layers — Every function that does I/O or spans a timeout accepts context.Context.
Checkpoint: No goroutine outlives its parent context without explicit detachment.
Configure with Environment Hierarchy — Load defaults → file → env vars → override, validate before starting. Checkpoint: Invalid configuration causes immediate startup failure with a clear error message.
Proper shutdown is critical for zero-downtime deployments and rolling restarts.
// ❌ BAD: server panics on interrupt signal, dropping all in-flight requests
func main() {
http.HandleFunc("/api/data", handleData)
server := &http.Server{
Addr: ":8080",
}
// Signal handler panics — no graceful drain
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
panic("shutting down") // kills everything instantly
}
What's wrong:
// runServer starts the HTTP server and handles graceful shutdown on signals.
// It drains in-flight requests and closes all resources before exiting.
func runServer(ctx context.Context, srv *http.Server, addr string) error {
// Run server in a goroutine so we can wait for shutdown signal
serverDone := make(chan struct{})
go func() {
defer close(serverDone)
// ListenAndServe blocks until the server is shut down
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("server error: %v", err)
}
}()
// Wait for interrupt or shutdown signal
select {
case <-ctx.Done():
log.Println("shutdown signal received")
case sig := <-sigChannel():
log.Printf("received signal %v, initiating shutdown", sig)
}
// Create a deadline for graceful shutdown (drain in-flight requests)
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
return fmt.Errorf("server shutdown: %w", err)
}
<-serverDone
log.Println("server stopped gracefully")
return nil
}
// sigChannel returns a channel that receives SIGINT and SIGTERM.
func sigChannel() chan os.Signal {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
return ch
}
Why this works:
srv.Shutdown() stops accepting new connections and waits for in-flight requestsListenAndServe allows signal-based controlSeparate liveness (is the process alive?) from readiness (can it handle traffic?).
// ❌ BAD: single endpoint checks everything — slow checks kill liveness probes
func handleHealth(w http.ResponseWriter, r *http.Request) {
// Blocking database check in liveness probe
rows, err := db.Query("SELECT 1")
if err != nil {
http.Error(w, "unhealthy", http.StatusServiceUnavailable)
return
}
defer rows.Close()
// Checks cache, message queue, external APIs — too many blocking calls
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
}
What's wrong:
// healthChecker reports whether the process is alive (fast, never blocks).
func healthHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Liveness: just check if we can reach the database at all
// Uses a very short timeout — should never block
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
http.Error(w, "not alive", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "alive")
}
}
// readinessHandler reports whether the service can accept traffic.
// Checks all dependencies that are required for normal operation.
func readinessHandler(db *sql.DB, cache *cache.Client, mq *mq.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
// Check each dependency; if ANY critical dep fails, we're not ready
if err := db.PingContext(ctx); err != nil {
http.Error(w, "not ready: db unavailable", http.StatusServiceUnavailable)
return
}
if !cache.Ping(ctx) {
http.Error(w, "not ready: cache unavailable", http.StatusServiceUnavailable)
return
}
if err := mq.Connected(ctx); err != nil {
http.Error(w, "not ready: mq unavailable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ready")
}
}
Why this works:
Configuration should be explicit, validated, and impossible to misconfigure.
// ❌ BAD: configuration loaded lazily, never validated, defaults hidden
var (
dbHost string
dbPort int
apiKey string
logLevel string
)
func init() {
// init() is called at package load time — no error handling possible
dbHost = os.Getenv("DB_HOST") // "" if not set
dbPort = 5432 // magic number, not documented
apiKey = os.Getenv("API_KEY") // "" if not set — no validation
logLevel = os.Getenv("LOG_LEVEL") // invalid values accepted
}
func main() {
// Bug surfaces hours later when a query fails with empty credentials
runServer()
}
What's wrong:
init() cannot return errors — failures are silent and hard to debugAPI_KEY is accepted, causing runtime failures// Config holds all application configuration. Zero values are invalid —
// all fields must be explicitly set via LoadConfig.
type Config struct {
Server ServerConfig
Database DatabaseConfig
Logging LoggingConfig
API APIConfig
}
// ServerConfig holds HTTP server configuration.
type ServerConfig struct {
Addr string
Port int
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
// DatabaseConfig holds database connection settings.
type DatabaseConfig struct {
Host string
Port int
Name string
User string
Password string
MaxConns int
}
// LoadConfig reads configuration from environment and validates it.
// Returns an error if required fields are missing or invalid.
func LoadConfig() (*Config, error) {
cfg := &Config{}
// Load with explicit defaults
cfg.Server.Addr = envOr("SERVER_ADDR", "0.0.0.0")
cfg.Server.Port = envIntOr("SERVER_PORT", 8080)
cfg.Server.ReadTimeout = envDurationOr("READ_TIMEOUT", 15*time.Second)
cfg.Server.WriteTimeout = envDurationOr("WRITE_TIMEOUT", 30*time.Second)
cfg.Server.IdleTimeout = envDurationOr("IDLE_TIMEOUT", 120*time.Second)
cfg.Database.Host = envOr("DB_HOST", "")
cfg.Database.Port = envIntOr("DB_PORT", 5432)
cfg.Database.Name = envOr("DB_NAME", "")
cfg.Database.User = envOr("DB_USER", "")
cfg.Database.Password = envOr("DB_PASSWORD", "")
cfg.Database.MaxConns = envIntOr("DB_MAX_CONNS", 25)
cfg.Logging.Level = envOr("LOG_LEVEL", "info")
cfg.API.Key = envOr("API_KEY", "")
// Validate required fields — fail fast at startup
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("configuration validation: %w", err)
}
return cfg, nil
}
// Validate checks that all required configuration is present and valid.
func (c *Config) Validate() error {
var errs []error
if c.Database.Host == "" {
errs = append(errs, errors.New("DB_HOST is required"))
}
if c.Database.Name == "" {
errs = append(errs, errors.New("DB_NAME is required"))
}
if c.Database.User == "" {
errs = append(errs, errors.New("DB_USER is required"))
}
if c.Database.Password == "" {
errs = append(errs, errors.New("DB_PASSWORD is required"))
}
if c.API.Key == "" {
errs = append(errs, errors.New("API_KEY is required"))
}
if c.Server.Port < 1 || c.Server.Port > 65535 {
errs = append(errs, fmt.Errorf("SERVER_PORT must be 1-65535, got %d", c.Server.Port))
}
if len(errs) > 0 {
return multiError(errs)
}
return nil
}
// envOr returns the environment variable value or the default.
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// multiError collects multiple validation errors into one.
type multiError []error
func (e multiError) Error() string {
var msgs []string
for _, err := range e {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
Why this works:
LoadConfig returns an error — startup fails immediately with a clear messageValidate() checks all required fields in one passLoadConfig can be unit tested with explicit env var manipulationsrv.Shutdown(ctx) — never call srv.Close() directlyReadTimeout, WriteTimeout, IdleTimeout)context.WithTimeout for all database and external API calls within handlerscontext.Context through all function boundaries that perform I/Ohttp.ListenAndServe without a timeout — it can block foreverhttp.DefaultServeMux in production — always use explicit routerspanic() for error handling — use structured error propagation*http.Request or http.ResponseWriter in global statesrv.Close() — it doesn't drain in-flight requests, use srv.Shutdown() instead| Skill | Purpose |
|---|---|
best-practices | Go idioms, naming, and error handling conventions |
web-applications | HTTP handlers, routing, middleware, and REST API design |
modular-design | Clean architecture and dependency injection for services |
deployment-patterns | Multi-stage Docker builds and binary optimization |