Files
satusehat-worker/internal/worker/worker.go
T
2026-04-26 08:06:57 +00:00

280 lines
7.3 KiB
Go

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")
}()
}