7.0 KiB
The Biergarten App — Vertical Slice Architecture
Two diagrams, split by concern: which host wires up which slice at startup, and which projects each slice actually compiles against. Mixing DI wiring and compile-time references in one graph is what made the old diagram unreadable.
Host wiring
Each host (API.Core, Database.Seed) decides for itself which Features.*
assemblies to register — there's no shared bootstrap. Database.Migrations
doesn't wire up any slice; it applies schema directly.
---
config:
flowchart:
curve: stepAfter
---
flowchart TB
API["API.Core<br/>(ASP.NET Core Web API)"]
Seed["Database.Seed<br/>(console app)"]
Migrations["Database.Migrations<br/>(console app)"]
subgraph SLICES["Feature Slices"]
direction LR
UsersSlice["Features.Users<br/>(controller)"]
BrewerySlice["Features.Breweries<br/>(controller)"]
PhotoSlice["Features.PhotoUpload<br/>(MediatR only)"]
EmailsSlice["Features.Emails<br/>(MediatR only)"]
LocationsSlice["Features.Locations<br/>(plain DI service)"]
BeersSlice["Features.Beers<br/>(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
Solid arrows are compile-time project references. Dashed arrows are the two
places slices talk to each other without a direct reference: the email
MediatR contract routed through Shared.Application, and the one place that
pattern is broken outright. Every DB-backed slice depends on the same three
foundation projects (Shared.Contracts, Domain.Entities,
Database.Connection) — drawn as one box instead of fifteen separate arrows;
see the notes for what else each of those foundation projects pulls in.
---
config:
flowchart:
curve: stepAfter
---
flowchart TB
subgraph SLICES["Feature Slices"]
direction LR
BrewerySlice["Features.Breweries"]
LocationsSlice["Features.Locations"]
BeersSlice["Features.Beers<br/>(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
SharedApp["Shared.Application<br/>(email command contract)"]
subgraph INFRA["Slice-specific Infrastructure"]
direction LR
JWT["Infrastructure.Jwt"]
PwdHash["Infrastructure.PasswordHashing"]
FileUpload["Infrastructure.FileUpload"]
Email["Infrastructure.Email"]
EmailTemplates["Infrastructure.Email.Templates"]
end
Tables[("SQL Server<br/>Tables")]
%% the one feature-to-feature reference, breaking slice isolation
UsersSlice ==>|"project ref, sends UploadPhotoCommand"| PhotoSlice
%% cross-slice MediatR contract, routed through Shared.Application instead
UsersSlice -.->|sends email commands| SharedApp
EmailsSlice -.->|handles email commands| SharedApp
%% every DB-backed slice depends on the same foundation
BrewerySlice & LocationsSlice & BeersSlice & PhotoSlice & UsersSlice --> FOUNDATION
%% slice-specific infra, one edge per slice
UsersSlice --> JWT & PwdHash
PhotoSlice --> FileUpload
EmailsSlice --> Email & EmailTemplates
DBConn -->|Dapper SQL, no ORM| Tables
Notes
- API.Core: Program.cs wiring only — MediatR +
AddApplicationPartper HTTP-exposedFeatures.*assembly, Swagger/OpenAPI, health checks, JWT auth middleware, global exception filter. OnlyFeatures.UsersandFeatures.BreweriesgetAddApplicationPart(controllers);Features.PhotoUploadandFeatures.Emailsare scanned for MediatR handlers only.Features.LocationsandFeatures.Beersaren't referenced byAPI.Coreat all. - Feature slices: each owns its own controller (where present), commands/queries + handlers, validators, and Dapper repository.
Features.Userscovers auth (Commands/Authentication), account management (Commands/Account), and profile/avatar (Commands/Profile) — internally namespacedFeatures.Auth, despite the project being namedFeatures.Users.Features.EmailsandFeatures.PhotoUploadhave no controller — invoked only via MediatR commands sent from other slices.Features.Locationshas no controller either; it now has one MediatR handler (GetOrCreateCityHandler), but that's only registered byDatabase.Seed's own MediatR pipeline —API.Corenever scans it.Features.Beersis scaffolded (project + references only, no source files yet). - The one exception to slice isolation:
Features.Userstakes a direct project reference toFeatures.PhotoUploadand sends itsUploadPhotoCommandvia MediatR fromUploadAvatarHandler— unlike the email flow, the command type lives in the target slice itself, not inShared.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 (includingFeatures.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 forBeerPost/BeerStyle; no repository yet sinceFeatures.Beershas 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.Sqlproject. - Database.Migrations: separate console app (
schema.dbml+ SQL scripts) that applies DDL directly — distinct fromDatabase.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 withAPI.Core. - Omitted from the diagram:
Domain.Exceptions(referenced by nearly every project directly, including all ofShared.Contracts,Infrastructure.Jwt, and each feature slice) andInfrastructure.Configuration(the shared options-binding project referenced byDatabase.Connection,Infrastructure.Email,Infrastructure.FileUpload, andFeatures.Emailsdirectly) — both are plumbing-level dependencies of nearly everything above, and drawing them would just add a second copy of the same fan-in.