package worker import ( "context" "fmt" "os" "sync" "time" "service/internal/infrastructure/config" "service/internal/infrastructure/database" "service/internal/interfaces/satusehat" "service/internal/worker/satusehat/allergyintolerance" "service/internal/worker/satusehat/careplan" "service/internal/worker/satusehat/clinicalimpression" "service/internal/worker/satusehat/composition" "service/internal/worker/satusehat/condition" "service/internal/worker/satusehat/diagnosticreport" "service/internal/worker/satusehat/encounter" "service/internal/worker/satusehat/episodeofcare" "service/internal/worker/satusehat/imagingstudy" "service/internal/worker/satusehat/immunization" "service/internal/worker/satusehat/medication" "service/internal/worker/satusehat/medicationdispense" "service/internal/worker/satusehat/medicationrequest" "service/internal/worker/satusehat/medicationstatement" "service/internal/worker/satusehat/observation" "service/internal/worker/satusehat/procedure" "service/internal/worker/satusehat/questionnaireresponse" "service/internal/worker/satusehat/servicerequest" "service/internal/worker/satusehat/specimen" "service/pkg/logger" ) // Manager mengelola siklus hidup semua background workers type Manager struct { cfg *config.Config db database.Service satusehatClient satusehat.SatuSehatClient } // NewManager membuat instance Manager baru func NewManager(cfg *config.Config, db database.Service, satusehatClient satusehat.SatuSehatClient) *Manager { return &Manager{ cfg: cfg, db: db, satusehatClient: satusehatClient, } } // Start menjalankan semua worker terdaftar dalam goroutine terpisah func (m *Manager) Start(ctx context.Context) { logger.Default().Info("Starting background workers...") var wg sync.WaitGroup // Tentukan Organization ID (Bisa dari Environment Variable atau Config Struct) orgID := os.Getenv("SATUSEHAT_ORGANIZATION_ID") if orgID == "" { logger.Default().Warn("SATUSEHAT_ORGANIZATION_ID is not set in environment variables") } internalPort := 8080 if m.cfg != nil && m.cfg.Server.REST.Port != 0 { internalPort = m.cfg.Server.REST.Port } internalBaseURL := fmt.Sprintf("http://localhost:%d/api/v1", internalPort) internalToken := os.Getenv("INTERNAL_API_TOKEN") // Jobs mendefinisikan urutan dan jeda waktu (delay) boot-up setiap worker. // Konfigurasi waktu ini mencegah perebutan resource di awal (thundering herd) // dan memastikan resource inti (seperti Encounter) mendapat "head start" // sebelum worker anak mencoba merujuk ke data tersebut. jobs := []struct { name string delay time.Duration run func(context.Context) }{ { name: "Medication (Master)", delay: 0, // Master data berjalan seketika run: func(c context.Context) { medication.NewWorker(medication.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Encounter", delay: 5 * time.Second, // Memberikan jeda 5 detik agar Encounter mendapat porsi jalan lebih dulu run: func(c context.Context) { encounter.NewWorker(encounter.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Condition", delay: 2 * time.Second, run: func(c context.Context) { condition.NewWorker(condition.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Observation", delay: 2 * time.Second, run: func(c context.Context) { observation.NewWorker(observation.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Procedure", delay: 2 * time.Second, run: func(c context.Context) { procedure.NewWorker(procedure.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "AllergyIntolerance", delay: 2 * time.Second, run: func(c context.Context) { allergyintolerance.NewWorker(allergyintolerance.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "ClinicalImpression", delay: 2 * time.Second, run: func(c context.Context) { clinicalimpression.NewWorker(clinicalimpression.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Immunization", delay: 2 * time.Second, run: func(c context.Context) { immunization.NewWorker(immunization.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "ServiceRequest", delay: 2 * time.Second, run: func(c context.Context) { servicerequest.NewWorker(servicerequest.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Specimen", delay: 2 * time.Second, run: func(c context.Context) { specimen.NewWorker(specimen.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "DiagnosticReport", delay: 2 * time.Second, run: func(c context.Context) { diagnosticreport.NewWorker(diagnosticreport.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "ImagingStudy", delay: 2 * time.Second, run: func(c context.Context) { imagingstudy.NewWorker(imagingstudy.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "Composition", delay: 2 * time.Second, run: func(c context.Context) { composition.NewWorker(composition.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "CarePlan", delay: 2 * time.Second, run: func(c context.Context) { careplan.NewWorker(careplan.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "EpisodeOfCare", delay: 2 * time.Second, run: func(c context.Context) { episodeofcare.NewWorker(episodeofcare.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "MedicationRequest", delay: 2 * time.Second, run: func(c context.Context) { medicationrequest.NewWorker(medicationrequest.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "MedicationDispense", delay: 2 * time.Second, run: func(c context.Context) { medicationdispense.NewWorker(medicationdispense.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "MedicationStatement", delay: 2 * time.Second, run: func(c context.Context) { medicationstatement.NewWorker(medicationstatement.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, { name: "QuestionnaireResponse", delay: 2 * time.Second, run: func(c context.Context) { questionnaireresponse.NewWorker(questionnaireresponse.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID, }).Run(c) }, }, } // Eksekusi semua background worker secara tertib go func() { for _, job := range jobs { // Jeda startup, dapat di-interrupt oleh Context Cancel (Shutdown System) if job.delay > 0 { select { case <-ctx.Done(): return case <-time.After(job.delay): } } wg.Add(1) j := job // copy pointer untuk goroutine loop go func() { defer wg.Done() logger.Default().Info(fmt.Sprintf("[%s WORKER] Started", j.name)) j.run(ctx) logger.Default().Info(fmt.Sprintf("[%s WORKER] Stopped", j.name)) }() } // Waiter untuk Graceful Shutdown keseluruhan wg.Wait() logger.Default().Info("All background workers stopped gracefully") }() }