Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> for assistance with diagram creation and documentation prose updates.
4.4 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.
--8<-- "web/diagrams/vertical-slice/host-wiring.mmd"
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.
--8<-- "web/diagrams/vertical-slice/project-references.mmd"
Notes
-
API.Core: Program.cs wiring only
- MediatR +
AddApplicationPartper HTTP-exposedFeatures.*assembly - Swagger/OpenAPI, health checks, JWT auth middleware, global exception filter
- Only
Features.UsersandFeatures.BreweriesgetAddApplicationPart(controllers) Features.PhotoUploadandFeatures.Emailsscanned for MediatR handlers onlyFeatures.LocationsandFeatures.Beersnot referenced byAPI.Coreat all
- MediatR +
-
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 namespaced
Features.Auth, despite project nameFeatures.Users
- internally namespaced
Features.EmailsandFeatures.PhotoUploadhave no controller — invoked only via MediatR commands sent from other slicesFeatures.Locationshas no controller; one MediatR handler (GetOrCreateCityHandler) registered byDatabase.Seedonly —API.Corenever scans itFeatures.Beersis scaffolded (project + references only, no source files yet)
-
The one exception to slice isolation:
Features.Userstakes a direct project reference toFeatures.PhotoUpload- sends its
UploadPhotoCommandvia MediatR fromUploadAvatarHandler - unlike the email flow, the command type lives in the target slice itself,
not in
Shared.Application
- sends its
-
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,UserAvatarBreweryPost,BreweryPostLocationCity,StateProvince,CountryBeerPost,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- 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
- calls each slice's
-
Omitted from the diagram:
Domain.Exceptions(referenced by nearly every project directly, including all ofShared.Contracts,Infrastructure.Jwt, and each feature slice)Infrastructure.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