# ADR-0001: Technology stack - **Status:** Accepted (database decided 2026-10-30: **SQL Server**) - **Date:** 2026-10-30 - **Decided by:** project owner (Stream) ## Decision | Layer | Choice | |---|---| | Frontend | **Vue**, with **Nuxt.js** for SSR (SEO for public pages) | | Backend | **Go**, using the **Stream Framework** () | | Database | **SQL Server** (the customer already runs SQL Server) | | Object storage (S3) | **RustFS** | | Cache / sessions | **Redis** | | Container registry | **Harbor** | | GitOps / CD | **ArgoCD** | | Cluster management | **Rancher** | | Runtime | **Kubernetes** | | Centralised logging | **OpenSearch** + OpenSearch Dashboards | | Monitoring | **Grafana** (Rancher monitoring) | ## What the Stream Framework provides (from its public docs, 2026-10-30) - Go 1.25+ service template on **Fiber** (REST) plus **gRPC** (Protobuf). - **GORM** with automatic table prefixes, `BaseModel` / `BaseAuditorModel`; **golang-migrate** migrations in `database/migrations`. - **JWT** authentication with **Redis** sessions, role/permission authorisation, single-device mode. - **Queues** with pluggable drivers: PostgreSQL (`SKIP LOCKED`) or RabbitMQ; a worker binary and a failed-job CLI. - **Zap** structured logging (targets: Graylog, S3, GCP, file), OpenTelemetry tracing (Jaeger / Tempo / OTLP), Sentry, a live monitor, and `/health`. - Module pattern **Module → Controller → Service → Repository** (`src/modules/_module/`, generated with `./gen module `), routes registered in `src/routes/http.go`, DTOs and resources per module, fake repositories for unit tests. Read the framework docs (`/docs`, `/docs#module-pattern`, `/swagger/`) before writing backend code. Follow the module pattern. ## Database: SQL Server **Decision (2026-10-30):** use **SQL Server**. Recorded from the review of tracker task T-302 by ทิว: *"ลูกค้าใช้ SQL Server อยู่แล้วคงจะใช้ SQL Server ต่อไป"* (the customer already uses SQL Server and will continue to). What this means for the build: - **ORM and migrations:** GORM with `gorm.io/driver/sqlserver`; golang-migrate with its `sqlserver` driver. Check that the Stream Framework's base models and table-prefix logic work on SQL Server before the first module. - **Queues:** the framework's documented queue drivers are PostgreSQL (`SKIP LOCKED`) and **RabbitMQ**, so with SQL Server the queue uses **RabbitMQ**. Add RabbitMQ to the platform (K8s) unless the framework gains a SQL Server driver. - **Thai text:** use `NVARCHAR` and a Thai collation (e.g. `Thai_100_CI_AS`) so Thai sorts and compares correctly. - **JSON data** (DMN rule versions, consent records, audit payloads): store as `NVARCHAR(MAX)` checked with `ISJSON`, queried with SQL Server's JSON functions. - **Useful features:** row-level security for unit scoping; temporal (system-versioned) tables for effective-dated records such as name changes and guardian relations. - **Licensing:** covered by the customer's existing SQL Server [to confirm the edition and core count]. Still open: SQL Server **version and edition**, and whether the database runs on the **customer's existing server** or in the cluster. Record the answer here. The comparison that was used for the decision: | Consideration | PostgreSQL | SQL Server | |---|---|---| | Framework fit | The framework docs name PostgreSQL. The PostgreSQL queue driver works as-is | GORM and golang-migrate support SQL Server, but the queue would need RabbitMQ (the documented SKIP LOCKED driver is PostgreSQL) | | Licence cost on K8s | None | Per-core licensing (Standard/Enterprise) | | Features the use cases need | JSONB for DMN rule versions, consent records and audit payloads; row-level security as an option for unit scoping; partitioning for audit tables | JSON support; row-level security; temporal tables (useful for effective-dated records) | | Operations on K8s | Mature operators (e.g. CloudNativePG) | SQL Server on Linux containers is supported; fewer K8s operators | | Client preference / existing skills | — | **Customer already runs SQL Server** (deciding factor) | ## Consequences and follow-ups - Logs: the framework's logger targets do not include OpenSearch. Ship stdout/file logs with a collector (e.g. Fluent Bit) into OpenSearch [inferred]. - Audit evidence (WORM, signed snapshots; see [../agent/14-security-matrix.md](../agent/14-security-matrix.md)) should not live only in OpenSearch. Consider object lock on RustFS, or SQL Server **ledger tables** (SQL Server 2022+, tamper-evident by design) [to confirm the version]. - Nuxt SSR is mainly for public pages (admission, announcements, verification). The backoffice can be client-rendered. - Mobile approach (native, cross-platform, or PWA/LIFF as the use cases suggest for parents) is still open. - Secrets management, ingress / WAF, backup and DR are not decided yet.