Skip to content

Marten 9.35.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 13 Sep 19:31
· 151 commits to master since this release
ee981cc

Marten 9.35.0 — Event Model naming, store identity, and idempotent archiving.

⚠️ Behaviour changes

Three, all deliberate. None require a code change to adopt, but each changes what an existing application observes.

1. A store's derived Event Model is now named after your service, not "EventModel" (#5408).

The store-derived Event Model source fell back to the literal "EventModel" — the one default guaranteed to be wrong for every host. Wolverine names its derived model after JasperFxOptions.ServiceName, and so does Bobcat's spec assembly, so the common case (Wolverine + one Marten store) assembled two models out of the box and had to restate a name it had already declared.

The fallback is now the service name. An explicit name still wins:

services.AddMarten(opts =>
{
    opts.Connection(connectionString);
    opts.EventModelName = "Ledgers";   // optional; defaults to JasperFxOptions.ServiceName
});

If you were relying on the model being called EventModel, set opts.EventModelName = "EventModel" explicitly.

2. The primary store's Subject now follows StoreName (#5409).

Subject was a literal marten://main that ignored StoreOptions.StoreName, while Identity had always been built from it — so naming a primary store moved one and left the other behind, and Subject is the one consumers key on. Both are now built from StoreName. Tooling that keys on store.Subject (CritterWatch's explorer reads and shard progression ids among them) will see marten://{storename} for a named store where it previously saw marten://main.

A store name is user-supplied text and a uri host is not, so names are sanitized: My Store would otherwise throw UriFormatException and a/b would silently parse its tail as a path. Ordinary names are unchanged.

3. Archiving an already-archived stream is a no-op instead of an error (#5403).

Under UseArchivedStreamPartitioning, a second archive of the same stream raised 23505 on mt_streams_archived_pkey. This also stalled async single-stream projections with IncludeArchivedEvents = true when they processed an Archived marker after an inline snapshot had already archived the stream. Repeated archiving now succeeds and does nothing.

A genuine collision is still reported: a different active stream reusing an archived stream id still fails, rather than being silently swallowed and losing its metadata.

Fixes and improvements

  • #5405 / #5407 — StoreOptions.EventModelName. A host that called AddEventModel("Something", …) assembled two models: its own, and one the store contributed under the default name. The name is configured on StoreOptions and read lazily when the model is assembled, so AddEventModel may be called before or after AddMarten.
  • #5409 — an ancillary store keeps a name it was given. BuildStoreOptions assigned StoreName both before and after the IConfigureMarten<T> chain, so a contribution that named the store was silently reverted to the marker type's name.
  • #5403 — partitioned archiving performance. Because is_archived is the list partition key on both mt_streams and mt_events, the archive function's new predicates also let PostgreSQL prune to the active partition, where the previous statements had to consider both.
  • JasperFx 2.69.3. Codegen output is now deterministic (#832) — an ImHashMap keyed by Frame, which does not override GetHashCode, meant statement order followed identity hash codes and varied per process. Every Event Model slice also now carries the store it came from, so two stores projecting a document of the same simple name leave a recorded disagreement rather than one silently winning (#836).

Compatibility

Binary compatible with 9.34.0. The public AddMarten / AddMartenStore<T> surface is unchanged — verified by diffing the extracted signature list against the 9.34.0 tag. New configuration is additive on StoreOptions.