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 (<https://api-docs.innovationstream.app/>) |
| 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 indatabase/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/<name>_module/, generated with./gen module <name>), routes registered insrc/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 itssqlserverdriver. 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
NVARCHARand 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 withISJSON, 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) 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.