Skip to content

Instantly share code, notes, and snippets.

@appleboy
Created February 21, 2026 13:22
Show Gist options
  • Select an option

  • Save appleboy/75551a9e7643327572df747383df71df to your computer and use it in GitHub Desktop.

Select an option

Save appleboy/75551a9e7643327572df747383df71df to your computer and use it in GitHub Desktop.
claude code plan: Refactor main.go into internal/bootstrap Package

Refactor main.go into internal/bootstrap Package

Context

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.go to ~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

Proposed Solution

1. Create internal/bootstrap/ Package Structure

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

2. Core Design: Application Struct

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) error

3. Initialization Flow

The Run() function will orchestrate initialization in 5 phases:

  1. validateAllConfiguration() — Config validation
  2. initializeInfrastructure() — DB → Metrics → Cache → Redis
  3. initializeBusinessLayer() — Audit → Auth Providers → Token Providers → Services
  4. initializeHTTPLayer() — OAuth → Handlers → Router → Server
  5. startWithGracefulShutdown() — Graceful shutdown manager

Each phase returns errors that bubble up to main() via bootstrap.Run().

4. Updated main.go

After refactoring, main.go will contain only:

  • Flag parsing
  • printUsage() function
  • runServer() simplified to: bootstrap.Run(config.Load(), templatesFS)
  • Swagger annotations (required in main package)

Target: ~100–150 lines total

5. Functions Migrated by File

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()
  • handlerSet struct (moved from main.go)

router.go

  • setupRouter()
  • setupSessionMiddleware()
  • serveStaticFiles()
  • setupMetricsEndpoint()
  • setupAllRoutes()
  • setupOAuthRoutes()
  • createHealthCheckHandler()
  • setupGinMode()
  • logServerStartup()
  • ginModeMap and ginModeLogMessage variables

ratelimit.go

  • setupRateLimiting()
  • createRateLimiters()
  • rateLimitMiddlewares struct (moved from main.go)

server.go

  • createHTTPServer()
  • startServerWithGracefulShutdown()
  • All 8 add*Job() functions (graceful shutdown)
  • updateGaugeMetricsWithCache()
  • errorLogger struct and newErrorLogger() (moved from main.go)
  • gaugeErrorLogger variable

Migration Strategy

Phase 1: Create Foundation (No Breaking Changes)

  1. Create internal/bootstrap/ directory
  2. Create bootstrap.go with Application struct skeleton
  3. Migrate functions one file at a time (start with config.go, database.go)
  4. Keep duplicate functions in main.go during migration
  5. Test after each file: make test && make build

Phase 2: Integration

  1. Implement Application methods in bootstrap.go
  2. Update main.go to call bootstrap.Run(cfg, templatesFS)
  3. Test full server startup: ./bin/authgate server

Phase 3: Cleanup

  1. Remove all migrated functions from main.go
  2. Run full test suite: make test
  3. Run linter: make lint
  4. Format code: make fmt

Phase 4: Testing & Documentation

  1. Write unit tests in bootstrap_test.go
  2. Update CLAUDE.md with bootstrap package documentation

Testing Strategy

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

Critical Files to Modify

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

Benefits

  1. Readability: main.go reduced from 1171 → ~100 lines
  2. Maintainability: Related functions grouped by concern
  3. Testability: Each initialization function can be unit tested
  4. Clarity: Application struct makes dependencies explicit
  5. Convention: Follows existing patterns (no interfaces, constructor injection)
  6. Safety: Zero API changes, internal refactor only

Verification Plan

After implementation:

  1. Build succeeds: make build
  2. Tests pass: make test
  3. Linting passes: make lint
  4. Server starts successfully: ./bin/authgate server
  5. All endpoints functional (manual smoke test)
  6. Configuration validation works as before
  7. Graceful shutdown completes cleanly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment