From 6a061f4e28b2bb150b58107d6a2092a1e0067995 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Mon, 31 Aug 2026 20:27:02 -0400 Subject: [PATCH] Update architecture.md --- docs/website/diagrams-md/architecture.md | 235 ++++++++++++++++------- 1 file changed, 165 insertions(+), 70 deletions(-) diff --git a/docs/website/diagrams-md/architecture.md b/docs/website/diagrams-md/architecture.md index f466b2b7..f4e91347 100644 --- a/docs/website/diagrams-md/architecture.md +++ b/docs/website/diagrams-md/architecture.md @@ -12,34 +12,64 @@ doesn't wire up any slice; it applies schema directly. ```mermaid --- + config: - flowchart: - curve: stepAfter + +flowchart: + +curve: stepAfter + +layout: elk + --- + flowchart TB - API["API.Core
(ASP.NET Core Web API)"] - Seed["Database.Seed
(console app)"] - Migrations["Database.Migrations
(console app)"] - subgraph SLICES["Feature Slices"] - direction LR - UsersSlice["Features.Users
(controller)"] - BrewerySlice["Features.Breweries
(controller)"] - PhotoSlice["Features.PhotoUpload
(MediatR only)"] - EmailsSlice["Features.Emails
(MediatR only)"] - LocationsSlice["Features.Locations
(plain DI service)"] - BeersSlice["Features.Beers
(scaffolded, no code yet)"] - end +API["API.Core
(ASP.NET Core Web API)"] - API -->|AddApplicationPart + MediatR scan| UsersSlice - API -->|AddApplicationPart + MediatR scan| BrewerySlice - API -->|MediatR scan only| PhotoSlice - API -->|MediatR scan only| EmailsSlice +Seed["Database.Seed
(console app)"] - Seed -->|AddFeaturesUsers| UsersSlice - Seed -->|AddFeaturesBreweries| BrewerySlice - Seed -->|AddFeaturesPhotoUpload| PhotoSlice - Seed -->|AddFeaturesLocations| LocationsSlice +Migrations["Database.Migrations
(console app)"] + + + +subgraph SLICES["Feature Slices"] + +direction LR + +UsersSlice["Features.Users
(controller)"] + +BrewerySlice["Features.Breweries
(controller)"] + +PhotoSlice["Features.PhotoUpload
(MediatR only)"] + +EmailsSlice["Features.Emails
(MediatR only)"] + +LocationsSlice["Features.Locations
(plain DI service)"] + +BeersSlice["Features.Beers
(scaffolded, no code yet)"] + +end + + + +API -->|AddApplicationPart + MediatR scan| UsersSlice + +API -->|AddApplicationPart + MediatR scan| BrewerySlice + +API -->|MediatR scan only| PhotoSlice + +API -->|MediatR scan only| EmailsSlice + + + +Seed -->|AddFeaturesUsers| UsersSlice + +Seed -->|AddFeaturesBreweries| BrewerySlice + +Seed -->|AddFeaturesPhotoUpload| PhotoSlice + +Seed -->|AddFeaturesLocations| LocationsSlice ``` ## Project reference dependencies @@ -54,67 +84,132 @@ see the notes for what else each of those foundation projects pulls in. ```mermaid --- + config: - flowchart: - curve: stepAfter + +flowchart: {} + +layout: fixed + --- + flowchart TB - subgraph SLICES["Feature Slices"] - direction LR - BrewerySlice["Features.Breweries"] - LocationsSlice["Features.Locations"] - BeersSlice["Features.Beers
(scaffolded)"] - PhotoSlice["Features.PhotoUpload"] - UsersSlice["Features.Users"] - EmailsSlice["Features.Emails"] - end - subgraph FOUNDATION["Foundation (every DB-backed slice depends on all three)"] - direction LR - SharedContracts["Shared.Contracts"] - Domain["Domain.Entities"] - DBConn["Database.Connection"] - end +subgraph SLICES["Feature Slices"] - SharedApp["Shared.Application
(email command contract)"] +direction LR - subgraph INFRA["Slice-specific Infrastructure"] - direction LR - JWT["Infrastructure.Jwt"] - PwdHash["Infrastructure.PasswordHashing"] - FileUpload["Infrastructure.FileUpload"] - Email["Infrastructure.Email"] - EmailTemplates["Infrastructure.Email.Templates"] - end +BrewerySlice["Features.Breweries"] - Tables[("SQL Server
Tables")] +LocationsSlice["Features.Locations"] - %% the one feature-to-feature reference, breaking slice isolation - UsersSlice ==>|"project ref, sends UploadPhotoCommand"| PhotoSlice +BeersSlice["Features.Beers
(scaffolded)"] - %% cross-slice MediatR contract, routed through Shared.Application instead - UsersSlice -.->|sends email commands| SharedApp - EmailsSlice -.->|handles email commands| SharedApp +PhotoSlice["Features.PhotoUpload"] - %% every DB-backed slice depends on the same foundation - BrewerySlice & LocationsSlice & BeersSlice & PhotoSlice & UsersSlice --> FOUNDATION +UsersSlice["Features.Users"] - %% slice-specific infra, one edge per slice - UsersSlice --> JWT & PwdHash - PhotoSlice --> FileUpload - EmailsSlice --> Email & EmailTemplates +EmailsSlice["Features.Emails"] - DBConn -->|Dapper SQL, no ORM| Tables +end + +subgraph FOUNDATION["Foundation (every DB-backed slice depends on all three)"] + +direction LR + +SharedContracts["Shared.Contracts"] + +Domain["Domain.Entities"] + +DBConn["Database.Connection"] + +end + +subgraph INFRA["Slice-specific Infrastructure"] + +direction LR + +JWT["Infrastructure.Jwt"] + +PwdHash["Infrastructure.PasswordHashing"] + +FileUpload["Infrastructure.FileUpload"] + +Email["Infrastructure.Email"] + +EmailTemplates["Infrastructure.Email.Templates"] + +end + +UsersSlice == project ref, sends UploadPhotoCommand ==> PhotoSlice + +UsersSlice -. sends email commands .-> SharedApp["Shared.Application
(email command contract)"] + +EmailsSlice -. handles email commands .-> SharedApp + +BrewerySlice --> FOUNDATION + +LocationsSlice --> FOUNDATION + +BeersSlice --> FOUNDATION + +PhotoSlice --> FOUNDATION & FileUpload + +UsersSlice --> FOUNDATION & JWT & PwdHash + +EmailsSlice --> Email & EmailTemplates + +DBConn -- Dapper SQL, no ORM --> Tables[("SQL Server
Tables")] ``` ## Notes -- **API.Core**: Program.cs wiring only — MediatR + `AddApplicationPart` per HTTP-exposed `Features.*` assembly, Swagger/OpenAPI, health checks, JWT auth middleware, global exception filter. Only `Features.Users` and `Features.Breweries` get `AddApplicationPart` (controllers); `Features.PhotoUpload` and `Features.Emails` are scanned for MediatR handlers only. `Features.Locations` and `Features.Beers` aren't referenced by `API.Core` at all. -- **Feature slices**: each owns its own controller (where present), commands/queries + handlers, validators, and Dapper repository. `Features.Users` covers auth (`Commands/Authentication`), account management (`Commands/Account`), and profile/avatar (`Commands/Profile`) — internally namespaced `Features.Auth`, despite the project being named `Features.Users`. `Features.Emails` and `Features.PhotoUpload` have no controller — invoked only via MediatR commands sent from other slices. `Features.Locations` has no controller either; it now has one MediatR handler (`GetOrCreateCityHandler`), but that's only registered by `Database.Seed`'s own MediatR pipeline — `API.Core` never scans it. `Features.Beers` is scaffolded (project + references only, no source files yet). -- **The one exception to slice isolation**: `Features.Users` takes a direct project reference to `Features.PhotoUpload` and sends its `UploadPhotoCommand` via MediatR from `UploadAvatarHandler` — unlike the email flow, the command type lives in the target slice itself, not in `Shared.Application`. -- **Shared.Application**: `ValidationBehavior` (MediatR pipeline) plus the cross-slice email commands (`SendRegistrationEmailCommand`, `SendResendConfirmationEmailCommand`) — that interaction is a MediatR command contract, not a project reference, and every slice (including `Features.Emails`) references it for the pipeline behavior alone. -- **Domain.Entities**: `UserAccount`, `UserCredential`, `UserVerification`, `UserProfile`, `UserAvatar`, `BreweryPost`, `BreweryPostLocation`, `City`, `StateProvince`, `Country`, `BeerPost`, `BeerStyle`, `Photo` (schema tables exist for `BeerPost`/`BeerStyle`; no repository yet since `Features.Beers` has no code). -- **Database.Connection**: the shared connection-factory project — each slice's own repository issues inline SQL via Dapper against it, no ORM, no stored procedures. Replaces the old `Infrastructure.Sql` project. -- **Database.Migrations**: separate console app (`schema.dbml` + SQL scripts) that applies DDL directly — distinct from `Database.Seed`, which loads data through the feature slices' own DI extensions. -- **Database.Seed**: calls each slice's `AddFeatures*()` DI extension directly and builds its own MediatR registration — no API host, no shared pipeline with `API.Core`. -- **Omitted from the diagram**: `Domain.Exceptions` (referenced by nearly every project directly, including all of `Shared.Contracts`, `Infrastructure.Jwt`, and each feature slice) and `Infrastructure.Configuration` (the shared options-binding project referenced by `Database.Connection`, `Infrastructure.Email`, `Infrastructure.FileUpload`, and `Features.Emails` directly) — both are plumbing-level dependencies of nearly everything above, and drawing them would just add a second copy of the same fan-in. +- **API.Core**: Program.cs wiring only + - MediatR + `AddApplicationPart` per HTTP-exposed `Features.*` assembly + - Swagger/OpenAPI, health checks, JWT auth middleware, global exception filter + - Only `Features.Users` and `Features.Breweries` get `AddApplicationPart` (controllers) + - `Features.PhotoUpload` and `Features.Emails` scanned for MediatR handlers only + - `Features.Locations` and `Features.Beers` not referenced by `API.Core` at all + +- **Feature slices**: each owns its own controller (where present), commands/queries + handlers, validators, and Dapper repository + - `Features.Users` covers auth (`Commands/Authentication`), account management (`Commands/Account`), and profile/avatar (`Commands/Profile`) + - internally namespaced `Features.Auth`, despite project name `Features.Users` + - `Features.Emails` and `Features.PhotoUpload` have no controller — invoked only via MediatR commands sent from other slices + - `Features.Locations` has no controller; one MediatR handler (`GetOrCreateCityHandler`) registered by `Database.Seed` only — `API.Core` never scans it + - `Features.Beers` is scaffolded (project + references only, no source files yet) + +- **The one exception to slice isolation**: `Features.Users` takes a direct project reference to `Features.PhotoUpload` + - sends its `UploadPhotoCommand` via MediatR from `UploadAvatarHandler` + - unlike the email flow, the command type lives in the target slice itself, not in `Shared.Application` + +- **Shared.Application**: + - `ValidationBehavior` (MediatR pipeline) + - Cross-slice email commands (`SendRegistrationEmailCommand`, `SendResendConfirmationEmailCommand`) + - MediatR command contract (not project reference) + - Every slice (including `Features.Emails`) references it for the pipeline behavior alone + +- **Domain.Entities**: + - `UserAccount`, `UserCredential`, `UserVerification`, `UserProfile`, `UserAvatar` + - `BreweryPost`, `BreweryPostLocation` + - `City`, `StateProvince`, `Country` + - `BeerPost`, `BeerStyle`, `Photo` (schema tables exist for `BeerPost`/`BeerStyle`; no repository yet since `Features.Beers` has no code) + +- **Database.Connection**: the shared connection-factory project + - each slice's own repository issues inline SQL via Dapper against it + - no ORM, no stored procedures + - Replaces the old `Infrastructure.Sql` project + +- **Database.Migrations**: separate console app + - `schema.dbml` + SQL scripts + - applies DDL directly + - distinct from `Database.Seed`, which loads data through the feature slices' own DI extensions + +- **Database.Seed**: + - calls each slice's `AddFeatures*()` DI extension directly + - builds its own MediatR registration + - no API host, no shared pipeline with `API.Core` + +- **Omitted from the diagram**: + - `Domain.Exceptions` (referenced by nearly every project directly, including all of `Shared.Contracts`, `Infrastructure.Jwt`, and each feature slice) + - `Infrastructure.Configuration` (the shared options-binding project referenced by `Database.Connection`, `Infrastructure.Email`, `Infrastructure.FileUpload`, and `Features.Emails` directly) + - both are plumbing-level dependencies of nearly everything above, and drawing them would just add a second copy of the same fan-in