rhythm-backend/BACKEND_BLUEPRINT.md
Dmitri 4565a728a5
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m38s
dependecy injection uber to the win
2026-04-20 21:28:36 +02:00

184 lines
6.1 KiB
Markdown

# 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** have been wired into `cmd/api/main.go`, utilizing the `embed.FS` strategy and logging via Zap adapter.
- DB pool is successfully wired in `cmd/api`.
- Environment-aware Zap logger is configured (development vs production).
- **fx DI wiring** is being introduced to replace manual dependency injection in `main.go`. HTTP layer will use domain handlers structured by route hierarchy (`internal/http/api/...`).
- **Next Planned:** Implement fx providers for Gin server and DB pool, build `internal/http/server.go`, `internal/http/router.go`, and initial domain handlers (`health`, `auth`).
---
## Testing Approach (Beginner-Friendly)
### Phase 1 (Recommended Start)
- 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.