System Overview
Vega AI is a Go web application for AI-powered job search and application tracking, built with privacy and multi-tenancy in mind. The system follows a clean, domain-driven layered architecture with clear separation of concerns.
Architecture Diagram
Architecture Patterns
Layered Architecture
Vega AI follows a domain-driven layered architecture with clear boundaries:
Handlers HTTP request/response handling, template rendering, and input validation
Services Business logic, AI integration, and domain operations
Repositories Data access layer with interface-based design for testability
Database SQLite with transaction support and multi-tenant isolation
Request Flow
// Example: Creating a job
Handler → Service → Repository → Database
↓ ↓ ↓
Validate Business Data Access
Input Logic + Caching
Key Principles:
Handlers → HTTP layer, no business logic
Services → Core business rules, orchestration
Repositories → Database operations only
Database → Data persistence with constraints
Application Entry Points
The application follows a clean initialization flow:
Main Entry Point
Location: cmd/vega/main.go
func main () {
cfg := config . NewSettings ()
app := vega . New ( cfg )
if err := app . Run (); err != nil {
log . Fatalf ( "Failed to start: %v " , err )
}
app . WaitForShutdown ()
}
Application Core
The App struct serves as the central application container:
type App struct {
config config . Settings
router * gin . Engine
db * sql . DB
cache cache . Cache
server * http . Server
done chan os . Signal
renderer * render . HTMLRenderer
authLimiter * localmiddleware . RateLimiter
}
Initialization Flow
New()
Creates app instance with router, CORS, and template loading
Setup()
Initializes logging, database, cache, migrations, and admin user
SetupRoutes()
Configures all routes, middleware, and service dependencies
Run()
Starts HTTP server with graceful shutdown support
WaitForShutdown()
Blocks until SIGINT/SIGTERM, then cleans up resources
Service Initialization
Services are initialized in dependency order in internal/vega/routes.go:26:
func SetupRoutes ( a * App ) {
// 1. AI Service (graceful degradation if unavailable)
aiService , err := ai . Setup ( & a . config )
if err != nil {
log . Warn (). Err ( err ). Msg ( "AI features disabled" )
aiService = nil
}
// 2. Authentication
authHandler , authService := auth . SetupAuthWithService ( a . db , & a . config )
// 3. Job Service with cache
jobService := job . SetupService ( a . db , & a . config , a . cache )
// 4. Quota Service
jobRepo := job . SetupJobRepository ( a . db , a . cache )
quotaAdapter := quota . NewJobRepositoryAdapter ( jobRepo )
unifiedQuotaService := quota . NewUnifiedService ( a . db , quotaAdapter , a . config . IsCloudMode )
// 5. Settings and other handlers
settingsHandler , _ := settings . SetupWithService ( & a . config , a . db , aiService , unifiedQuotaService , authService )
// ...
}
Key Design Patterns
1. Dependency Injection
Services receive dependencies through constructors:
func NewJobHandler ( service * JobService , config * config . Settings ) ( * JobHandler , error ) {
return & JobHandler {
service : service ,
config : config ,
}, nil
}
2. Interface-Based Design
Repositories use interfaces for testability:
type JobRepository interface {
Create ( ctx context . Context , userID int , job * models . Job ) error
GetByID ( ctx context . Context , userID int , id int ) ( * models . Job , error )
// ...
}
3. Graceful Degradation
AI service failure doesn’t crash the app:
aiService , err := ai . Setup ( & a . config )
if err != nil {
log . Warn (). Err ( err ). Msg ( "AI service initialization failed" )
aiService = nil // App continues without AI features
}
4. Mode-Based Configuration
Cloud vs self-hosted behavior differences:
if a . config . IsCloudMode {
// Google OAuth required
a . router . GET ( "/auth/login" , authHandler . GetLoginPage )
} else {
// Username/password authentication
authLimiter := localmiddleware . NewAuthRateLimiter ()
auth . RegisterPublicRoutes ( authGroup , authHandler , authLimiter )
}
5. Middleware Composition
a . router . Use ( localmiddleware . RequestID ())
a . router . Use ( globalErrorHandler ( a . renderer ))
a . router . Use ( localmiddleware . RequestTimeout ( 30 * time . Second ))
if a . config . EnableSecurityHeaders {
a . router . Use ( middleware . SecurityHeaders ())
}
if a . config . EnableCSRF {
a . router . Use ( middleware . CSRF ( & a . config ))
}
6. Clean Shutdown
Proper resource cleanup on termination:
func ( a * App ) Shutdown ( ctx context . Context ) error {
var err error
if a . server != nil {
err = a . server . Shutdown ( ctx )
}
if a . db != nil {
dbErr := a . db . Close ()
if err == nil { err = dbErr }
}
if a . cache != nil {
cacheErr := a . cache . Close ()
if err == nil { err = cacheErr }
}
if a . authLimiter != nil {
a . authLimiter . Stop ()
}
return err
}
AI Integration Architecture
Vega AI uses a pluggable AI architecture with Google Gemini:
Job Matcher Analyzes job-profile compatibility using AI
Letter Generator Creates personalized cover letters
CV Generator Generates professional CVs from profiles
CV Parser Extracts profile data from uploaded CVs
AI Service Structure
type AIService struct {
JobMatcher * services . JobMatcherService
CoverLetterGenerator * services . CoverLetterGeneratorService
CVParser * services . CVParserService
CVGenerator * services . CVGeneratorService
}
AI Processing Flow
Data Retrieval
User profile and job data retrieved from database
Prompt Generation
Structured prompts generated using templates
AI Processing
Gemini API processes the request with model selection
Validation
Results validated and sanitized
Storage
Match results stored in database for history
Quota System
The quota system manages usage limits for AI-powered features in cloud mode:
AI Analysis 10 analyses/month for new jobs
Job Search Unlimited tracking for all users
Re-analysis Unlimited for existing jobs
// Quota check before AI analysis
result , err := quotaService . CanAnalyzeJob ( ctx , userID , jobID )
if ! result . Allowed {
// Handle quota exceeded
}
// Record usage after successful analysis
err = quotaService . RecordJobAnalysis ( ctx , userID , jobID )
Code Organization Best Practices
Domain Separation
Each package owns its domain with clear boundaries
Interface Boundaries
Repository interfaces defined in domain packages
Dependency Injection
Services receive dependencies through constructors
Error Handling
Wrapped errors with context for debugging
Privacy First
No PII in logs or errors (GDPR-compliant)
This architecture provides clear separation of concerns, testability through dependency injection, and flexibility for different deployment modes (cloud vs self-hosted).