mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Update architecture.md
This commit is contained in:
@@ -12,33 +12,63 @@ doesn't wire up any slice; it applies schema directly.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
---
|
---
|
||||||
|
|
||||||
config:
|
config:
|
||||||
|
|
||||||
flowchart:
|
flowchart:
|
||||||
|
|
||||||
curve: stepAfter
|
curve: stepAfter
|
||||||
|
|
||||||
|
layout: elk
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
flowchart TB
|
flowchart TB
|
||||||
|
|
||||||
API["API.Core<br/>(ASP.NET Core Web API)"]
|
API["API.Core<br/>(ASP.NET Core Web API)"]
|
||||||
|
|
||||||
Seed["Database.Seed<br/>(console app)"]
|
Seed["Database.Seed<br/>(console app)"]
|
||||||
|
|
||||||
Migrations["Database.Migrations<br/>(console app)"]
|
Migrations["Database.Migrations<br/>(console app)"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
subgraph SLICES["Feature Slices"]
|
subgraph SLICES["Feature Slices"]
|
||||||
|
|
||||||
direction LR
|
direction LR
|
||||||
|
|
||||||
UsersSlice["Features.Users<br/>(controller)"]
|
UsersSlice["Features.Users<br/>(controller)"]
|
||||||
|
|
||||||
BrewerySlice["Features.Breweries<br/>(controller)"]
|
BrewerySlice["Features.Breweries<br/>(controller)"]
|
||||||
|
|
||||||
PhotoSlice["Features.PhotoUpload<br/>(MediatR only)"]
|
PhotoSlice["Features.PhotoUpload<br/>(MediatR only)"]
|
||||||
|
|
||||||
EmailsSlice["Features.Emails<br/>(MediatR only)"]
|
EmailsSlice["Features.Emails<br/>(MediatR only)"]
|
||||||
|
|
||||||
LocationsSlice["Features.Locations<br/>(plain DI service)"]
|
LocationsSlice["Features.Locations<br/>(plain DI service)"]
|
||||||
|
|
||||||
BeersSlice["Features.Beers<br/>(scaffolded, no code yet)"]
|
BeersSlice["Features.Beers<br/>(scaffolded, no code yet)"]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
API -->|AddApplicationPart + MediatR scan| UsersSlice
|
API -->|AddApplicationPart + MediatR scan| UsersSlice
|
||||||
|
|
||||||
API -->|AddApplicationPart + MediatR scan| BrewerySlice
|
API -->|AddApplicationPart + MediatR scan| BrewerySlice
|
||||||
|
|
||||||
API -->|MediatR scan only| PhotoSlice
|
API -->|MediatR scan only| PhotoSlice
|
||||||
|
|
||||||
API -->|MediatR scan only| EmailsSlice
|
API -->|MediatR scan only| EmailsSlice
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Seed -->|AddFeaturesUsers| UsersSlice
|
Seed -->|AddFeaturesUsers| UsersSlice
|
||||||
|
|
||||||
Seed -->|AddFeaturesBreweries| BrewerySlice
|
Seed -->|AddFeaturesBreweries| BrewerySlice
|
||||||
|
|
||||||
Seed -->|AddFeaturesPhotoUpload| PhotoSlice
|
Seed -->|AddFeaturesPhotoUpload| PhotoSlice
|
||||||
|
|
||||||
Seed -->|AddFeaturesLocations| LocationsSlice
|
Seed -->|AddFeaturesLocations| LocationsSlice
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -54,67 +84,132 @@ see the notes for what else each of those foundation projects pulls in.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
---
|
---
|
||||||
|
|
||||||
config:
|
config:
|
||||||
flowchart:
|
|
||||||
curve: stepAfter
|
flowchart: {}
|
||||||
|
|
||||||
|
layout: fixed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
flowchart TB
|
flowchart TB
|
||||||
|
|
||||||
subgraph SLICES["Feature Slices"]
|
subgraph SLICES["Feature Slices"]
|
||||||
|
|
||||||
direction LR
|
direction LR
|
||||||
|
|
||||||
BrewerySlice["Features.Breweries"]
|
BrewerySlice["Features.Breweries"]
|
||||||
|
|
||||||
LocationsSlice["Features.Locations"]
|
LocationsSlice["Features.Locations"]
|
||||||
BeersSlice["Features.Beers<br/>(scaffolded)"]
|
|
||||||
|
BeersSlice["Features.Beers<br>(scaffolded)"]
|
||||||
|
|
||||||
PhotoSlice["Features.PhotoUpload"]
|
PhotoSlice["Features.PhotoUpload"]
|
||||||
|
|
||||||
UsersSlice["Features.Users"]
|
UsersSlice["Features.Users"]
|
||||||
|
|
||||||
EmailsSlice["Features.Emails"]
|
EmailsSlice["Features.Emails"]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph FOUNDATION["Foundation (every DB-backed slice depends on all three)"]
|
subgraph FOUNDATION["Foundation (every DB-backed slice depends on all three)"]
|
||||||
direction LR
|
|
||||||
SharedContracts["Shared.Contracts"]
|
|
||||||
Domain["Domain.Entities"]
|
|
||||||
DBConn["Database.Connection"]
|
|
||||||
end
|
|
||||||
|
|
||||||
SharedApp["Shared.Application<br/>(email command contract)"]
|
direction LR
|
||||||
|
|
||||||
|
SharedContracts["Shared.Contracts"]
|
||||||
|
|
||||||
|
Domain["Domain.Entities"]
|
||||||
|
|
||||||
|
DBConn["Database.Connection"]
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
subgraph INFRA["Slice-specific Infrastructure"]
|
subgraph INFRA["Slice-specific Infrastructure"]
|
||||||
|
|
||||||
direction LR
|
direction LR
|
||||||
|
|
||||||
JWT["Infrastructure.Jwt"]
|
JWT["Infrastructure.Jwt"]
|
||||||
|
|
||||||
PwdHash["Infrastructure.PasswordHashing"]
|
PwdHash["Infrastructure.PasswordHashing"]
|
||||||
|
|
||||||
FileUpload["Infrastructure.FileUpload"]
|
FileUpload["Infrastructure.FileUpload"]
|
||||||
|
|
||||||
Email["Infrastructure.Email"]
|
Email["Infrastructure.Email"]
|
||||||
|
|
||||||
EmailTemplates["Infrastructure.Email.Templates"]
|
EmailTemplates["Infrastructure.Email.Templates"]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Tables[("SQL Server<br/>Tables")]
|
UsersSlice == project ref, sends UploadPhotoCommand ==> PhotoSlice
|
||||||
|
|
||||||
%% the one feature-to-feature reference, breaking slice isolation
|
UsersSlice -. sends email commands .-> SharedApp["Shared.Application<br>(email command contract)"]
|
||||||
UsersSlice ==>|"project ref, sends UploadPhotoCommand"| PhotoSlice
|
|
||||||
|
|
||||||
%% cross-slice MediatR contract, routed through Shared.Application instead
|
EmailsSlice -. handles email commands .-> SharedApp
|
||||||
UsersSlice -.->|sends email commands| SharedApp
|
|
||||||
EmailsSlice -.->|handles email commands| SharedApp
|
|
||||||
|
|
||||||
%% every DB-backed slice depends on the same foundation
|
BrewerySlice --> FOUNDATION
|
||||||
BrewerySlice & LocationsSlice & BeersSlice & PhotoSlice & UsersSlice --> FOUNDATION
|
|
||||||
|
LocationsSlice --> FOUNDATION
|
||||||
|
|
||||||
|
BeersSlice --> FOUNDATION
|
||||||
|
|
||||||
|
PhotoSlice --> FOUNDATION & FileUpload
|
||||||
|
|
||||||
|
UsersSlice --> FOUNDATION & JWT & PwdHash
|
||||||
|
|
||||||
%% slice-specific infra, one edge per slice
|
|
||||||
UsersSlice --> JWT & PwdHash
|
|
||||||
PhotoSlice --> FileUpload
|
|
||||||
EmailsSlice --> Email & EmailTemplates
|
EmailsSlice --> Email & EmailTemplates
|
||||||
|
|
||||||
DBConn -->|Dapper SQL, no ORM| Tables
|
DBConn -- Dapper SQL, no ORM --> Tables[("SQL Server<br>Tables")]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## 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.
|
- **API.Core**: Program.cs wiring only
|
||||||
- **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).
|
- MediatR + `AddApplicationPart` per HTTP-exposed `Features.*` assembly
|
||||||
- **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`.
|
- Swagger/OpenAPI, health checks, JWT auth middleware, global exception filter
|
||||||
- **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.
|
- Only `Features.Users` and `Features.Breweries` get `AddApplicationPart` (controllers)
|
||||||
- **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).
|
- `Features.PhotoUpload` and `Features.Emails` scanned for MediatR handlers only
|
||||||
- **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.
|
- `Features.Locations` and `Features.Beers` not referenced by `API.Core` at all
|
||||||
- **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`.
|
- **Feature slices**: each owns its own controller (where present), commands/queries + handlers, validators, and Dapper repository
|
||||||
- **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.
|
- `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
|
||||||
|
|||||||
Reference in New Issue
Block a user