// File: /home/meninjar/goprint/service-general/main.go package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "service/internal/auth" antrol "service/internal/bpjs/antrol/reference" aplicareBed "service/internal/bpjs/aplicare/bed" apotekDpho "service/internal/bpjs/apotek/reference/dpho" apotekPoli "service/internal/bpjs/apotek/reference/poli" "service/internal/bpjs/vclaim/peserta" "service/internal/bpjs/vclaim/sep" "service/internal/infrastructure/cache" "service/internal/infrastructure/config" "service/internal/infrastructure/database" grpcHandlers "service/internal/infrastructure/transport/grpc/handlers" httpServer "service/internal/infrastructure/transport/http/servers" bpjsFactory "service/internal/interfaces/bpjs" "service/internal/interfaces/minio" satuSehatFactory "service/internal/interfaces/satusehat" satuSehatAuth "service/internal/satusehat/reference/auth" satuSehatKfa "service/internal/satusehat/reference/kfa" satuSehatLocation "service/internal/satusehat/reference/location" satuSehatOrganization "service/internal/satusehat/reference/organization" satuSehatPatient "service/internal/satusehat/reference/patient" satuSehatPractitioner "service/internal/satusehat/reference/practitioner" satuSehatCondition "service/internal/satusehat/usecase/condition" satuSehatEncounter "service/internal/satusehat/usecase/encounter" satuSehatProcedure "service/internal/satusehat/usecase/procedure" "service/pkg/logger" imagingStudyWorker "service/internal/worker/satusehat/imagingstudy" // 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 grpcServers "service/internal/infrastructure/transport/grpc/servers" "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) // --- [NEW] Inisialisasi Modul CQRS Baru (Memanfaatkan DB Service) --- // Contoh ketika mengimplementasikan entity "Province" atau "Person": // provinceCmdRepo := province.NewCommandRepository(dbService, "default") // provinceQueryRepo := province.NewQueryRepository(dbService, "default") // provinceSvc := province.NewService(provinceCmdRepo, provinceQueryRepo) // --- [NEW] Inisialisasi Factory BPJS --- bpjsServices := bpjsFactory.NewBPJSFactory(cfg.Bpjs) // 1. Modul VClaim (Lama) vclaimServiceEngine := bpjsServices.VClaim() sepRepo := sep.NewRepository(vclaimServiceEngine) sepService := sep.NewService(sepRepo) pesertaRepo := peserta.NewRepository(vclaimServiceEngine) pesertaService := peserta.NewService(pesertaRepo) // 2. Modul Antrean RS (Baru) antreanClient := bpjsServices.AntreanRS() antrolRepo := antrol.NewRepository(antreanClient) antrolService := antrol.NewService(antrolRepo) // 3. Modul Aplicares (Bed Ketersediaan Tempat Tidur) aplicareClient := bpjsServices.Aplicare() bedRepo := aplicareBed.NewRepository(aplicareClient) bedService := aplicareBed.NewService(bedRepo) // 4. Modul Apotek (Referensi DPHO dan Poli) apotekClient := bpjsServices.Apotek() dphoRepo := apotekDpho.NewRepository(apotekClient) dphoService := apotekDpho.NewService(dphoRepo) poliApotekRepo := apotekPoli.NewRepository(apotekClient) poliApotekService := apotekPoli.NewService(poliApotekRepo) // --- [NEW] Inisialisasi Modul Satu Sehat --- satusehatFact := satuSehatFactory.NewSatuSehatFactory(cfg.SatuSehat) satusehatClient := satusehatFact.Client() satuSehatAuthSvc := satuSehatAuth.NewService(satusehatClient, cacheManager) satuSehatPatientRepo := satuSehatPatient.NewRepository(satusehatClient) satuSehatPatientSvc := satuSehatPatient.NewService(satuSehatPatientRepo) satuSehatPractitionerRepo := satuSehatPractitioner.NewRepository(satusehatClient) satuSehatPractitionerSvc := satuSehatPractitioner.NewService(satuSehatPractitionerRepo) satuSehatOrganizationRepo := satuSehatOrganization.NewRepository(satusehatClient) satuSehatOrganizationSvc := satuSehatOrganization.NewService(satuSehatOrganizationRepo) satuSehatLocationRepo := satuSehatLocation.NewRepository(satusehatClient) satuSehatLocationSvc := satuSehatLocation.NewService(satuSehatLocationRepo) satuSehatKfaRepo := satuSehatKfa.NewRepository(satusehatClient) satuSehatKfaSvc := satuSehatKfa.NewService(satuSehatKfaRepo) satuSehatEncounterRepo := satuSehatEncounter.NewRepository(satusehatClient, dbService) satuSehatEncounterSvc := satuSehatEncounter.NewService(satuSehatEncounterRepo) satuSehatProcedureRepo := satuSehatProcedure.NewRepository(satusehatClient) satuSehatProcedureSvc := satuSehatProcedure.NewService(satuSehatProcedureRepo) satuSehatConditionRepo := satuSehatCondition.NewRepository(satusehatClient) satuSehatConditionSvc := satuSehatCondition.NewService(satuSehatConditionRepo) // 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 --- if cfg.SatuSehat.Enabled { imagingStudyWorkerCfg := imagingStudyWorker.Config{ DBManager: dbService, InternalBaseURL: fmt.Sprintf("http://localhost:%d%s", cfg.Server.REST.Port, cfg.Swagger.BasePath), // otomatis: http://localhost:8080/api/v1 InternalToken: "", // Isi token di sini jika endpoint internal Usecase Anda membutuhkan Bearer Token OrganizationID: cfg.SatuSehat.OrgID, } isWorker := imagingStudyWorker.NewWorker(imagingStudyWorkerCfg) g.Go(func() error { isWorker.Run(ctx) return nil }) } 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, }, VClaim: &httpServer.VClaimServices{ Sep: sepService, Peserta: pesertaService, }, Antrol: &httpServer.AntrolServices{ Reference: antrolService, }, Aplicare: &httpServer.AplicaresServices{ Bed: bedService, }, Apotek: &httpServer.ApotekServices{ DPHO: dphoService, Poli: poliApotekService, }, SatuSehat: &httpServer.SatuSehatServices{ Auth: satuSehatAuthSvc, Patient: satuSehatPatientSvc, Practitioner: satuSehatPractitionerSvc, Organization: satuSehatOrganizationSvc, Location: satuSehatLocationSvc, KFA: satuSehatKfaSvc, Encounter: satuSehatEncounterSvc, Procedure: satuSehatProcedureSvc, Condition: satuSehatConditionSvc, // TODO: Inisialisasi service sisanya di sini saat repo & service usecase lainnya sudah Anda buat, contoh: // AllergyIntolerance: allergySvc, // CarePlan: careplanSvc, // ClinicalImpression: clinicalImpressionSvc, // Composition: compositionSvc, // DiagnosticReport: diagnosticReportSvc, // EpisodeOfCare: episodeOfCareSvc, // ImagingStudy: imagingStudySvc, // Immunization: immunizationSvc, // Medication: medicationSvc, // MedicationDispense: medicationDispenseSvc, // MedicationRequest: medicationRequestSvc, // MedicationStatement: medicationStatementSvc, // Observation: observationSvc, // QuestionnaireResponse: questionnaireResponseSvc, // ServiceRequest: serviceRequestSvc, // Specimen: specimenSvc, }, } 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)") } // --- [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") } }