Skip to content

Commit ea65d27

Browse files
committed
arch(blueprints): unify database engine to SQL Server 2022 and map service entities
1 parent 5455493 commit ea65d27

3 files changed

Lines changed: 98 additions & 40 deletions

File tree

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
1-
# ADR 0026: Authoritative Database Engine Strategy by Language Stack
1+
# ADR 0026: Authoritative Unified Database Engine Strategy
22

33
## Status
4-
Proposed
4+
Approved
55

66
## Context
7-
As the UMS project evolves into an enterprise-grade monorepo and migrates core components to .NET 8, it is necessary to standardize the database engine preferences to optimize for ecosystem alignment, performance, and maintainability across different language stacks.
7+
As the UMS project evolves into an enterprise-grade monorepo, it is necessary to standardize the database engine to optimize for operational simplicity, cross-service data integrity, and corporate licensing alignment.
88

9-
Previously, PostgreSQL was used as the default relational engine for both Node.js and .NET prototypes. However, corporate standards for .NET excellence prioritize SQL Server for mission-critical .NET applications.
9+
Previously, a polyglot engine strategy was proposed (SQL Server for .NET and PostgreSQL/MongoDB for Node.js). However, managing multiple database engines in production—especially in on-premise or localized deployments—increases infrastructure overhead, security complexity (multiple RLS implementations), and total cost of ownership.
1010

1111
## Decision
12-
We will adopt a polyglot-aware database strategy based on the primary language of the application service:
12+
We will adopt a **Unified Database Strategy** for all services within the UMS product ecosystem:
1313

14-
1. **For .NET 8+ Applications (e.g., UMS Core API):**
15-
* **Relational Engine:** **SQL Server 2022** (Latest stable version).
16-
* **ORM:** Entity Framework Core (EF Core) with `Microsoft.EntityFrameworkCore.SqlServer`.
17-
* **Rationale:** Seamless integration with .NET ecosystem, superior tooling (SSMS/Azure Data Studio), and optimized execution plans for MediatR-based workloads.
14+
1. **Unified Relational Engine:** **SQL Server 2022** (Standard or Enterprise).
15+
* **All runtimes** (.NET 8 Core API and Node.js/NestJS Satellite services) must persist their data exclusively in SQL Server 2022.
16+
* **Rationale:** Standardizing on a single engine allows for a unified Row-Level Security (RLS) implementation, simplified backup/restore procedures, and consistent execution plan analysis.
1817

19-
2. **For Node.js / NestJS Applications:**
20-
* **Relational Engine:** **PostgreSQL 16**.
21-
* **NoSQL Engine:** **MongoDB**.
22-
* **Rationale:** Community maturity, native JSONB support in PG, and high developer velocity in the Node.js ecosystem.
18+
2. **Schema-per-Context Isolation:**
19+
* To maintain modularity, each Bounded Context will own a dedicated SQL Server Schema (e.g., `[ums_identity]`, `[ums_authz]`).
20+
* Direct cross-schema joins are discouraged in favor of domain events, but permitted for optimized read-only reporting views under strict governance.
2321

24-
### Security Implementation
25-
* **Row-Level Security (RLS):** For .NET/SQL Server services, RLS will be implemented using SQL Server **Security Policies** and **Inline Table-Valued Functions (iTVF)** for filtering, instead of PostgreSQL policies.
26-
* **Multi-Tenancy:** The "Shared Database, Shared Schema" model remains mandatory, enforced via SQL Server RLS.
22+
3. **ORM / Driver Support:**
23+
* **.NET 8**: Entity Framework Core with `Microsoft.EntityFrameworkCore.SqlServer`.
24+
* **Node.js / NestJS**: TypeORM or Prisma using the **`mssql`** driver.
25+
26+
4. **Security Implementation:**
27+
* **Unified RLS**: All services will utilize SQL Server **Security Policies** and **SESSION_CONTEXT** for multi-tenant isolation.
28+
* This eliminates the need to maintain parallel RLS logic for PostgreSQL.
2729

2830
## Consequences
29-
* The UMS migration plan from NestJS (Node) to .NET 8 must be updated to replace Npgsql with the SQL Server provider.
30-
* Local development environments (Docker Compose) will include a `mssql-server-linux` container for the .NET API.
31-
* Architectural blueprints and technical inventories must be updated to reflect SQL Server as the authoritative engine for the .NET stack.
32-
* Satellite systems inheriting from this reference architecture must follow these same engine preferences based on their language stack.
31+
* **Correction**: All references to PostgreSQL or MongoDB for Node.js services in `stack.md` or earlier documentation are now deprecated.
32+
* **Infrastructure**: Local development environments (Docker Compose) will only require a single `mssql-server-linux` instance.
33+
* **Skill Set**: The team must ensure proficiency in T-SQL and SQL Server-specific performance tuning across all language stacks.
34+
* **Migration**: Any existing Node.js prototypes using PostgreSQL must be migrated to SQL Server.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Service-Entity Map & Data Ownership
2+
3+
This document serves as the authoritative mapping between system entities, their Bounded Contexts, owning services, and database schemas within the UMS enterprise ecosystem.
4+
5+
---
6+
7+
## 🏛️ 1. Entity Ownership & Schema Mapping
8+
9+
| Entity | Bounded Context | Service Owner (Write) | Runtime | SQL Server Schema |
10+
| :--- | :--- | :--- | :--- | :--- |
11+
| **TENANT** | Identity | UMS Core API | .NET 8 | `[ums_identity]` |
12+
| **BRANCH** | Identity | UMS Core API | .NET 8 | `[ums_identity]` |
13+
| **USER_ACCOUNT** | Identity | UMS Core API | .NET 8 | `[ums_identity]` |
14+
| **ROLE** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
15+
| **PROFILE** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
16+
| **PROFILE_PERMISSION** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
17+
| **PERMISSION_TEMPLATE** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
18+
| **FUNCTIONAL_MODULE** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
19+
| **FUNCTIONAL_SUBMODULE**| Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
20+
| **FUNCTIONAL_OPTION** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
21+
| **ACTION** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
22+
| **SYSTEM_SUITE** | Authorization | UMS Core API | .NET 8 | `[ums_authz]` |
23+
| **ROLE_PROMOTION_CRITERIA**| IGA | IGA Satellite | NestJS | `[ums_iga]` |
24+
| **USER_PROMOTION_PROCESS** | IGA | IGA Satellite | NestJS | `[ums_iga]` |
25+
| **USER_MANAGEMENT_DELEGATION**| IGA | IGA Satellite | NestJS | `[ums_iga]` |
26+
| **DOCUMENT_TYPE** | Compliance | Compliance Satellite | NestJS | `[ums_compliance]` |
27+
| **USER_DOCUMENT** | Compliance | Compliance Satellite | NestJS | `[ums_compliance]` |
28+
| **NOTIFICATION_RULE** | Compliance | Compliance Satellite | NestJS | `[ums_compliance]` |
29+
| **ACCESS_ENFORCEMENT_POLICY**| Compliance | Compliance Satellite | NestJS | `[ums_compliance]` |
30+
| **APPROVAL_WORKFLOW** | Approvals | UMS Core API | .NET 8 | `[ums_approval]` |
31+
| **APPROVAL_REQUIRED_DOCUMENT**| Approvals | UMS Core API | .NET 8 | `[ums_approval]` |
32+
| **APPROVAL_REQUEST** | Approvals | UMS Core API | .NET 8 | `[ums_approval]` |
33+
| **APPROVAL_LOG** | Approvals | UMS Core API | .NET 8 | `[ums_approval]` |
34+
| **APP_CONFIGURATION** | Configuration | UMS Core API | .NET 8 | `[ums_config]` |
35+
36+
---
37+
38+
## 🛠️ 2. Data Access & Governance Rules
39+
40+
1. **Strict Write Ownership**: Only the **Service Owner** specified in the table above is allowed to perform `INSERT`, `UPDATE`, or `DELETE` operations on the corresponding entities.
41+
2. **Cross-Service Reads (Read-Only)**:
42+
* Satellite services (NestJS) may perform `SELECT` operations on `ums_identity` and `ums_authz` schemas to resolve context, but must do so through optimized views or read-only database users.
43+
* Cross-service data dependency should ideally be resolved via **Domain Events** (Asynchronous) rather than direct cross-schema joins to maintain decoupling.
44+
3. **Schema Isolation**: Each schema acts as a logical boundary. Permissions in SQL Server must be granted granularly per service account.
45+
46+
---
47+
48+
## ⚠️ 3. Explicit Correction: Database Engine Standardization
49+
50+
> [!IMPORTANT]
51+
> **Unified Engine Strategy**: Although `architecture/blueprints/stack.md` and some early prototypes mention PostgreSQL for Node.js/NestJS components, the final authoritative decision for the UMS production product is **SQL Server 2022 for all services**, regardless of the runtime (.NET 8 or NestJS).
52+
>
53+
> **Action Required**: **ADR-0026** must be updated to reflect this unification, removing PostgreSQL and MongoDB from the relational/NoSQL requirements for this specific codebase.
54+
55+
---
56+
57+
## 🔗 4. References
58+
* For the complete E/R diagram and relationship cardinality, refer to **[er-export-formats.md](er-export-formats.md)**.
59+
* For technical implementation of Row-Level Security (RLS) in SQL Server, refer to the Identity Context documentation.

architecture/blueprints/stack.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* **Team Expertise:** Strong NestJS & TypeScript/JavaScript, some DevOps (Docker, Kubernetes), no Java expertise
1919
* **Existing Constraints:** Transitioning to .NET 8 for Core API, SQL Server relational engine, high-performance Redis cache, Dapr-ready architecture, strict on-premise K8s deployment capability.
2020
* **Non-Negotiables:** Absolutely zero cloud-provider SDK dependencies in the core domain layer (strict Hexagonal Architecture); 100% self-hostable open-source infrastructure alternatives.
21-
* **Polyglot Strategy (ADR 0026):** .NET 8 -> SQL Server; Node.js -> PostgreSQL/MongoDB.
21+
* **Polyglot Strategy (ADR 0026):** .NET 8 (Core) & Node.js (Satellites) -> **Unified SQL Server 2022 Engine**.
2222

2323
---
2424

@@ -97,7 +97,7 @@
9797

9898
### 4.1 Architectural Pattern
9999
* **Chosen Tool:** **Hexagonal Architecture (Ports & Adapters) / Clean Architecture**
100-
* **Why Chosen:** Mandated to ensure the core Domain layer has **absolutely zero dependencies** on NestJS, TypeORM, PostgreSQL, or external cloud SDKs. Core logic communicates exclusively with interfaces (Ports), making the kernel completely sovereign and future-proof.
100+
* **Why Chosen:** Mandated to ensure the core Domain layer has **absolutely zero dependencies** on NestJS, TypeORM, SQL Server, or external cloud SDKs. Core logic communicates exclusively with interfaces (Ports), making the kernel completely sovereign and future-proof.
101101
* **Alternatives Rejected:**
102102
* *Standard 3-Tier Layered Architecture*: Creates strong coupling between business logic, database ORMs, and network frameworks, violating our non-negotiable sovereignty constraint.
103103

@@ -109,7 +109,7 @@
109109

110110
### 4.3 CQRS Approach
111111
* **Chosen Tool:** **Internal CQRS via NestJS CQRS module**
112-
* **Why Chosen:** Decouples heavy permission graph compilation (Queries) from basic identity mutations (Commands), optimizing read performance. Caches read-projections in Redis while writing sequentially to PostgreSQL.
112+
* **Why Chosen:** Decouples heavy permission graph compilation (Queries) from basic identity mutations (Commands), optimizing read performance. Caches read-projections in Redis while writing sequentially to **SQL Server 2022**.
113113
* **Alternatives Rejected:**
114114
* *Direct CRUD*: Directly querying and compiling relational tables on every read request degrades database performance under high concurrent loads.
115115

@@ -118,12 +118,10 @@
118118
## 5. Data Layer
119119

120120
### 5.1 Primary Database + ORM/Query Builder
121-
* **For .NET 8 Services:** **SQL Server 2022 + Entity Framework Core (EF Core)**
122-
* **Why:** Official Microsoft support, tight ecosystem integration, and optimized RLS via `SESSION_CONTEXT`.
123-
* **For Node.js Relational Services:** **PostgreSQL v16 + TypeORM / Drizzle**
124-
* **Why:** Robust native JSONB support and high maturity in the Node.js community.
125-
* **For Node.js NoSQL Services:** **MongoDB (Atlas / Enterprise)**
126-
* **Why:** High flexibility for unstructured documents and rapid prototyping.
121+
* **Unified Relational Engine:** **SQL Server 2022 (All Services)**
122+
* **For .NET 8 Services:** EF Core with SQL Server Provider.
123+
* **For Node.js Services:** TypeORM / Prisma with `mssql` driver.
124+
* **Why:** Official corporate standard, superior RLS via `SESSION_CONTEXT`, and operational simplicity for on-premise deployments.
127125

128126
### 5.2 Migration Strategy
129127
* **Chosen Tool:** **TypeORM Migrations executed via K8s Init-Containers**
@@ -155,9 +153,8 @@
155153

156154
### 6.1 Isolation Model
157155
* **Strategy:** **Shared Database with Native Row-Level Security (RLS)**
158-
* **Implementation (SQL Server):** Uses `SESSION_CONTEXT('TenantId', @value)` and Security Policies with iTVF predicates.
159-
* **Implementation (PostgreSQL):** Uses `SET LOCAL app.current_tenant = 'value'` and native `CREATE POLICY`.
160-
* **Why Chosen:** High packing density and low administrative overhead while maintaining absolute isolation at the engine level.
156+
* **Implementation (Unified):** Uses SQL Server `SESSION_CONTEXT('TenantId', @value)` and Security Policies with iTVF predicates for both .NET and Node.js.
157+
* **Why Chosen:** High packing density and absolute isolation at the engine level with a single implementation to maintain.
161158
* **Alternatives Rejected:**
162159
* *Database-per-tenant*: High infrastructure cost and severe administrative overhead when managing thousands of databases.
163160
* *Schema-per-tenant*: Becomes hard to scale and migrate when tenant counts exceed 1,000, causing connection pool exhaustion.
@@ -166,7 +163,7 @@
166163
* **Chosen Tool:** **NestJS Interceptor + PostgreSQL Connection Session Context**
167164
* **Why Chosen:** Resolves the `tenant_id` from JWT claims or `X-Tenant-ID` headers at ingress, and uses a database transaction wrapper to inject the tenant context into the active PostgreSQL session dynamically.
168165
* **Alternatives Rejected:**
169-
* *Application-level filtering*: Prone to developer omissions (forgetting a `WHERE tenant_id = x` clause), leading to critical data leak vulnerabilities. RLS prevents this at the database level.
166+
* *Application-level filtering*: Prone to developer omissions (forgetting a `WHERE tenant_id = x` clause), leading to critical data leak vulnerabilities. RLS prevents this at the database level across all runtimes.
170167

171168
---
172169

@@ -232,7 +229,7 @@
232229

233230
### 10.1 Local Development Setup
234231
* **Chosen Tool:** **Docker Compose Spec**
235-
* **Why Chosen:** Allows developers to spin up the entire UMS dependency suite (**SQL Server**, **PostgreSQL**, Redis, RabbitMQ, MinIO) locally with a single command (`docker compose up -d`).
232+
* **Why Chosen:** Allows developers to spin up the entire UMS dependency suite (**SQL Server 2022**, Redis, RabbitMQ, MinIO) locally with a single command (`docker compose up -d`).
236233

237234
### 10.2 Monorepo vs. Multi-repo
238235
* **Chosen Tool:** **Nx Monorepo**
@@ -241,7 +238,7 @@
241238
### 10.3 Testing Pyramid
242239
* **Chosen Tool:**
243240
* *Unit Tests*: Jest (aiming for >80% coverage).
244-
* *Integration Tests*: Jest + Supertest with **Testcontainers** (spinning up ephemeral PostgreSQL and Redis instances in local Docker for realistic testing).
241+
* *Integration Tests*: Jest + Supertest with **Testcontainers** (spinning up ephemeral **SQL Server Edge/Express** and Redis instances in local Docker for realistic testing).
245242
* *End-to-End*: Playwright for Web Console regression testing.
246243

247244
---
@@ -261,7 +258,7 @@ To avoid cloud-provider lock-in and support offline, on-premise environments, **
261258

262259
| Component | Chosen Solution | Lock-in Risk | Mitigation Strategy | Re-evaluate Trigger |
263260
| :--- | :--- | :--- | :--- | :--- |
264-
| **Database** | PostgreSQL v16 | **Low** | Standard SQL compliance. Domain layer has no direct dependency (decoupled via Ports). | Exceeding 20 TB of active data |
261+
| **Database** | SQL Server 2022 | **Medium** | Standard SQL compliance. Domain layer has no direct dependency (decoupled via Ports). Use of RLS creates some lock-in. | Licensing cost change |
265262
| **Object Store** | MinIO | **Low** | MinIO uses the exact AWS S3 API contract. Swapping requires a simple config change. | Performance bottlenecks |
266263
| **Secrets Store**| HashiCorp Vault | **Low** | Secret resolution is abstracted via K8s secrets injection or custom Adapter. | Licensing model changes |
267264
| **Gateway** | Kong Gateway | **Low** | Configuration is managed via standard K8s Ingress resources. | Custom routing constraints |
@@ -275,10 +272,10 @@ To avoid cloud-provider lock-in and support offline, on-premise environments, **
275272
* **Rationale:** Balances high-performance/safety requirements for the core with development speed for utilities.
276273
* **Revisit When:** Organizational skills shift significantly or a unified single-runtime model is required.
277274

278-
### Decision 2: Language-Driven Database Selection (ADR 0026)
279-
* **Decision:** **SQL Server for .NET; PostgreSQL/Mongo for Node.**
280-
* **Rationale:** Leverages the native strengths of each platform's driver and ecosystem support.
281-
* **Revisit When:** Cross-service data sharing requires a single unified engine or licensing costs exceed budget.
275+
### Decision 2: Unified Database Engine (ADR 0026)
276+
* **Decision:** **SQL Server 2022 for all services (.NET and Node.js).**
277+
* **Rationale:** Eliminates infrastructure fragmentation and ensures unified security enforcement (RLS).
278+
* **Revisit When:** Licensing costs exceed budget or a specific context requires native NoSQL features.
282279

283280
---
284281

0 commit comments

Comments
 (0)