219 lines
8.9 KiB
Markdown
219 lines
8.9 KiB
Markdown
# 🛡️ Quality Management Document (QMD)
|
||
|
||
**Project:** GoPrint Service General (`worker-satusehat`)
|
||
**Versi:** 1.0
|
||
**Tanggal:** 2026-05-13
|
||
|
||
Dokumen ini merangkum **standar mutu, pola tata-kelola kode, dan kontrol resiko** yang berlaku pada codebase. Setiap item disertai titik verifikasi (file/fungsi) supaya bisa diaudit ulang.
|
||
|
||
---
|
||
|
||
## 1. Standar Arsitektur
|
||
|
||
### 1.1 Clean Architecture + CQRS
|
||
|
||
Setiap modul bisnis (`auth`, `master/role/*`, `master/kfa`, `worker/satusehat/*`) **WAJIB** terdiri minimal dari:
|
||
|
||
- `entity.go` — definisi domain (GORM struct).
|
||
- `dto.go` — request/response & filter.
|
||
- `repository.go` — terpisah `CommandRepository` (GORM, primary) dan `QueryRepository` (sqlx, read replica).
|
||
- `service.go` — orkestrasi, validasi, integrasi cache.
|
||
- `mapper.go` — DTO ↔ Entity (anti-leak GORM tag).
|
||
|
||
> Generator boilerplate tersedia: lihat [scripts/](scripts/) dan `tools/generate.go`. Wajib digunakan untuk modul baru supaya struktur konsisten.
|
||
|
||
### 1.2 Pembagian Tanggung Jawab Worker
|
||
|
||
Setiap worker SatuSehat di `internal/worker/satusehat/<resource>/` menggunakan kontrak yang seragam:
|
||
|
||
- `Config{ DBManager, InternalBaseURL, OrganizationID }`
|
||
- `TokenManager` interface (`GetAccessToken`, `ForceRefreshAndGetToken`) — di-implement oleh `worker.Manager`.
|
||
- `Run(ctx context.Context)` — long-running loop dengan select-context untuk *graceful shutdown*.
|
||
- Tracker per modul (`last_*_id.txt` / `last_*_time.txt`) — *to be migrated to DB*.
|
||
|
||
---
|
||
|
||
## 2. Observability & Logging
|
||
|
||
### 2.1 Logger (`pkg/logger`)
|
||
|
||
- Wrapper **Logrus** dengan format JSON otomatis di mode `production`, text-formatted di `development`.
|
||
- **`localTimeHook`** memaksa timestamp = `Asia/Jakarta` (WIB). Tidak boleh ada log dengan timezone lain.
|
||
- Rotasi harian: `logs/YYYY/MM/YYYY-MM-DD.log` (writer ditutup & dibuka ulang per hari).
|
||
- Helper field: `logger.String/Int/Int64/Bool/Any/ErrorField` untuk konsistensi key.
|
||
- `caller` & `function_name` aktif otomatis ketika `Environment != "production"`.
|
||
|
||
### 2.2 Metrics
|
||
|
||
- `prometheus/client_golang` ter-import. Belum ada handler `/metrics` yang terpasang di routes — **TODO** sebelum production multi-instance.
|
||
|
||
### 2.3 Tracing
|
||
|
||
- Field `request_id` (UUID) wajib di-inject melalui middleware HTTP. Untuk worker, gunakan ID dokumen yang sedang diproses (`idxpesanobat`, `kfa_code`) sebagai correlation ID.
|
||
|
||
---
|
||
|
||
## 3. Database Resilience
|
||
|
||
### 3.1 Connection Pool
|
||
|
||
Setiap entry `databases.<name>` di `config.yaml` wajib mendefinisikan:
|
||
|
||
```yaml
|
||
max_open_conns: 25
|
||
max_idle_conns: 25
|
||
conn_max_lifetime: 5m
|
||
```
|
||
|
||
Jika `max_open_conns` tidak diatur, infrastructure menerapkan formula default `(CPU_Cores * 2) + 1`.
|
||
|
||
### 3.2 Multi-DB & Read Replica
|
||
|
||
- Database Manager mendukung **PostgreSQL, MySQL, SQL Server, SQLite, MongoDB** dalam satu service.
|
||
- `CommandRepository` selalu menulis ke primary; `QueryRepository` membaca dari replica (round-robin) jika dikonfigurasi.
|
||
- **SLOW QUERY THRESHOLD: 200 ms** (GORM logger adapter); query di atas ambang ini dilog warn level dengan SQL + duration.
|
||
|
||
### 3.3 Query Builder (`pkg/utils/query`)
|
||
|
||
- Whitelist kolom + dialect-aware (Postgres, MySQL, MSSQL).
|
||
- Pencegahan SQL injection wajib via parameter binding — *no string concat di repository*.
|
||
- Tests: [sql_builder_test.go](pkg/utils/query/sql_builder_test.go), [dialects_test.go](pkg/utils/query/dialects_test.go).
|
||
|
||
---
|
||
|
||
## 4. Keamanan
|
||
|
||
### 4.1 Secret Management
|
||
|
||
- Semua kredensial dilarang hardcoded. Akses via env var atau Viper dari `.env` / `.env.prod`.
|
||
- **Anomali yang diketahui** ([patient/migrator.go:23-26](internal/worker/patient/migrator.go#L23-L26)): `defaultUsername "adm_pendaftaran"` & `defaultPassword "1234"`. **WAJIB dihapus** atau dijadikan env-only sebelum file ini diaktifkan.
|
||
|
||
### 4.2 Auth
|
||
|
||
- Provider pluggable: `jwt`, `keycloak`, `static`, `hybrid` (config `auth.type` + `auth.fallback_to`).
|
||
- JWT signing key di env; Keycloak JWKS URL di env.
|
||
- Rate limit per IP dikelola Redis (`security.rate_limit.requests_per_minute`, default 60).
|
||
|
||
### 4.3 Crypto BPJS
|
||
|
||
- HMAC-SHA256 untuk signature (`X-cons-id` + `&` + `X-timestamp`).
|
||
- Dekripsi AES-256-CBC (`bpjs/crypto.go`):
|
||
- key = `SHA256(ConsID + SecretKey + Timestamp)`
|
||
- IV = 16 byte pertama dari key
|
||
- Padding PKCS7
|
||
- Auto-decompression `LZString → Gzip → plaintext` ([decompression.go](internal/interfaces/bpjs/decompression.go)).
|
||
|
||
### 4.4 SatuSehat OAuth2
|
||
|
||
- Client-Credentials flow; token cached in-memory dengan **safety margin 60 detik** sebelum exp.
|
||
- Pada 401, request retry otomatis setelah `ForceRefreshAndGetToken` ([satusehat/client.go](internal/interfaces/satusehat/client.go)).
|
||
- Mutex `tokenMutex` di [worker.Manager](internal/worker/worker.go#L25-L27) mencegah race antar worker.
|
||
|
||
### 4.5 RBAC Caching
|
||
|
||
- Kunci cache permission menggunakan SHA-256 dari (role_id, page_id, parameter query) — bukan plaintext.
|
||
- *Invalidate by prefix* otomatis pada `Create/Update/Delete`.
|
||
|
||
---
|
||
|
||
## 5. Resiliensi Worker
|
||
|
||
### 5.1 Initial Login Retry
|
||
|
||
- 6× percobaan dengan jeda 5 detik. Jika semua gagal → `Fatal` (service mati). Lihat [worker.go:42-55](internal/worker/worker.go#L42-L55).
|
||
|
||
### 5.2 Rate Limit Handling (per worker)
|
||
|
||
| Worker | Strategi |
|
||
| :--- | :--- |
|
||
| KFA Master Puller | 30s list API, 60s detail API, 500ms antar item, idle 6h saat halaman kosong |
|
||
| Medication / MedicationRequest / MedicationDispense | `rateLimitSleep = 60s` + retry dokumen yang sama (tidak skip ID) |
|
||
| ImagingStudy / ServiceRequest | Pola serupa, time-based tracker |
|
||
|
||
> Deteksi rate-limit fleksibel: cek status code 429 *atau* substring respons (`"too many requests"`, `"rate limit"`, `"limit exceeded"`, `"limit pengiriman"`, `"quota exceeded"`).
|
||
|
||
### 5.3 Token Recovery
|
||
|
||
- Tiap worker yang memanggil API internal `service-satusehat` *wajib*:
|
||
1. Sertakan `Authorization: Bearer <token>` dari `TokenManager.GetAccessToken()`.
|
||
2. Jika respons 401 → `ForceRefreshAndGetToken` lalu retry sekali.
|
||
3. Jika refresh gagal → fallback re-login ([auth.go:139-149](internal/worker/auth.go#L139-L149)).
|
||
|
||
### 5.4 Graceful Shutdown
|
||
|
||
- Root `context` dengan `errgroup` (`golang.org/x/sync/errgroup`).
|
||
- Sinyal `SIGINT`/`SIGTERM` → `cancel()` → semua goroutine select-context exit → `g.Wait()`.
|
||
- Tidak boleh ada `for { ... }` tanpa `select { case <-ctx.Done(): return; default: ... }` di kode worker.
|
||
|
||
### 5.5 Staggered Startup
|
||
|
||
- `baseDelay = 10s` antar tiap worker untuk mencegah *thundering herd* terhadap API gov't ([worker.go:268-303](internal/worker/worker.go#L268-L303)).
|
||
|
||
---
|
||
|
||
## 6. State Management Worker
|
||
|
||
### 6.1 Tracker File (Praktik Saat Ini)
|
||
|
||
- Lokasi: `internal/worker/satusehat/<resource>/last_*.txt` & `internal/master/kfa/kfa_tracker.txt`.
|
||
- Format: integer ID (medication, KFA, imagingstudy) atau ISO timestamp (servicerequest radiology).
|
||
- Mapping FHIR ID disimpan harian: `mapperdata/YYYY-MM/YYYY-MM-DD.txt` berformat `idxpesanobat|fhir_id`.
|
||
|
||
> **Batasan kritikal:** tidak aman untuk multi-replica. **Wajib** dipindah ke DB / Redis untuk deployment Kubernetes (lihat Devplan Phase 2).
|
||
|
||
### 6.2 Sync Log
|
||
|
||
- Setiap pengiriman → record `SyncLog` (request payload, response, status `SUCCESS`/`FAILED`/`RATE_LIMITED`, error message) tersimpan di tabel SIMRS untuk audit.
|
||
|
||
---
|
||
|
||
## 7. Standar Penulisan Kode
|
||
|
||
### 7.1 Linting & Format
|
||
|
||
- `go fmt` mandatory (CI hook disarankan).
|
||
- Komentar fungsi publik dalam Bahasa Indonesia diperbolehkan (codebase saat ini multilingual); konsistensi per file diutamakan.
|
||
|
||
### 7.2 Error Handling
|
||
|
||
- Gunakan `pkg/errors.AppError` untuk error berkode (HTTP/gRPC mapping otomatis).
|
||
- *Wrapping*: `fmt.Errorf("konteks: %w", err)` — selalu `%w` jika ingin di-unwrap.
|
||
- Worker **tidak boleh panic**; pakai `logger.Error` + retry/skip.
|
||
|
||
### 7.3 Testing
|
||
|
||
- File `*_test.go` wajib di package `pkg/utils/query`. Coverage modul bisnis saat ini rendah — **TODO** unit test layer service.
|
||
|
||
### 7.4 Generator Boilerplate
|
||
|
||
- Modul baru dibuat via `scripts/context.sh -s <file.sql>` atau `-j <file.json>`.
|
||
- Generator menghasilkan: entity, DTO, repository (Command + Query), service, mapper, gRPC proto draft.
|
||
|
||
---
|
||
|
||
## 8. Dependensi & Lisensi
|
||
|
||
- Go runtime: **1.25** (go.mod baris 3).
|
||
- Web: Gin 1.10, gRPC 1.78, Swagger via `swaggo/swag` 1.16.
|
||
- Persistence: GORM 1.30 (postgres/mysql/sqlserver/sqlite driver), sqlx 1.4, MongoDB driver 1.17.
|
||
- Cache & infra: redis/go-redis v9.17, MinIO Go v7.0.98, Prometheus client 1.23.
|
||
- Crypto: golang.org/x/crypto v0.49, JWT golang-jwt v5.2.
|
||
- Util: logrus 1.9, viper-style config via `karincake/apem`.
|
||
- License: Apache 2.0 (lihat header swagger).
|
||
|
||
---
|
||
|
||
## 9. Checklist Audit Periodik
|
||
|
||
- [ ] Semua tracker `.txt` masih dibutuhkan & sinkron dengan DB.
|
||
- [ ] Tidak ada secret di repository (`grep -r "password.*=.*\"" .` clean).
|
||
- [ ] `logger.Default()` digunakan; tidak ada `fmt.Println` di production path.
|
||
- [ ] Setiap worker punya rate-limit handler + token retry.
|
||
- [ ] Cache TTL & invalidation pattern terdokumentasi per modul.
|
||
- [ ] Migration script idempotent.
|
||
- [ ] Prometheus `/metrics` endpoint live & terkonsumsi monitoring stack.
|
||
|
||
---
|
||
|
||
*Disusun ulang: Tim Engineering GoPrint, 2026-05-13.*
|