Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions docs/operate/backend-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ required. The Marten outbox is still active for event side-effects:
SignalR notifications fire after `SaveChangesAsync` via
`ProjectionSideEffects`.

Codegen runs with `TypeLoadMode.Auto` — Wolverine/Marten generated
classes are pre-generated at build time to save cold-start time and
avoid Roslyn compilation at runtime.
Codegen mode is environment-aware. Production runs `TypeLoadMode.Dynamic`
— Wolverine/Marten handler classes are generated in memory on first use
and never written to disk, so the container never tries to write into its
read-only application directory. Local dev and tests run
`TypeLoadMode.Auto`, which generates into `Internal/Generated/` on first
boot and reuses it on the next.

## Marten usage

Expand Down Expand Up @@ -241,5 +244,6 @@ Integration tests (`Modgud.Api.Tests`) use:
- **Shared PostgreSQL container** — one container instance for all
test collections, parallelised
- **WireMock** — fake OIDC server for external login tests
- **Pre-generated Wolverine/Marten code** (`TypeLoadMode.Auto`) —
eliminates Roslyn compilation at runtime
- **Wolverine/Marten codegen** (`TypeLoadMode.Auto`) — generated on the
first boot into the test working tree and reused, so repeat runs skip
Roslyn compilation
16 changes: 15 additions & 1 deletion src/dotnet/Modgud.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,21 @@
opts.Discovery.IncludeAssembly(typeof(Modgud.Authentication.Api.Admin.RecoveryCli).Assembly);
opts.Discovery.IncludeAssembly(typeof(Modgud.Authorization.Commands.CreateGroupCommand).Assembly);
opts.Durability.Mode = wolverineMode;
opts.CodeGeneration.TypeLoadMode = JasperFx.CodeGeneration.TypeLoadMode.Auto;
// Production generates handler code in-memory (Dynamic): each handler is
// compiled via Roslyn on first use and never written to disk. The previous
// Auto mode tried to persist the generated source under /app/Internal —
// which the non-root container user cannot write — logging "Access to the
// path '/app/Internal' is denied" on the first hit of every handler.
// Dev/Test keep Auto, which writes into Internal/Generated/ on a writable
// working tree and reuses it across restarts.
//
// (Pre-generating at build time + TypeLoadMode.Static would also avoid the
// runtime Roslyn pass, but it requires JasperFx's `RunJasperFxCommands`
// entry point, which is incompatible with this app's WebApplicationFactory
// integration tests — see git history for fix/wolverine-codegen-static-prod.)
opts.CodeGeneration.TypeLoadMode = builder.Environment.IsProduction()
? JasperFx.CodeGeneration.TypeLoadMode.Dynamic
: JasperFx.CodeGeneration.TypeLoadMode.Auto;

// Wolverine 6 made `ServiceLocationPolicy.NotAllowed` the default. We
// keep that strict default — accidental new service-location dependencies
Expand Down
Loading