Project layout
Where a change belongs in the solution.
Dependencies point inward. Presentation calls application code. Application coordinates the domain. Infrastructure implements what application code defines. The domain project references nothing in the outer layers.
src/
├── Host/
│ ├── AppHost/ Aspire orchestration and shared config
│ ├── ServiceDefaults/ Health checks, telemetry, service defaults
│ └── Mursil.DevIssuer/ Local OpenID Connect stand-in
├── Mursil/
│ ├── Contracts/ DTOs and the tenancy switch
│ ├── Domain/ Aggregates, ids, smart enums
│ ├── Application/ Handlers, validators, delivery ports
│ ├── Client/ Transactional send client
│ ├── Integration/ Host package: send, template push, status
│ ├── Infrastructure/ EF Core, dispatch, SMTP transport
│ ├── Modules/
│ │ ├── Templates/ Template handlers and HTTP endpoints
│ │ └── Providers/ Provider and sender-domain handlers and endpoints
│ └── Presentation/
│ ├── Mursil.AspNetCore/ Request context and shared policies
│ ├── Mursil.Server/ Minimal API host
│ ├── Mursil.Admin/ Blazor admin portal
│ └── Mursil.Migrator/ Migrations and first-party seed
└── Tests/Mursil/ Domain, application, and integration tests
Packaged projects
These projects set IsPackable and publish a package id:
| Project | Package id |
|---|---|
Mursil.Domain | Mursil.Domain |
Mursil.Application | Mursil.Application |
Mursil.Infrastructure | Mursil.Infrastructure |
Mursil.Contracts | Mursil.Contracts |
Mursil.AspNetCore | Mursil.AspNetCore |
Mursil.Integration | Mursil.Integration |
Mursil.Modules.Templates | Mursil.Modules.Templates |
Mursil.Modules.Templates.Presentation | Mursil.Modules.Templates.Presentation |
Mursil.Modules.Providers | Mursil.Modules.Providers |
Mursil.Modules.Providers.Presentation | Mursil.Modules.Providers.Presentation |
Mursil.Admin is not one of them. The portal stays in that project. Do not extract it into a UI package. Mursil.Server and Mursil.Migrator are hosts. Mursil.Client is an in-repo send client and is not packed.
Mursil.Integration is the package a separate host uses to send, push a draft template, and read status. Core send and the portal work without a host referencing it.
Where a change goes
| You are adding | Put it in |
|---|---|
| A use case | Application/Features/<Feature>/<Verb><Noun>/, or the Templates or Providers module when that module already owns the area |
| A business rule | The domain aggregate that owns the invariant |
| A request or response shape | Mursil.Contracts |
| An EF mapping | Infrastructure/Persistence/Configurations/, then a migration from Mursil.Migrator |
| An HTTP route | The endpoint registration for that area |
| An operator page | Mursil.Admin, following the existing page layout |
Server maps routes in MapMursilServerEndpoints. The group prefix is /api/v1/mursil. Template routes are registered from Mursil.Modules.Templates.Presentation. Provider and sender-domain routes are registered from Mursil.Modules.Providers.Presentation.