# 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.