|
| 1 | +# ADR 0066: Database Schema per Domain for UMS Phase 1 |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Accepted |
| 6 | + |
| 7 | +## Parent Standard |
| 8 | + |
| 9 | +This ADR specializes the Evolith parent decision: |
| 10 | + |
| 11 | +- [Evolith ADR 0067: Modular Monolith Database Boundary — Schema per Domain](https://github.com/beyondnetcode/evolith_arch32/blob/main/reference/architecture/adrs/core/0067-modular-monolith-schema-per-domain.md) |
| 12 | + |
| 13 | +## Context and Problem Statement |
| 14 | + |
| 15 | +UMS is implemented as both the official applied reference for Evolith and the Phase 1 product solution for enterprise user management. |
| 16 | + |
| 17 | +The solution starts as a Modular Monolith, but it must avoid decisions that create unnecessary coupling or block a future migration toward distributed modules or microservices. |
| 18 | + |
| 19 | +The key architectural question is whether UMS should use: |
| 20 | + |
| 21 | +1. A single shared database without logical schema separation. |
| 22 | +2. A single physical database with schemas separated by module/domain. |
| 23 | +3. Multiple physical databases from Phase 1. |
| 24 | + |
| 25 | +This decision is critical because it directly impacts: |
| 26 | + |
| 27 | +- Domain responsibility separation. |
| 28 | +- Future migration to microservices. |
| 29 | +- Module independence. |
| 30 | +- Prevention of unnecessary coupling. |
| 31 | +- Formal Phase 1 ADR traceability. |
| 32 | + |
| 33 | +## Decision |
| 34 | + |
| 35 | +UMS Phase 1 will use: |
| 36 | + |
| 37 | +```text |
| 38 | +Single physical SQL Server database + logical schemas per module/domain |
| 39 | +``` |
| 40 | + |
| 41 | +UMS will not start with multiple physical databases. However, it will also not use an unconstrained shared default schema for all modules. |
| 42 | + |
| 43 | +Each bounded context or module must own its database schema. |
| 44 | + |
| 45 | +Reference structure: |
| 46 | + |
| 47 | +```text |
| 48 | +ums_database |
| 49 | +├── identity_schema |
| 50 | +├── access_schema |
| 51 | +├── tenant_schema |
| 52 | +├── audit_schema |
| 53 | +└── notification_schema |
| 54 | +``` |
| 55 | + |
| 56 | +The final schema names must align with the official UMS bounded context map and domain model. |
| 57 | + |
| 58 | +## Architectural Rules for UMS |
| 59 | + |
| 60 | +1. Each UMS module owns its schema and internal tables. |
| 61 | +2. A module must not directly mutate tables owned by another module. |
| 62 | +3. Cross-module interaction must happen through application services, explicit contracts, ports, domain services, or integration events. |
| 63 | +4. Cross-schema joins between modules are discouraged and require explicit architectural justification. |
| 64 | +5. EF Core mappings and migrations must preserve module ownership by schema. |
| 65 | +6. Any exception to schema ownership must be documented and reviewed architecturally. |
| 66 | + |
| 67 | +## Rationale |
| 68 | + |
| 69 | +This strategy preserves Phase 1 simplicity while enforcing architectural boundaries at the persistence layer. |
| 70 | + |
| 71 | +A fully shared database schema would allow hidden coupling through direct table access, implicit joins, and unclear ownership. That would make the code look modular while the data model remains tightly coupled. |
| 72 | + |
| 73 | +Using schemas per module/domain makes the Modular Monolith more honest and prepares UMS for future extraction when needed. |
| 74 | + |
| 75 | +The intended evolution path is: |
| 76 | + |
| 77 | +```text |
| 78 | +UMS Modular Monolith with schema-per-domain boundaries |
| 79 | + ↓ |
| 80 | +Identify a bounded context that needs extraction |
| 81 | + ↓ |
| 82 | +Move that schema to a dedicated database |
| 83 | + ↓ |
| 84 | +Replace direct access with APIs, events, or integration contracts |
| 85 | +``` |
| 86 | + |
| 87 | +## Alternatives Considered |
| 88 | + |
| 89 | +### Alternative 1: Single SQL Server database with one shared default schema |
| 90 | + |
| 91 | +Example: |
| 92 | + |
| 93 | +```text |
| 94 | +ums_database |
| 95 | +└── dbo |
| 96 | +``` |
| 97 | + |
| 98 | +**Advantages:** |
| 99 | + |
| 100 | +- Simplest setup for the first implementation. |
| 101 | +- Fewer initial conventions and migration rules. |
| 102 | + |
| 103 | +**Disadvantages:** |
| 104 | + |
| 105 | +- High risk of hidden coupling. |
| 106 | +- Unclear table ownership. |
| 107 | +- Harder future service extraction. |
| 108 | +- Easier introduction of cross-domain joins and direct table dependencies. |
| 109 | + |
| 110 | +**Result:** Rejected. |
| 111 | + |
| 112 | +### Alternative 2: Physical database per module from Phase 1 |
| 113 | + |
| 114 | +Example: |
| 115 | + |
| 116 | +```text |
| 117 | +ums_identity_db |
| 118 | +ums_access_db |
| 119 | +ums_audit_db |
| 120 | +ums_notification_db |
| 121 | +``` |
| 122 | + |
| 123 | +**Advantages:** |
| 124 | + |
| 125 | +- Strongest physical isolation. |
| 126 | +- Clear deployment and ownership boundaries. |
| 127 | + |
| 128 | +**Disadvantages:** |
| 129 | + |
| 130 | +- Premature operational complexity for Phase 1. |
| 131 | +- More complex local development, backups, observability, transactions, migrations, and deployment. |
| 132 | +- Higher coordination cost before the product requires independent scaling or deployment. |
| 133 | + |
| 134 | +**Result:** Rejected for Phase 1. |
| 135 | + |
| 136 | +### Alternative 3: Single SQL Server database with schemas per module/domain |
| 137 | + |
| 138 | +Example: |
| 139 | + |
| 140 | +```text |
| 141 | +ums_database |
| 142 | +├── identity |
| 143 | +├── access |
| 144 | +├── tenant |
| 145 | +├── audit |
| 146 | +└── notification |
| 147 | +``` |
| 148 | + |
| 149 | +**Advantages:** |
| 150 | + |
| 151 | +- Balances simplicity and architectural discipline. |
| 152 | +- Makes ownership explicit. |
| 153 | +- Reduces coupling risk. |
| 154 | +- Supports future microservice extraction. |
| 155 | + |
| 156 | +**Disadvantages:** |
| 157 | + |
| 158 | +- Requires schema naming and migration discipline. |
| 159 | +- Requires governance to avoid unauthorized cross-schema access. |
| 160 | + |
| 161 | +**Result:** Accepted. |
| 162 | + |
| 163 | +## Consequences |
| 164 | + |
| 165 | +### Positive |
| 166 | + |
| 167 | +- UMS has clear persistence ownership boundaries from Phase 1. |
| 168 | +- Module independence is reinforced beyond source-code structure. |
| 169 | +- Future migration to microservices becomes less disruptive. |
| 170 | +- Database coupling risks become visible and governable. |
| 171 | +- The decision remains aligned with Evolith parent architecture. |
| 172 | + |
| 173 | +### Negative / Trade-offs |
| 174 | + |
| 175 | +- Requires schema conventions from the beginning. |
| 176 | +- Requires careful EF Core configuration and migrations. |
| 177 | +- Teams must avoid using SQL Server as an informal integration mechanism. |
| 178 | +- Reporting or read-model scenarios may need explicit architectural exceptions. |
| 179 | + |
| 180 | +## Implementation Guidance |
| 181 | + |
| 182 | +- Define one EF Core schema mapping per bounded context/module. |
| 183 | +- Keep migrations traceable to the owning module/schema. |
| 184 | +- Avoid direct writes across schema boundaries. |
| 185 | +- Prefer application-layer contracts, integration events, or dedicated read models for cross-module scenarios. |
| 186 | +- Document any approved cross-schema dependency as an explicit exception. |
| 187 | + |
| 188 | +## Final Rule |
| 189 | + |
| 190 | +Each UMS module owns its schema. No module may directly access or modify another module's internal tables except through explicit, documented, and architecturally approved contracts. |
0 commit comments