MP Core 0.9.0
The first version published to nuget.org, and the first that is not a prerelease. It follows the internal
prerelease 0.2.0-alpha.9. Everything below was found, or asked for, by running a generated backend end
to end: the Storefront sample.
0.9.0 is a stable version whose major version is 0. The public API may still change in a minor
version; these notes will say what changed and how to move.
Moving from 0.2.0-alpha.9
| Change |
What to do |
EntityFrameworkAuditSink<TContext> no longer takes IServiceScopeFactory |
Nothing, unless you construct the sink yourself; AddMPCoreAudit registers it |
ICurrentActorAccessor, ITenantContext, IAuditContext and the audit interceptor are singletons |
Nothing, unless you registered your own scoped implementation; register it as a singleton that reads ambient state |
A broken business rule reaches the caller under the rule's own error domain and code, not mpcore.domain |
Clients that matched on the generic code match on the rule's code |
MPCoreObservabilityOptions.ServiceNamespace defaults to default |
Set it, as the template always did |
| A modular monolith is generated with one project per module |
Nothing for an existing repository; nothing forces a migration |
| The packages carry the Apache-2.0 licence and are published on nuget.org |
Remove any internal feed from NuGet.Config |
New
| Package |
What it adds |
MPCore.Validation.FluentValidation |
Input validators run as Wolverine middleware before the handler. An invalid message never reaches it, and the caller receives one violation per field, with a field path, a rule code and a message key. |
MPCore.Localization |
A message catalog: failure message keys are rendered in the language the caller asked for, from resource files, with a culture chain and named placeholders. MP Core's own messages ship in English and Persian. |
MPCore.Localization.EntityFrameworkCore.PostgreSql |
Translations an administrator edits at run time, stored in the product's own database and refreshed on every instance. |
MPCore.Idempotency.EntityFrameworkCore.PostgreSql |
Request idempotency and the consumer inbox (ADR-013). |
MPCore.Application |
IIdempotentExecutor and its ports; IFailureMessageLocalizer. |
MPCore.Domain |
BusinessRule, with an error domain, a code and a message key, validated when the rule is created; Entity<TId>.CheckRule. |
MPCore.Transport.Http, MPCore.Transport.Grpc |
Idempotency-Key and Idempotency-Replayed; RequireIdempotencyKey(). |
MPCore.Messaging.Abstractions |
PublishAsync(message, MessageDeliveryContext); the port IMessageInbox. |
MPCore.Messaging.Wolverine |
UseMPCoreInbox(); WolverineFoundationOptions.ApplicationAssembly; every handler with no other actor runs as system:<Message>. |
MPCore.Templates |
A design-time context factory for every backend, so that dotnet ef works whatever is attached to the context later; it reads the database from ConnectionStrings__PostgreSql. One project per module for a modular monolith; the message catalog and the validators registered for every host; guides that name the source of every convention. |
Fixed
| Where |
What was wrong |
MPCore.Messaging.Wolverine |
A message could leave without the change that caused it. A command that was invoked, whose save failed and whose retry changed nothing, delivered the messages of the failed attempt. An attempt that does not run to its end is now cleared (HandlerAttemptMiddleware; ADR-011, addendum of 2026-09-27). |
MPCore.Application, MPCore.Messaging.Wolverine |
In the same case the idempotency key was stored with the answer of the attempt that had failed. |
MPCore.Messaging.Wolverine |
The rollback policy moved the handler ahead of every middleware registered after it, so validators ran after the handler. |
MPCore.Messaging.Wolverine |
The host project's own handlers were never discovered. |
MPCore.Messaging.Wolverine |
Work done by a queued handler was audited as anonymous. |
MPCore.Messaging.Wolverine |
A queued message that breaks a business rule was retried; it is now dead-lettered after one attempt. |
MPCore.Audit.EntityFrameworkCore.PostgreSql |
A host with business audit could not start in Development, and every handler that recorded a business action failed at its first message. |
MPCore.Observability |
No Wolverine metric was exported; no MPCore.* meter was exported. |
MPCore.Persistence.EntityFrameworkCore.PostgreSql, MPCore.Audit.EntityFrameworkCore.PostgreSql, MPCore.Messaging.Wolverine |
Microsoft.EntityFrameworkCore.Relational resolved to an older patch than EF Core. |
MPCore.Templates |
A generated project could not build on Apple Silicon without Rosetta. |
MPCore.Templates |
A generated host had no health check. grpc.health.v1.Health answered UNKNOWN, which a gRPC probe reads as not serving, and /health/ready answered Healthy without asking anything. A host now has a check for the process and one for its database (Hosting/HostHealthChecks.cs). |
Decisions recorded
| Record |
Subject |
| ADR-004, addendum |
Public source, public packages, continuous integration without automated publication |
| ADR-007, addendum |
The current-actor accessor is a singleton that reads ambient state |
| ADR-011, addenda |
What a generated host taught the code-generation constraints; an attempt that failed takes its messages with it |
| ADR-012 |
One project per module; business rules, input validation and messages; how one module makes another change |
| ADR-013 |
Request idempotency and the consumer inbox |
ADR-012, ADR-013 and the addenda are recorded as proposed and await acceptance.
Known limits
- A process that stops between a commit and the delivery of its messages relies on Wolverine's recovery of
durable envelopes. MP Core has no test of it.
- Nothing stops a message from printing a secret into the log when its handling fails: Wolverine prints a
failed message, and a record prints every property. Override ToString on a message that carries one,
and keep secrets out of messages between modules.
- A command that ends in the error queue answers nothing, so a process that waits for it waits for ever
unless the product answers when the message is given up. The sample shows how.
IMessagePublisher cannot set a partition key or a delay.
- The name of an integration event (
IntegrationEvent.EventName) is not the name the broker carries. Two
services that share no assembly agree on one topic or queue per contract, and the consumer names the
type it reads with Wolverine's DefaultIncomingMessage<T>(). The sample shows how.
MPCore.Observability.Prometheus depends on a prerelease of the OpenTelemetry Prometheus exporter.
Verification
| Check |
Result |
| All tests, including integration against PostgreSQL, Redis and TimescaleDB |
444 passed, 0 failed, 0 skipped, in 15 test assemblies |
| Release build, warnings as errors |
0 warnings, 0 errors |
| Restore in locked mode |
passed |
Packed as 0.9.0 |
30 packages, 28 symbol packages |
eng/verify-release-artifacts.sh 0.9.0 on the packed files |
verified, 89 checks |
| A backend generated from the packed template in three combinations of options, built against the packed packages |
0 warnings, 0 errors |
| The Storefront sample, built and tested against the packed packages |
254 tests passed; every scenario passed against the running backends |
Measured on 2026-09-27, on macOS (Apple Silicon), from the working tree that became the first commit of
this repository. The packages that are published are packed again from that commit by a maintainer; these
numbers are the evidence that the tree was fit to be packed, not a description of the published files.