The main.go file has grown to 1171 lines with 22 initialization helper functions. This makes the codebase difficult to understand for new contributors and violates the single responsibility principle. The file mixes CLI argument parsing, configuration validation, infrastructure setup, service initialization, HTTP routing, and graceful shutdown logic.
This refactoring will:
- Extract initialization logic into a new
internal/bootstrap/package - Reduce
main.goto ~100 lines (CLI handling only) - Improve testability by isolating initialization functions
- Follow existing project conventions (constructor injection, no interfaces)
- Maintain the current initialization order and error handling behavior
Create 12 new files organized by concern:
internal/bootstrap/
├── bootstrap.go # Application struct + Run() orchestrator (150 lines)
├── config.go # Configuration validation (80 lines)
├── database.go # Database initialization (20 lines)
├── cache.go # Metrics and cache setup (80 lines)
├── redis.go # Redis client initialization (50 lines)
├── providers.go # Auth/token provider setup (60 lines)
├── oauth.go # OAuth provider configuration (120 lines)
├── services.go # Business services initialization (50 lines)
├── handlers.go # HTTP handlers initialization (50 lines)
├── router.go # Router and middleware setup (200 lines)
├── ratelimit.go # Rate limiting configuration (80 lines)
├── server.go # HTTP server and graceful shutdown (250 lines)
└── bootstrap_test.go # Unit tests
The Application struct will hold all initialized components and expose a single Run() method:
type Application struct {
Config *config.Config
DB *store.Store
MetricsRecorder metrics.MetricsRecorder
MetricsCache cache.Cache
MetricsCacheCloser func() error
RateLimitRedisClient *redis.Client
// Services
AuditService *services.AuditService
UserService *services.UserService
DeviceService *services.DeviceService
TokenService *services.TokenService
ClientService *services.ClientService
AuthorizationService *services.AuthorizationService
// HTTP
HandlerSet handlerSet
Router *gin.Engine
Server *http.Server
TemplatesFS embed.FS
}
func Run(cfg *config.Config, templatesFS embed.FS) errorThe Run() function will orchestrate initialization in 5 phases:
- validateAllConfiguration() — Config validation
- initializeInfrastructure() — DB → Metrics → Cache → Redis
- initializeBusinessLayer() — Audit → Auth Providers → Token Providers → Services
- initializeHTTPLayer() — OAuth → Handlers → Router → Server
- startWithGracefulShutdown() — Graceful shutdown manager
Each phase returns errors that bubble up to main() via bootstrap.Run().
After refactoring, main.go will contain only:
- Flag parsing
printUsage()functionrunServer()simplified to:bootstrap.Run(config.Load(), templatesFS)- Swagger annotations (required in main package)
Target: ~100–150 lines total
config.go
validateConfiguration()→validateAllConfiguration()validateAuthConfig()validateTokenProviderConfig()
database.go
initializeDatabase()
cache.go
initializeMetrics()initializeMetricsCache()
redis.go
initializeRateLimitRedisClient()
providers.go
initializeHTTPAPIAuthProvider()initializeHTTPTokenProvider()
oauth.go
initializeOAuthProviders()createOAuthHTTPClient()logOAuthProvidersStatus()getProviderNames()
services.go
initializeServices()
handlers.go
initializeHandlers()handlerSetstruct (moved from main.go)
router.go
setupRouter()setupSessionMiddleware()serveStaticFiles()setupMetricsEndpoint()setupAllRoutes()setupOAuthRoutes()createHealthCheckHandler()setupGinMode()logServerStartup()ginModeMapandginModeLogMessagevariables
ratelimit.go
setupRateLimiting()createRateLimiters()rateLimitMiddlewaresstruct (moved from main.go)
server.go
createHTTPServer()startServerWithGracefulShutdown()- All 8
add*Job()functions (graceful shutdown) updateGaugeMetricsWithCache()errorLoggerstruct andnewErrorLogger()(moved from main.go)gaugeErrorLoggervariable
- Create
internal/bootstrap/directory - Create
bootstrap.gowithApplicationstruct skeleton - Migrate functions one file at a time (start with config.go, database.go)
- Keep duplicate functions in main.go during migration
- Test after each file:
make test && make build
- Implement
Applicationmethods inbootstrap.go - Update
main.goto callbootstrap.Run(cfg, templatesFS) - Test full server startup:
./bin/authgate server
- Remove all migrated functions from
main.go - Run full test suite:
make test - Run linter:
make lint - Format code:
make fmt
- Write unit tests in
bootstrap_test.go - Update
CLAUDE.mdwith bootstrap package documentation
Unit tests (internal/bootstrap/bootstrap_test.go):
- Config validation with various configurations
- Metrics initialization (enabled/disabled)
- OAuth provider initialization
- Rate limiter setup
- Health check handler
Integration test (optional, in main_test.go):
- Full server startup with in-memory database
- Verify graceful shutdown
| File | Role |
|---|---|
main.go |
Source of all functions; final refactor target |
internal/config/config.go |
Reference for validation patterns |
internal/services/user.go |
Reference for constructor injection |
CLAUDE.md |
Update with bootstrap architecture notes |
- Readability: main.go reduced from 1171 → ~100 lines
- Maintainability: Related functions grouped by concern
- Testability: Each initialization function can be unit tested
- Clarity: Application struct makes dependencies explicit
- Convention: Follows existing patterns (no interfaces, constructor injection)
- Safety: Zero API changes, internal refactor only
After implementation:
- Build succeeds:
make build - Tests pass:
make test - Linting passes:
make lint - Server starts successfully:
./bin/authgate server - All endpoints functional (manual smoke test)
- Configuration validation works as before
- Graceful shutdown completes cleanly