feat: backend service
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
// Package postgres opens the backend's primary Postgres pool and applies the
|
||||
// embedded migrations.
|
||||
//
|
||||
// The package is a thin wrapper around galaxy/postgres: it adapts the backend
|
||||
// configuration shape to galaxy/postgres.Config, plumbs the OpenTelemetry
|
||||
// tracer and meter providers from the telemetry runtime, instruments the
|
||||
// pool, and verifies connectivity with a bounded Ping.
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"galaxy/backend/internal/config"
|
||||
"galaxy/backend/internal/postgres/migrations"
|
||||
"galaxy/backend/internal/telemetry"
|
||||
|
||||
pgshared "galaxy/postgres"
|
||||
)
|
||||
|
||||
// connMaxLifetime caps the lifetime of an individual pooled connection. Kept
|
||||
// in sync with galaxy/postgres.DefaultConnMaxLifetime so behaviour matches
|
||||
// the helper's defaults until backend has reason to deviate.
|
||||
const connMaxLifetime = 30 * time.Minute
|
||||
|
||||
// Open constructs the primary Postgres pool, instruments it, pings it, and
|
||||
// returns the *sql.DB. Closing the database is the caller's responsibility.
|
||||
func Open(ctx context.Context, cfg config.PostgresConfig, runtime *telemetry.Runtime) (*sql.DB, error) {
|
||||
pgCfg := pgshared.Config{
|
||||
PrimaryDSN: cfg.DSN,
|
||||
OperationTimeout: cfg.OperationTimeout,
|
||||
MaxOpenConns: cfg.MaxConns,
|
||||
MaxIdleConns: cfg.MinConns,
|
||||
ConnMaxLifetime: connMaxLifetime,
|
||||
}
|
||||
|
||||
db, err := pgshared.OpenPrimary(
|
||||
ctx,
|
||||
pgCfg,
|
||||
pgshared.WithTracerProvider(runtime.TracerProvider()),
|
||||
pgshared.WithMeterProvider(runtime.MeterProvider()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open backend postgres pool: %w", err)
|
||||
}
|
||||
|
||||
if _, err := pgshared.InstrumentDBStats(
|
||||
db,
|
||||
pgshared.WithTracerProvider(runtime.TracerProvider()),
|
||||
pgshared.WithMeterProvider(runtime.MeterProvider()),
|
||||
); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, fmt.Errorf("instrument backend postgres pool: %w", err)
|
||||
}
|
||||
|
||||
if err := pgshared.Ping(ctx, db, cfg.OperationTimeout); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, fmt.Errorf("ping backend postgres pool: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// schemaName is the Postgres schema owned by the backend service. Every
|
||||
// backend table lives here.
|
||||
const schemaName = "backend"
|
||||
|
||||
// ApplyMigrations runs every pending Up migration embedded in the backend
|
||||
// binary against db. The schema is created upfront so goose's bookkeeping
|
||||
// table (`goose_db_version`, scoped to the DSN `search_path = backend`)
|
||||
// has somewhere to land before the first migration runs; migration
|
||||
// `00001_init.sql` re-asserts the schema with `IF NOT EXISTS`, so the
|
||||
// double-create is idempotent.
|
||||
func ApplyMigrations(ctx context.Context, db *sql.DB) error {
|
||||
if _, err := db.ExecContext(ctx, "CREATE SCHEMA IF NOT EXISTS "+schemaName); err != nil {
|
||||
return fmt.Errorf("ensure backend schema: %w", err)
|
||||
}
|
||||
if err := pgshared.RunMigrations(ctx, db, migrations.Migrations(), "."); err != nil {
|
||||
return fmt.Errorf("apply backend migrations: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user