Files
go-simple-api/internal/database/mysql.go
T
2026-07-16 10:13:46 +03:30

67 lines
2.6 KiB
Go

// Package database owns everything related to talking to MySQL: opening
// the connection pool and running startup migrations. Nothing outside this
// package (and the repositories in internal/models) should import the
// MySQL driver directly - that keeps the database technology an
// implementation detail hidden behind a plain *sql.DB.
package database
import (
"context"
"database/sql"
"fmt"
"time"
// Blank import: we never call anything from this package by name.
// Importing it purely for its side effect - its init() function
// registers "mysql" as a driver name with database/sql, which is what
// lets sql.Open("mysql", ...) below work at all.
_ "github.com/go-sql-driver/mysql"
"git.hamidsoltani.com/hamid/go-simple-api/internal/config"
)
// NewMySQL opens a connection pool to MySQL, configures reasonable pool
// limits, and verifies connectivity with a real ping before returning -
// so a bad connection string / unreachable database fails loudly at
// startup instead of silently on the first real query.
func NewMySQL(ctx context.Context, cfg config.Config) (*sql.DB, error) {
// DSN = Data Source Name. Format: user:password@tcp(host:port)/dbname?options
// parseTime=true tells the driver to convert MySQL DATETIME/TIMESTAMP
// columns into Go time.Time values automatically.
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?parseTime=true",
cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName,
)
// sql.Open does NOT connect yet - it just validates the DSN and
// returns a *sql.DB, which represents a pool of connections managed
// for you, not a single live connection.
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("open mysql: %w", err)
}
// Pool tuning:
// MaxOpenConns - hard ceiling on simultaneous connections, protects
// the database from being overwhelmed by this app.
// MaxIdleConns - how many connections to keep warm/ready even when
// idle, so we don't pay reconnect cost constantly.
// ConnMaxLifetime - force connections to be recycled periodically,
// useful behind load balancers or if MySQL itself
// closes long-lived idle connections.
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
// Give the ping a hard deadline instead of letting it hang forever if
// the database host is unreachable.
pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := db.PingContext(pingCtx); err != nil {
return nil, fmt.Errorf("ping mysql: %w", err)
}
return db, nil
}