All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m33s
6.2 KiB
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 wiringinternal/http- Gin Server provider,router.gofor fx mappinginternal/http/api/...- Domain handlers structured by route hierarchy (e.g.,api/health,api/protected/users)internal/service- business logic + transaction boundariesinternal/db/<context>- SQLC-generated code by bounded contextinternal/store- shared DB/Tx helpersinternal/auth- JWT validation + role guardsinternal/config- env configuration loadingmigrations/- Goose SQL migration filesapi/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
DBTXinterfaces. - No transaction logic in handlers.
Dependency Injection (fx)
- Use
fx.Provideto register constructors (config, logger, db pool, server, handlers). - Use
fx.Invokeinmain.goto orchestrate route mapping and application startup. internal/http/router.goacts as the central route mapping orchestrator injected viafx.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 aliveGET /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.goand 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.FSfrom a dedicatedmigrationspackage to isolate them frominternaldb logic.
SQLC
- Generate one package per bounded context (similar to Spring repository modules)
- Keep SQL in context directories (
query.sql,models.sqlstyle) - Service layer composes multiple repositories when needed
Work in Progress Snapshot
sqlc.yamlis now configured for PostgreSQL with schema frommigrations/*.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/v5emit_interface: true(generatedQuerierinterface)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 theembed.FSstrategy. - fx DI fully implemented. All dependencies are provided via
fx.Provideconstructors:config.Provide→*Configlogger.NewFromConfig→*zap.SugaredLoggerdb.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 aliveGET /api/health/ready- DB ping succeeds
- Next Planned: Auth handler (
/api/auth/login,/api/auth/register), JWT middleware, service layer.
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
-
JWT signing:
- HS256 shared secret (simple)
- RS256 keypair (better long-term)
-
Token model:
- Access token only
- Access + refresh token
-
Initial roles:
- USER / ADMIN
- USER / MODERATOR / ADMIN
-
OpenAPI workflow:
- Contract-first (spec first)
- Code-first annotations
-
CORS policy:
- Allowed frontend origins in dev/prod
-
Schema strategy:
- Single schema (
public) confirmation
- Single schema (
-
Initial bounded contexts:
- e.g.
auth,users,rooms(or your domain names)
- e.g.
Iteration Notes
- This file is intentionally a working draft.
- We will refine decisions and turn this into a concrete implementation checklist.