rhythm-backend/BACKEND_BLUEPRINT.md
Dmitri 730f411494
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m33s
logger
2026-04-20 21:47:28 +02:00

6.2 KiB

Go Backend Blueprint (Iteration Draft)

IDEAS

  • instead of elastic search use ts vector and ts query to allow easy iseeu searching for smooth experience (pg vector?)

Chosen Stack

  • Framework: Gin
  • Dependency Injection: Uber fx (runtime DI, Spring-like autowiring)
  • Migrations: Goose (SQL-only)
  • DB Access: SQLC (no ORM), package per bounded context
  • PostgreSQL Driver/Pool: pgx/v5 + pgxpool
  • Logging: Uber Zap (structured logs)
  • Health Probes: liveness/readiness endpoints
  • API Docs: OpenAPI (for frontend TypeScript type generation)
  • Deployment: Docker Compose on self-hosted hardware

Architecture Direction

Layering

  • cmd/api - application entrypoint and fx DI wiring
  • internal/http - Gin Server provider, router.go for fx mapping
  • internal/http/api/... - Domain handlers structured by route hierarchy (e.g., api/health, api/protected/users)
  • internal/service - business logic + transaction boundaries
  • internal/db/<context> - SQLC-generated code by bounded context
  • internal/store - shared DB/Tx helpers
  • internal/auth - JWT validation + role guards
  • internal/config - env configuration loading
  • migrations/ - Goose SQL migration files
  • api/openapi/ - OpenAPI spec + generated artifacts

Transaction Strategy

  • Handlers stay thin.
  • Service layer owns DB transactions.
  • SQLC queries are called with either pool or tx using DBTX interfaces.
  • No transaction logic in handlers.

Dependency Injection (fx)

  • Use fx.Provide to register constructors (config, logger, db pool, server, handlers).
  • Use fx.Invoke in main.go to orchestrate route mapping and application startup.
  • internal/http/router.go acts as the central route mapping orchestrator injected via fx.Invoke.
  • Each domain handler (internal/http/api/*/handler.go) receives its dependencies via constructor injection.
  • Lifecycle hooks (fx.Lifecycle) are used for graceful startup/shutdown of the HTTP server and DB pool.

API and Runtime

API Shape

  • REST JSON API
  • Possible future WebSocket support for interactive features
  • Suggested versioning: /api/v1

Health Endpoints

  • GET /api/health/live - process is alive
  • GET /api/health/ready - DB ping succeeds (and optionally migration version check)

Logging

  • Zap JSON logs
  • Uber Zap logger will be initialized in cmd/api/main.go and injected into services/middleware (no global loggers).
  • Logs will be written directly to a file from the application, with log rotation implemented via lumberjack.
  • Correlation/request ID in middleware
  • Structured error logging from middleware and service boundaries

Database and Migrations

Goose

  • SQL-only migrations
  • Keep up/down migration scripts
  • Run on startup in non-prod optional, required in CI/CD/deploy step
  • Migrations are securely bundled into the binary using Go's embed.FS from a dedicated migrations package to isolate them from internal db logic.

SQLC

  • Generate one package per bounded context (similar to Spring repository modules)
  • Keep SQL in context directories (query.sql, models.sql style)
  • Service layer composes multiple repositories when needed

Work in Progress Snapshot

  • sqlc.yaml is now configured for PostgreSQL with schema from migrations/*.sql
  • First bounded context added: internal/db/users
  • Current users SQLC artifacts generated:
    • db.go, models.go, queries.sql.go, querier.go
  • Current SQLC generation options in use:
    • sql_package: pgx/v5
    • emit_interface: true (generated Querier interface)
    • emit_json_tags: false (can be revisited if API structs are returned directly)
  • Initial queries implemented for users: GetUser, CreateUser, DeleteUser
  • Goose startup migrations wired into db.ProvidePool, utilizing the embed.FS strategy.
  • fx DI fully implemented. All dependencies are provided via fx.Provide constructors:
    • config.Provide*Config
    • logger.NewFromConfig*zap.SugaredLogger
    • db.ProvidePool*pgxpool.Pool (lifecycle hooks for graceful shutdown)
    • http.NewServer*gin.Engine (lifecycle hooks for startup/shutdown)
    • health.NewHandler*health.Handler
  • HTTP route hierarchy via internal/http/router.go (GlueRoutes):
    • GET /api/health/live - process is alive
    • GET /api/health/ready - DB ping succeeds
  • Next Planned: Auth handler (/api/auth/login, /api/auth/register), JWT middleware, service layer.

Testing Approach (Beginner-Friendly)

  • Unit tests for pure service logic (no DB)
  • Integration tests for SQLC repositories with real Postgres via Docker

DB Interface Testing (via SQLC Querier)

  • Treat generated SQLC interfaces (e.g. usersdb.Querier) as service dependencies
  • For service unit tests, provide a fake/mock implementation of Querier
  • Focus unit tests on business rules, branching, and error mapping (not SQL behavior)
  • Keep SQL correctness in integration tests against real Postgres
  • This split gives fast unit tests plus high-confidence DB integration coverage

Phase 2

  • HTTP handler tests with httptest
  • Auth middleware tests

Phase 3

  • Minimal end-to-end happy path tests

OpenAPI + Frontend Type Generation

  • Keep spec in repo at api/openapi/openapi.yaml
  • Generate frontend TypeScript types from OpenAPI (e.g. openapi-typescript)
  • Optionally serve Swagger UI from backend

Deployment

  • Docker Compose for app + postgres
  • Healthcheck in compose should target readiness endpoint
  • Env-based configuration (.env, .env.example)

Pending Decisions

  1. JWT signing:

    • HS256 shared secret (simple)
    • RS256 keypair (better long-term)
  2. Token model:

    • Access token only
    • Access + refresh token
  3. Initial roles:

    • USER / ADMIN
    • USER / MODERATOR / ADMIN
  4. OpenAPI workflow:

    • Contract-first (spec first)
    • Code-first annotations
  5. CORS policy:

    • Allowed frontend origins in dev/prod
  6. Schema strategy:

    • Single schema (public) confirmation
  7. Initial bounded contexts:

    • e.g. auth, users, rooms (or your domain names)

Iteration Notes

  • This file is intentionally a working draft.
  • We will refine decisions and turn this into a concrete implementation checklist.