package worker import ( "context" "fmt" "os" "sync" "time" "service/internal/infrastructure/config" "service/internal/infrastructure/database" "service/internal/interfaces/satusehat" "service/internal/worker/satusehat/medication" "service/pkg/logger" ) // Manager mengelola siklus hidup semua background workers type Manager struct { cfg *config.Config db database.Service satusehatClient satusehat.SatuSehatClient // Auth state for all workers accessToken string refreshToken string tokenMutex sync.RWMutex } // 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...") // Lakukan login awal untuk mendapatkan token bagi semua worker if err := m.login(ctx); err != nil { logger.Default().Fatal("Initial login for workers failed, stopping.", logger.ErrorField(err)) return } var wg sync.WaitGroup // Tentukan Organization ID (Bisa dari Environment Variable atau Config Struct) orgID := os.Getenv("SATUSEHAT_ORG_ID") if orgID == "" { logger.Default().Warn("SATUSEHAT_ORG_ID is not set in environment variables") } internalBaseURL := m.cfg.SatuSehat.InternalFHIRServerURL // 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: "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, OrganizationID: orgID, // }).Run(c) // }, // }, // { // name: "Condition", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // condition.NewWorker(...).Run(c) // }, // }, { name: "Medication (Master)", delay: 0, // Master data berjalan seketika run: func(c context.Context) { medication.NewWorker(medication.Config{ DBManager: m.db, InternalBaseURL: internalBaseURL, OrganizationID: orgID, }, m).Run(c) }, }, // { // name: "Observation", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // observation.NewWorker(...).Run(c) // }, // }, // { // name: "Procedure", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // procedure.NewWorker(...).Run(c) // }, // }, // { // name: "AllergyIntolerance", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // allergyintolerance.NewWorker(...).Run(c) // }, // }, // { // name: "ClinicalImpression", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // clinicalimpression.NewWorker(...).Run(c) // }, // }, // { // name: "Immunization", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // immunization.NewWorker(...).Run(c) // }, // }, // { // name: "ServiceRequest (Default)", // delay: 0, // run: func(c context.Context) { // servicerequest.NewDefaultWorker(servicerequest.Config{ // DBManager: m.db, // InternalBaseURL: internalBaseURL, // OrganizationID: orgID, // }, m).Run(c) // }, // }, // { // name: "ServiceRequest (Radiology)", // delay: 2 * time.Second, // run: func(c context.Context) { // servicerequest.NewRadiologyWorker(servicerequest.Config{ // DBManager: m.db, // InternalBaseURL: internalBaseURL, // OrganizationID: orgID, // }, m).Run(c) // }, // }, // { // name: "Specimen", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // specimen.NewWorker(...).Run(c) // }, // }, // { // name: "DiagnosticReport", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // diagnosticreport.NewWorker(...).Run(c) // }, // }, // { // name: "ImagingStudy", // delay: 2 * time.Second, // run: func(c context.Context) { // // The Manager 'm' now acts as the TokenManager // imagingstudy.NewWorker(imagingstudy.Config{ // DBManager: m.db, InternalBaseURL: internalBaseURL, OrganizationID: orgID, // }, m).Run(c) // }, // }, // { // name: "Composition", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // composition.NewWorker(...).Run(c) // }, // }, // { // name: "CarePlan", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // careplan.NewWorker(...).Run(c) // }, // }, // { // name: "EpisodeOfCare", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // episodeofcare.NewWorker(...).Run(c) // }, // }, // { // name: "MedicationRequest", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // medicationrequest.NewWorker(...).Run(c) // }, // }, // { // name: "MedicationDispense", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // medicationdispense.NewWorker(...).Run(c) // }, // }, // { // name: "MedicationStatement", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // medicationstatement.NewWorker(...).Run(c) // }, // }, // { // name: "QuestionnaireResponse", // delay: 2 * time.Second, // run: func(c context.Context) { // // TODO: Refactor this worker // // questionnaireresponse.NewWorker(...).Run(c) // }, // }, } // Eksekusi semua background worker secara tertib dengan delay bertingkat go func() { baseDelay := 10 * time.Second // Jeda 10 detik antar tiap worker untuk cegah spike di awal for i, job := range jobs { // Hitung delay bertingkat: (index) * baseDelay staggeredDelay := time.Duration(i) * baseDelay // Jika job memiliki delay statis yang lebih besar, utamakan nilai tersebut if job.delay > staggeredDelay { staggeredDelay = job.delay } wg.Add(1) j := job d := staggeredDelay go func() { defer wg.Done() if d > 0 { select { case <-ctx.Done(): return case <-time.After(d): } } 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") }() }