Split dbo schema into per-domain SQL schemas (#274)

This commit is contained in:
Aaron Po
2026-09-02 22:23:30 -04:00
committed by GitHub
parent 8d59f09cb2
commit d6e176bcd9
37 changed files with 275 additions and 227 deletions

View File

@@ -15,33 +15,51 @@ convention. For the vertical-slice code that talks to this schema, see
## Schema
The full schema lives under
`web/backend/Database/Database.Migrations/scripts/01-schema/` with one numbered
script per table.
The schema is split into six SQL Server schemas, one per feature area, each
living in its own numbered folder under
`web/backend/Database/Database.Migrations/scripts/`. The folder numbers
(`01`-`06`) fix the load order so foreign-key dependencies resolve — DbUp runs
embedded scripts in alphabetical-by-resource-name order, so e.g. `04-Brewery`
(which references `Auth`, `Geolocation`, and `Media`) runs after all three.
Each folder's `00-Schema.sql` creates its SQL schema before its tables run:
```
01-schema
├── 01-UserAccount.sql
├── 02-Photo.sql
├── 03-UserVerification.sql
├── 04-UserCredential.sql
├── 05-UserFollow.sql
├── 06-Country.sql
├── 07-StateProvince.sql
├── 08-City.sql
├── 09-BreweryPost.sql
├── 10-BreweryPostLocation.sql
├── 11-BreweryPostPhoto.sql
├── 12-BeerStyle.sql
├── 13-BeerPost.sql
├── 14-BeerPostPhoto.sql
├── 15-BeerPostComment.sql
├── 16-UserProfile.sql
└── 17-UserAvatar.sql
scripts
├── 01-Auth (UserAccount, UserVerification, UserCredential)
│ ├── 00-Schema.sql
│ ├── 01-UserAccount.sql
│ ├── 02-UserVerification.sql
│ └── 03-UserCredential.sql
├── 02-Geolocation (Country, StateProvince, City)
│ ├── 00-Schema.sql
│ ├── 01-Country.sql
│ ├── 02-StateProvince.sql
│ └── 03-City.sql
├── 03-Media (Photo)
│ ├── 00-Schema.sql
│ └── 01-Photo.sql
├── 04-Brewery (BreweryPost, BreweryPostLocation, BreweryPostPhoto)
│ ├── 00-Schema.sql
│ ├── 01-BreweryPost.sql
│ ├── 02-BreweryPostLocation.sql
│ └── 03-BreweryPostPhoto.sql
├── 05-Beer (BeerStyle, BeerPost, BeerPostPhoto, BeerPostComment)
│ ├── 00-Schema.sql
│ ├── 01-BeerStyle.sql
│ ├── 02-BeerPost.sql
│ ├── 03-BeerPostPhoto.sql
│ └── 04-BeerPostComment.sql
└── 06-Social (UserFollow, UserProfile, UserAvatar)
├── 00-Schema.sql
├── 01-UserFollow.sql
├── 02-UserProfile.sql
└── 03-UserAvatar.sql
```
Each script is run in order by `Database.Migrations` (DbUp) on startup. Each
table is queried directly by the owning feature slice's repository (see
Each script is run in order by `Database.Migrations` (DbUp) on startup. Tables
are referenced schema-qualified in code (e.g. `Auth.UserAccount`), not under
the default `dbo` schema. Each table is queried directly by the owning
feature slice's repository (see
[Direct SQL via Dapper/ADO.NET](architecture.md#direct-sql-via-dapperadonet)).
A generated DBML copy of the schema (for import into a diagramming tool such as

View File

@@ -1,39 +1,46 @@
# Database Schema
Five diagrams, one per schema category — mirrors the `identity` / `media` /
`location` / `brewery` / `beer` schema split. Every table also has a
Six diagrams, one per SQL Server schema — mirrors the `Auth` / `Geolocation` /
`Media` / `Brewery` / `Beer` / `Social` schema split under
`web/backend/Database/Database.Migrations/scripts/`. Every table also has a
`RowVersion ROWVERSION` column (optimistic concurrency), omitted below for
brevity. Tables marked `(*)` have no repository/feature slice yet.
Boxes with no attribute list (e.g. `Photo` in the identity diagram) are owned by
a different schema and shown only to keep the relationship visible; see the
Boxes with no attribute list (e.g. `Photo` in the auth diagram) are owned by a
different schema and shown only to keep the relationship visible; see the
diagram named after that schema for its full definition.
## Identity (`identity`)
## Auth (`Auth`)
```mermaid
--8<-- "web/diagrams/database-schema/identity.mmd"
--8<-- "web/diagrams/database-schema/auth.mmd"
```
## Media (`media`)
## Social (`Social`)
```mermaid
--8<-- "web/diagrams/database-schema/social.mmd"
```
## Media (`Media`)
```mermaid
--8<-- "web/diagrams/database-schema/media.mmd"
```
## Location (`location`)
## Geolocation (`Geolocation`)
```mermaid
--8<-- "web/diagrams/database-schema/location.mmd"
--8<-- "web/diagrams/database-schema/geolocation.mmd"
```
## Brewery (`brewery`)
## Brewery (`Brewery`)
```mermaid
--8<-- "web/diagrams/database-schema/brewery.mmd"
```
## Beer (`beer`)
## Beer (`Beer`)
```mermaid
--8<-- "web/diagrams/database-schema/beer.mmd"

View File

@@ -6,12 +6,11 @@ config:
erDiagram
User ||--o{ Cred : "has credentials"
User ||--o| Verify : "has verification"
%% Cross-schema: defined in full in social.mmd / media.mmd / brewery.mmd / beer.mmd
User ||--o| Profile : "has profile"
User ||--o{ Follow : "follows"
User ||--o{ Follow : "followed by"
%% Cross-schema: defined in full in media.mmd / brewery.mmd / beer.mmd
Profile ||--o{ Avatar : "has avatar history"
User ||--o{ Photo : "uploads"
User ||--o{ Brewery : "posts"
User ||--o{ Beer : "posts"
@@ -43,16 +42,3 @@ erDiagram
uniqueidentifier UserAccountID "FK, UK"
datetime VerificationDateTime
}
Profile {
uniqueidentifier UserProfileID PK
uniqueidentifier UserAccountID "FK, UK"
nvarchar Biography
}
Follow {
uniqueidentifier UserFollowID PK
uniqueidentifier UserAccountID FK
uniqueidentifier FollowingID FK
datetime CreatedAt
}

View File

@@ -8,10 +8,11 @@ erDiagram
Beer ||--o{ BeerPhoto : "has photos"
Beer ||--o{ BeerComment : "has comments"
%% Cross-schema: defined in full in identity.mmd / brewery.mmd / media.mmd
%% Cross-schema: defined in full in auth.mmd / brewery.mmd / media.mmd
User ||--o{ Beer : "posts"
Brewery ||--o{ Beer : "brews"
User ||--o{ BeerComment : "comments"
BeerPhoto }o--|| Photo : "uses photo"
BeerStyle {
uniqueidentifier BeerStyleID PK
@@ -41,3 +42,10 @@ erDiagram
datetime CreatedAt
datetime UpdatedAt "nullable"
}
BeerPhoto {
uniqueidentifier BeerPostPhotoID PK
uniqueidentifier BeerPostID FK
uniqueidentifier PhotoID FK
datetime LinkedAt
}

View File

@@ -9,8 +9,10 @@ erDiagram
Brewery ||--o{ BreweryPhoto : "has photos"
Brewery ||--o{ Beer : "brews"
%% Cross-schema: defined in full in identity.mmd / location.mmd / media.mmd / beer.mmd
%% Cross-schema: defined in full in auth.mmd / geolocation.mmd / media.mmd / beer.mmd
User ||--o{ Brewery : "posts"
BreweryLocation }o--|| City : "located in"
BreweryPhoto }o--|| Photo : "uses photo"
Brewery {
uniqueidentifier BreweryPostID PK
@@ -20,3 +22,20 @@ erDiagram
datetime CreatedAt
datetime UpdatedAt "nullable"
}
BreweryLocation {
uniqueidentifier BreweryPostLocationID PK
uniqueidentifier BreweryPostID "FK, UK"
nvarchar AddressLine1
nvarchar AddressLine2 "nullable"
nvarchar PostalCode
uniqueidentifier CityID FK
geography Coordinates "nullable"
}
BreweryPhoto {
uniqueidentifier BreweryPostPhotoID PK
uniqueidentifier BreweryPostID FK
uniqueidentifier PhotoID FK
datetime LinkedAt
}

View File

@@ -1,10 +1,9 @@
erDiagram
Country ||--o{ StateProvince : "has"
StateProvince ||--o{ City : "has"
City ||--o{ BreweryLocation : "located in"
%% Cross-schema: defined in full in brewery.mmd
Brewery ||--o| BreweryLocation : "has location"
City ||--o{ BreweryLocation : "located in"
Country {
uniqueidentifier CountryID PK
@@ -24,13 +23,3 @@ erDiagram
nvarchar CityName
uniqueidentifier StateProvinceID FK
}
BreweryLocation {
uniqueidentifier BreweryPostLocationID PK
uniqueidentifier BreweryPostID "FK, UK"
nvarchar AddressLine1
nvarchar AddressLine2 "nullable"
nvarchar PostalCode
uniqueidentifier CityID FK
geography Coordinates "nullable"
}

View File

@@ -4,41 +4,16 @@ config:
---
erDiagram
User ||--o{ Photo : "uploads"
%% Cross-schema: defined in full in auth.mmd / social.mmd / brewery.mmd / beer.mmd
Avatar }o--|| Photo : "uses photo"
Photo ||--o{ BreweryPhoto : "used in"
Photo ||--o{ BeerPhoto : "used in"
%% Cross-schema: defined in full in identity.mmd / brewery.mmd / beer.mmd
Profile ||--o{ Avatar : "has avatar history"
User ||--o{ Photo : "uploads"
Brewery ||--o{ BreweryPhoto : "has photos"
Beer ||--o{ BeerPhoto : "has photos"
Photo {
uniqueidentifier PhotoID PK
nvarchar Hyperlink "nullable"
uniqueidentifier UploadedByID FK
datetime UploadedAt
}
Avatar {
uniqueidentifier UserAvatarID PK
uniqueidentifier UserProfileID FK
uniqueidentifier PhotoID FK
datetime2 ValidFrom
datetime2 ValidTo "nullable; at most one row per UserProfileID has ValidTo IS NULL (active avatar)"
}
BreweryPhoto {
uniqueidentifier BreweryPostPhotoID PK
uniqueidentifier BreweryPostID FK
uniqueidentifier PhotoID FK
datetime LinkedAt
}
BeerPhoto {
uniqueidentifier BeerPostPhotoID PK
uniqueidentifier BeerPostID FK
uniqueidentifier PhotoID FK
datetime LinkedAt
}

View File

@@ -0,0 +1,34 @@
---
config:
layout: elk
---
erDiagram
User ||--o| Profile : "has profile"
User ||--o{ Follow : "follows"
User ||--o{ Follow : "followed by"
Profile ||--o{ Avatar : "has avatar history"
%% Cross-schema: defined in full in auth.mmd / media.mmd
Avatar }o--|| Photo : "uses photo"
Profile {
uniqueidentifier UserProfileID PK
uniqueidentifier UserAccountID "FK, UK"
nvarchar Biography
}
Follow {
uniqueidentifier UserFollowID PK
uniqueidentifier UserAccountID FK
uniqueidentifier FollowingID FK
datetime CreatedAt
}
Avatar {
uniqueidentifier UserAvatarID PK
uniqueidentifier UserProfileID FK
uniqueidentifier PhotoID FK
datetime2 ValidFrom
datetime2 ValidTo "nullable; at most one row per UserProfileID has ValidTo IS NULL (active avatar)"
}