// File: /home/meninjar/goprint/service-general/main.go package main import ( "context" "net/http" "os" "os/signal" "syscall" "service/internal/auth" "service/internal/infrastructure/cache" "service/internal/infrastructure/config" "service/internal/infrastructure/database" grpcServers "service/internal/infrastructure/transport/grpc/servers" httpServer "service/internal/infrastructure/transport/http/servers" "service/internal/interfaces/minio" "service/internal/interfaces/satusehat" "service/internal/worker" "service/pkg/logger" // roleComponent "service/internal/master/role/component" roleMaster "service/internal/master/role/master" rolePages "service/internal/master/role/pages" rolePermission "service/internal/master/role/permission" _ "service/docs/swagger" // Wajib: import swagger docs yang di-generate oleh swag CLI "golang.org/x/sync/errgroup" ) // @title GoPrint Service General API // @version 1.0.0 // @description REST API for Service General including Multi-DB, BPJS, and SatuSehat integrations. // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.email support@example.com // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @BasePath /api/v1 // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description Type "Bearer" followed by a space and JWT token. func main() { // 1. Load Config cfg := config.LoadConfig() // 2. Init Logger dengan konfigurasi dari config loggerConfig := logger.Config{ Level: cfg.Logger.Level, Format: cfg.Logger.Format, Output: "both", // Output ke console dan ke dalam folder daily logs ServiceName: "service-general", EnableCaller: cfg.Server.Mode != "production", // Aktifkan caller info di non-production Environment: cfg.Server.Mode, // Gunakan mode server untuk environment } logger.Init(loggerConfig) // Sekarang bisa menggunakan logger custom logger.Default().Info("Application starting", logger.String("service", "service-general"), logger.String("environment", cfg.Server.Mode), logger.String("log_level", cfg.Logger.Level), ) // Di dalam main.go // Asumsikan di config.yaml Anda ada database bernama "postgres" dan "mysql_legacy" contoh pemanggilan dan prosesing 2 tabel berbeda dalam satu repository // reportRepo := report.NewRepository(dbService, "postgres", "mysql_legacy") // reportService := report.NewService(reportRepo) // reportService ini tinggal di-pass ke dalam Handler untuk dijadikan endpoint REST API. // Add debugging to see what config was loaded logger.Default().Debug("Config loaded", logger.Any("database_count", len(cfg.Databases)), logger.Any("cache_enabled", cfg.Cache.Enabled), logger.Any("server_mode", cfg.Server.Mode), ) if err := cfg.Validate(); err != nil { logger.Default().Fatal("Config validation failed", logger.ErrorField(err)) } // --- Inisialisasi Minio Object Storage --- minio.Connect() logger.Default().Info("Minio Object Storage client initialized") // 3. Init Database Manager (Mendukung CQRS & Multi-DB) dbService := database.New(cfg) defer dbService.Close() // Menjalankan Automigrasi // if err := dbService.Migrate(); err != nil { // logger.Default().Warn("Database migration completed with issues", logger.ErrorField(err)) // } // Get Master/Primary GORM DB Connection untuk modul lama // Gunakan nama koneksi "postgres" atau "default" sesuai config.yaml Anda gormDB, err := dbService.GetGormDB("default") if err != nil { logger.Default().Fatal("Failed to get primary gorm database", logger.ErrorField(err)) } // --- [NEW] Inisialisasi Message Broker (Kafka / RabbitMQ) untuk Event-Driven Microservice --- // Jika bertindak sebagai gateway atau microservice independen, inisialisasi Kafka di sini // kafkaProducer := broker.NewKafkaProducer(cfg.Kafka.Brokers) // defer kafkaProducer.Close() // logger.Default().Info("✅ Kafka Producer initialized") // 4. Init Cache using factory pattern cacheFactory := cache.NewFactory(cfg.Cache) cacheManager, err := cacheFactory.CreateManager() if err != nil { logger.Default().Fatal("Failed to initialize cache manager", logger.ErrorField(err)) } defer cacheManager.Close() if cfg.Cache.Enabled { logger.Default().Info("Cache initialized", logger.String("host", cfg.Cache.Redis.Host), logger.Int("port", cfg.Cache.Redis.Port)) } else { logger.Default().Warn("Cache disabled, using NoOp cache") } // 5. Init Layers (CQRS Repositories & Services) // Modul Auth authCmdRepo := auth.NewCommandRepository(dbService, "default") authQueryRepo := auth.NewQueryRepository(dbService, "default") authSvc := auth.NewService(authCmdRepo, authQueryRepo, cacheManager, cfg) // Inisialisasi Role Pages, Permission, dan Component (Menggunakan pola CQRS baru) rolePagesCmdRepo := rolePages.NewCommandRepository(dbService, "default") rolePagesQueryRepo := rolePages.NewQueryRepository(dbService, "default") rolePagesService := rolePages.NewService(rolePagesCmdRepo, rolePagesQueryRepo, cacheManager) rolePermissionCmdRepo := rolePermission.NewCommandRepository(dbService, "default") rolePermissionQueryRepo := rolePermission.NewQueryRepository(dbService, "default") rolePermissionService := rolePermission.NewService(rolePermissionCmdRepo, rolePermissionQueryRepo, rolePagesQueryRepo, cacheManager) // Modul Role Master (Hasil Generator) roleMasterCmdRepo := roleMaster.NewCommandRepository(dbService, "default") roleMasterQueryRepo := roleMaster.NewQueryRepository(dbService, "default") roleMasterService := roleMaster.NewService(roleMasterCmdRepo, roleMasterQueryRepo, cacheManager) // roleComponentCmdRepo := roleComponent.NewCommandRepository(dbService, "postgres") // roleComponentQueryRepo := roleComponent.NewQueryRepository(dbService, "postgres") // roleComponentService := roleComponent.NewService(roleComponentCmdRepo, roleComponentQueryRepo) // 6. Server Orchestration (Dual Protocol & Background Workers) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Gunakan errgroup untuk mengelola lifecycle server g, ctx := errgroup.WithContext(ctx) // --- [NEW] Start Background Workers --- serverCount := 0 // --- [A] Start REST Server --- if cfg.Server.REST.Enabled { serverCount++ // Menggunakan ServiceRegistry agar HTTP Server lebih rapi dan scalable registry := &httpServer.ServiceRegistry{ Config: cfg, DBManager: dbService, PrimaryDB: gormDB, CacheManager: cacheManager, AuthService: authSvc, Master: &httpServer.MasterServices{ RolePages: rolePagesService, RolePermission: rolePermissionService, RoleMaster: roleMasterService, }, } restSrv := httpServer.NewHTTPServer(&cfg.Server.REST, registry) g.Go(func() error { logger.Default().Info("REST API running", logger.Int("port", cfg.Server.REST.Port)) if err := restSrv.Start(&cfg.Server); err != nil && err != http.ErrServerClosed { logger.Default().Error("REST Server error", logger.ErrorField(err)) return err } return nil }) } // --- [B] Start gRPC Server --- if cfg.Server.GRPC.Enabled { serverCount++ // 1. Buat gRPC handlers // permissionHandler := grpcHandlers.NewPermissionHandler(rolePermissionService) // 2. Buat dan isi gRPC service registry grpcRegistry := &grpcServers.ServiceRegistry{ // PermissionHandler: permissionHandler, } // 3. Buat gRPC server dengan registry yang sudah diisi grpcSrv := grpcServers.NewGRPCServer( &cfg.Server.GRPC, grpcRegistry, ) g.Go(func() error { logger.Default().Info("gRPC Server running", logger.Int("port", cfg.Server.GRPC.Port)) if err := grpcSrv.Start(); err != nil { logger.Default().Error("gRPC Server error", logger.ErrorField(err)) return err } return nil }) g.Go(func() error { <-ctx.Done() logger.Default().Info("Gracefully stopping gRPC Server...") grpcSrv.Stop() return nil }) } if serverCount == 0 { logger.Default().Fatal("No server enabled (REST and gRPC are both disabled in config)") } // --- [NEW] Start Background Workers --- if cfg.SatuSehat.Enabled { // Inisialisasi Worker Manager yang akan mengelola semua background workers. // satusehatClient bisa di-pass jika diperlukan oleh worker lain. var satusehatClient satusehat.SatuSehatClient // Ganti nil dengan inisialisasi client jika sudah ada workerManager := worker.NewManager(cfg, dbService, satusehatClient) g.Go(func() error { workerManager.Start(ctx) return nil }) } // --- [C] Graceful Shutdown Listener --- g.Go(func() error { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) select { case <-quit: logger.Default().Warn("Signal received, shutting down servers...") cancel() case <-ctx.Done(): } return nil }) logger.Default().Info("Application started successfully") if err := g.Wait(); err != nil { logger.Default().Error("Server shutdown with error", logger.ErrorField(err)) } else { logger.Default().Info("Server shutdown successful") } }