tercul-backend/internal/app/application_builder.go
Damir Mukimov 4957117cb6 Initial commit: Tercul Go project with comprehensive architecture
- Core Go application with GraphQL API using gqlgen
- Comprehensive data models for literary works, authors, translations
- Repository pattern with caching layer
- Authentication and authorization system
- Linguistics analysis capabilities with multiple adapters
- Vector search integration with Weaviate
- Docker containerization support
- Python data migration and analysis scripts
- Clean architecture with proper separation of concerns
- Production-ready configuration and middleware
- Proper .gitignore excluding vendor/, database files, and build artifacts
2025-08-13 07:42:32 +02:00

312 lines
8.7 KiB
Go

package app
import (
"tercul/cache"
"tercul/config"
"tercul/db"
"tercul/linguistics"
"tercul/logger"
"tercul/repositories"
"tercul/services"
"time"
"github.com/hibiken/asynq"
"github.com/weaviate/weaviate-go-client/v5/weaviate"
"gorm.io/gorm"
)
// ApplicationBuilder handles the initialization of all application components
type ApplicationBuilder struct {
dbConn *gorm.DB
redisCache cache.Cache
weaviateClient *weaviate.Client
asynqClient *asynq.Client
repositories *RepositoryContainer
services *ServiceContainer
linguistics *linguistics.LinguisticsFactory
}
// RepositoryContainer holds all repository instances
type RepositoryContainer struct {
WorkRepository repositories.WorkRepository
UserRepository repositories.UserRepository
AuthorRepository repositories.AuthorRepository
TranslationRepository repositories.TranslationRepository
CommentRepository repositories.CommentRepository
LikeRepository repositories.LikeRepository
BookmarkRepository repositories.BookmarkRepository
CollectionRepository repositories.CollectionRepository
TagRepository repositories.TagRepository
CategoryRepository repositories.CategoryRepository
CopyrightRepository repositories.CopyrightRepository
}
// ServiceContainer holds all service instances
type ServiceContainer struct {
WorkService services.WorkService
CopyrightService services.CopyrightService
LocalizationService services.LocalizationService
AuthService services.AuthService
}
// NewApplicationBuilder creates a new ApplicationBuilder
func NewApplicationBuilder() *ApplicationBuilder {
return &ApplicationBuilder{}
}
// BuildDatabase initializes the database connection
func (b *ApplicationBuilder) BuildDatabase() error {
logger.LogInfo("Initializing database connection")
dbConn, err := db.InitDB()
if err != nil {
logger.LogFatal("Failed to initialize database - application cannot start without database connection",
logger.F("error", err),
logger.F("host", config.Cfg.DBHost),
logger.F("database", config.Cfg.DBName))
return err
}
b.dbConn = dbConn
logger.LogInfo("Database initialized successfully",
logger.F("host", config.Cfg.DBHost),
logger.F("database", config.Cfg.DBName))
return nil
}
// BuildCache initializes the Redis cache
func (b *ApplicationBuilder) BuildCache() error {
logger.LogInfo("Initializing Redis cache")
redisCache, err := cache.NewDefaultRedisCache()
if err != nil {
logger.LogWarn("Failed to initialize Redis cache, continuing without caching - performance may be degraded",
logger.F("error", err),
logger.F("redisAddr", config.Cfg.RedisAddr))
} else {
b.redisCache = redisCache
logger.LogInfo("Redis cache initialized successfully",
logger.F("redisAddr", config.Cfg.RedisAddr))
}
return nil
}
// BuildWeaviate initializes the Weaviate client
func (b *ApplicationBuilder) BuildWeaviate() error {
logger.LogInfo("Connecting to Weaviate",
logger.F("host", config.Cfg.WeaviateHost),
logger.F("scheme", config.Cfg.WeaviateScheme))
wClient, err := weaviate.NewClient(weaviate.Config{
Scheme: config.Cfg.WeaviateScheme,
Host: config.Cfg.WeaviateHost,
})
if err != nil {
logger.LogFatal("Failed to create Weaviate client - vector search capabilities will not be available",
logger.F("error", err),
logger.F("host", config.Cfg.WeaviateHost),
logger.F("scheme", config.Cfg.WeaviateScheme))
return err
}
b.weaviateClient = wClient
logger.LogInfo("Weaviate client initialized successfully")
return nil
}
// BuildBackgroundJobs initializes Asynq for background job processing
func (b *ApplicationBuilder) BuildBackgroundJobs() error {
logger.LogInfo("Setting up background job processing",
logger.F("redisAddr", config.Cfg.RedisAddr))
redisOpt := asynq.RedisClientOpt{
Addr: config.Cfg.RedisAddr,
Password: config.Cfg.RedisPassword,
DB: config.Cfg.RedisDB,
}
asynqClient := asynq.NewClient(redisOpt)
b.asynqClient = asynqClient
logger.LogInfo("Background job client initialized successfully")
return nil
}
// BuildRepositories initializes all repositories
func (b *ApplicationBuilder) BuildRepositories() error {
logger.LogInfo("Initializing repositories")
// Initialize base repositories
baseWorkRepo := repositories.NewWorkRepository(b.dbConn)
userRepo := repositories.NewUserRepository(b.dbConn)
authorRepo := repositories.NewAuthorRepository(b.dbConn)
translationRepo := repositories.NewTranslationRepository(b.dbConn)
commentRepo := repositories.NewCommentRepository(b.dbConn)
likeRepo := repositories.NewLikeRepository(b.dbConn)
bookmarkRepo := repositories.NewBookmarkRepository(b.dbConn)
collectionRepo := repositories.NewCollectionRepository(b.dbConn)
tagRepo := repositories.NewTagRepository(b.dbConn)
categoryRepo := repositories.NewCategoryRepository(b.dbConn)
copyrightRepo := repositories.NewCopyrightRepository(b.dbConn)
// Wrap work repository with cache if available
var workRepo repositories.WorkRepository
if b.redisCache != nil {
workRepo = repositories.NewCachedWorkRepository(
baseWorkRepo,
b.redisCache,
nil,
30*time.Minute, // Cache work data for 30 minutes
)
logger.LogInfo("Using cached work repository")
} else {
workRepo = baseWorkRepo
logger.LogInfo("Using non-cached work repository")
}
b.repositories = &RepositoryContainer{
WorkRepository: workRepo,
UserRepository: userRepo,
AuthorRepository: authorRepo,
TranslationRepository: translationRepo,
CommentRepository: commentRepo,
LikeRepository: likeRepo,
BookmarkRepository: bookmarkRepo,
CollectionRepository: collectionRepo,
TagRepository: tagRepo,
CategoryRepository: categoryRepo,
CopyrightRepository: copyrightRepo,
}
logger.LogInfo("Repositories initialized successfully")
return nil
}
// BuildLinguistics initializes the linguistics components
func (b *ApplicationBuilder) BuildLinguistics() error {
logger.LogInfo("Initializing linguistic analyzer")
b.linguistics = linguistics.NewLinguisticsFactory(
b.dbConn,
b.redisCache,
4, // Default concurrency
true, // Cache enabled
)
logger.LogInfo("Linguistics components initialized successfully")
return nil
}
// BuildServices initializes all services
func (b *ApplicationBuilder) BuildServices() error {
logger.LogInfo("Initializing service layer")
workService := services.NewWorkService(b.repositories.WorkRepository, b.linguistics.GetAnalyzer())
copyrightService := services.NewCopyrightService(b.repositories.CopyrightRepository)
localizationService := services.NewLocalizationService(b.repositories.TranslationRepository)
authService := services.NewAuthService(b.repositories.UserRepository)
b.services = &ServiceContainer{
WorkService: workService,
CopyrightService: copyrightService,
LocalizationService: localizationService,
AuthService: authService,
}
logger.LogInfo("Services initialized successfully")
return nil
}
// Build initializes all components in the correct order
func (b *ApplicationBuilder) Build() error {
// Build components in dependency order
if err := b.BuildDatabase(); err != nil {
return err
}
if err := b.BuildCache(); err != nil {
return err
}
if err := b.BuildWeaviate(); err != nil {
return err
}
if err := b.BuildBackgroundJobs(); err != nil {
return err
}
if err := b.BuildRepositories(); err != nil {
return err
}
if err := b.BuildLinguistics(); err != nil {
return err
}
if err := b.BuildServices(); err != nil {
return err
}
logger.LogInfo("Application builder completed successfully")
return nil
}
// GetDatabase returns the database connection
func (b *ApplicationBuilder) GetDatabase() *gorm.DB {
return b.dbConn
}
// GetCache returns the cache instance
func (b *ApplicationBuilder) GetCache() cache.Cache {
return b.redisCache
}
// GetWeaviateClient returns the Weaviate client
func (b *ApplicationBuilder) GetWeaviateClient() *weaviate.Client {
return b.weaviateClient
}
// GetAsynqClient returns the Asynq client
func (b *ApplicationBuilder) GetAsynqClient() *asynq.Client {
return b.asynqClient
}
// GetRepositories returns the repository container
func (b *ApplicationBuilder) GetRepositories() *RepositoryContainer {
return b.repositories
}
// GetServices returns the service container
func (b *ApplicationBuilder) GetServices() *ServiceContainer {
return b.services
}
// GetLinguistics returns the linguistics factory
func (b *ApplicationBuilder) GetLinguistics() *linguistics.LinguisticsFactory {
return b.linguistics
}
// Close closes all resources
func (b *ApplicationBuilder) Close() error {
if b.asynqClient != nil {
b.asynqClient.Close()
}
if b.dbConn != nil {
sqlDB, err := b.dbConn.DB()
if err == nil {
sqlDB.Close()
}
}
return nil
}