Skip to content

MP Core 0.9.0

Choose a tag to compare

@panahister panahister released this 27 Sep 17:09

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.