@startuml architecture !theme plain skinparam backgroundColor #FFFFFF skinparam defaultFontName Arial skinparam packageStyle rectangle title The Biergarten App - Vertical Slice Architecture package "API.Core (thin host)" #E3F2FD { [API.Core\nASP.NET Core Web API host] as API note right of API - Program.cs wiring only - MediatR + AddApplicationPart per Features.* assembly - Swagger/OpenAPI, health checks - JWT auth middleware - Global exception filter end note } package "Feature Slices" #F3E5F5 { [Features.Auth] as AuthSlice [Features.Breweries] as BrewerySlice [Features.UserManagement] as UserSlice [Features.Emails] as EmailsSlice [Features.Locations] as LocationsSlice note right of AuthSlice Each slice owns its own: - Controller (HTTP endpoints) - Commands/Queries + Handlers - Validators - Repository (Dapper) end note note right of EmailsSlice No controller - invoked only via MediatR commands sent from other slices end note note right of LocationsSlice No controller and no MediatR handlers - exposes ILocationRepository directly as a plain DI service. Not registered by API.Core at all; only Database.Seed depends on it today end note } package "Shared" #FCE4EC { [Shared.Contracts] as SharedContracts [Shared.Application] as SharedApp note right of SharedApp - ValidationBehavior (MediatR pipeline) - Cross-slice email commands (SendRegistrationEmailCommand, SendResendConfirmationEmailCommand) end note } package "Infrastructure Layer" #FFF3E0 { [Infrastructure.Sql] as Sql [Infrastructure.Jwt] as JWT [Infrastructure.PasswordHashing] as PwdHash [Infrastructure.Email] as Email [Infrastructure.Email.Templates] as EmailTemplates } package "Domain Layer" #E8F5E9 { [Domain.Entities] as Domain [Domain.Exceptions] as DomainExceptions note right of Domain - UserAccount, UserCredential, UserVerification - BreweryPost, BreweryPostLocation - City, StateProvince, Country - BeerPost, BeerStyle (schema tables exist; no repository yet) end note } database "SQL Server" { [Tables] as Tables } node "Database.Seed\n(separate console app)" as Seed ' Relationships API ..> AuthSlice : AddApplicationPart\n+ MediatR scan API ..> BrewerySlice : AddApplicationPart\n+ MediatR scan API ..> UserSlice : AddApplicationPart\n+ MediatR scan API ..> EmailsSlice : MediatR scan only\n(no controller) Seed ..> AuthSlice : AddFeaturesAuth()\n(DI only, no API/MediatR) Seed ..> BrewerySlice : AddFeaturesBreweries() Seed ..> LocationsSlice : AddFeaturesLocations() AuthSlice --> Sql AuthSlice --> JWT AuthSlice --> PwdHash AuthSlice --> SharedContracts AuthSlice --> SharedApp BrewerySlice --> Sql BrewerySlice --> SharedContracts BrewerySlice --> SharedApp UserSlice --> Sql UserSlice --> SharedContracts UserSlice --> SharedApp LocationsSlice --> Sql EmailsSlice --> Email EmailsSlice --> EmailTemplates EmailsSlice --> SharedApp ' The one cross-slice interaction: a MediatR command contract, not a project reference AuthSlice ..> SharedApp : sends\nSendRegistrationEmailCommand EmailsSlice ..> SharedApp : handles\nSendRegistrationEmailCommand Sql --> Domain AuthSlice --> Tables : Dapper SQL BrewerySlice --> Tables : Dapper SQL UserSlice --> Tables : Dapper SQL LocationsSlice --> Tables : Dapper SQL AuthSlice --> Domain BrewerySlice --> Domain UserSlice --> Domain LocationsSlice --> Domain AuthSlice --> DomainExceptions ' Notes note left of Sql SQL-first approach Each slice's own repository issues inline SQL via Dapper - no ORM, no stored procedures end note note bottom of AuthSlice No Features.* project ever references another Features.* project directly end note @enduml