Skip to content

[Audit] Marten ↔ Polecat dedup — multi-tenancy slice #224

Description

@jeremydmiller

Status: in-flight
Third slice of the dedup audit deliverable under pillar #214.
Prior slices: #218 (database-manipulation), #219 (events / projections).
Scope: multi-tenancy strategies cluster — anything in Marten or Polecat that fits Rule 1 (non-database) targeting JasperFx.MultiTenancy, or Rule 2 (database) targeting Weasel.Core.MultiTenancy.

Headline

This slice is mostly already done, much like the events/projections slice. The heavy consolidation of the multi-tenancy substrate — IHasTenantId, TenantId, TenantIdStyle, UnknownTenantIdException, dynamic / static tenant sources — has already happened in JasperFx.MultiTenancy. The strategy implementations (MasterTableTenancy, SeparateDatabaseTenancy, etc.) are database-coupled and rightfully stay product-side. The shared options shell (MasterTableTenancyOptions<T>) and the assignment strategies (HashTenantAssignment, SmallestTenantAssignment, ExplicitTenantAssignment, ITenantDatabasePool) already live in Weasel.Core.MultiTenancy.

The audit found exactly two Polecat-side rows that warrant migration, plus a low-priority ergonomic addition to JasperFx.

Active migrations (sub-issues filed)

  • Row 1 — Polecat ITenanted should extend IHasTenantId
    Marten's Marten.Metadata.ITenanted : IHasTenantId already extends the canonical JasperFx marker (a one-line implementation). Polecat's Polecat.Metadata.ITenanted re-declares its own string TenantId { get; set; } property without extending IHasTenantId, so Polecat-side framework code can't accept arbitrary IHasTenantId values. Migration: Polecat's ITenanted extends IHasTenantId (drop the local TenantId property since the base contributes it). Rule 1.
    → polecat#TBD (filed below)

  • Row 2 — Polecat UnknownTenantExceptionJasperFx.MultiTenancy.UnknownTenantIdException
    Polecat has Polecat.Exceptions.UnknownTenantException with a TenantId property and a Polecat-specific message. JasperFx has JasperFx.MultiTenancy.UnknownTenantIdException with a generic message and no TenantId property. Marten already consumes the JasperFx one directly (see Marten.Storage.ITenancy.GetTenant XML doc). Migration: Polecat drops the local exception; consumes the JasperFx version. Rule 4 (exception duplicated across products) + Rule 1.
    → polecat#TBD (filed below)

Optional JasperFx-side ergonomic addition (low priority)

  • Add TenantId property to JasperFx.MultiTenancy.UnknownTenantIdException
    Polecat's local exception carries a TenantId getter; the canonical JasperFx one does not. The id is in the message string but isn't programmatically accessible. Adding it is additive and unblocks Polecat consumers that want to catch + inspect. Worth doing alongside the Polecat migration so the Polecat side doesn't regress on diagnostics.
    → can be folded into Row 2's Polecat PR via a small JasperFx-side companion, or filed as a standalone JasperFx issue if preferred.

Already in JasperFx.MultiTenancy (no action needed)

The agent confirmed both products correctly consume these:

  • IHasTenantId — marker interface with string? TenantId { get; set; }
  • TenantId — value type
  • TenantIdStyle enum (CaseSensitive, ForceUpperCase, ForceLowerCase) + MaybeCorrectTenantId extension
  • UnknownTenantIdException (Marten consumes; Polecat doesn't yet — Row 2)
  • IDynamicTenantSource — dynamic tenant lookup
  • ITenantedSource — abstract tenanted source

Already in Weasel.Core.MultiTenancy (no action needed)

  • MasterTableTenancyOptions<T> (Marten's MasterTableTenancyOptions is a Postgres NpgsqlDataSource shim over it)
  • ExplicitTenantAssignment, HashTenantAssignment, SmallestTenantAssignment
  • IDatabaseSizingStrategy, ITenantAssignmentStrategy, ITenantDatabasePool
  • MasterTableMultiTenancyExtensions
  • PooledDatabase

Polecat doesn't currently use the master-table tenancy pattern, so there's no Polecat-side gap here.

Correctly product-specific (no migration)

Audited and confirmed these stay where they are — they encode product-specific storage primitives or use-cases without cross-product parallels:

  • Marten.Storage.ITenancy vs Polecat.Storage.ITenancy — different shapes. Marten's extends IDatabaseSource, IDisposable, IDatabaseUser and returns Marten.Storage.Tenant + IMartenDatabase; Polecat's exposes DatabaseCardinality, ConnectionFactory, PolecatDatabase. Both correctly DB-specific.
  • Marten tenancy strategiesDefaultTenancy, MasterTableTenancy, ShardedTenancy, SingleServerMultiTenancy, StaticMultiTenancy. All Postgres-specific.
  • Polecat tenancy strategiesDefaultTenancy, SeparateDatabaseTenancy. SQL-Server-specific.
  • Marten.Storage.TenancyStyleSingle, Conjoined. Marten-only; Polecat doesn't model conjoined tenancy as an enum.
  • Marten.Schema.Indexing.Unique.TenancyScopeGlobal, PerTenant. Index-tenancy scope; no Polecat equivalent.
  • Polecat.Internal.DatabaseCardinality — different concept from Marten's TenancyStyle; describes connection routing, not data-scoping.
  • Polecat.Linq.SqlGeneration.TenantInFilter — LINQ-tenancy filter. SQL-Server-specific.
  • Marten.Metadata.TenancyPolicy — Marten-only document-mapping policy hook.

Slice exit criteria

  • Both active sub-issues closed.
  • No remaining duplicated multi-tenancy abstractions between Marten and Polecat (excluding the product-specific list above).
  • No regressions in either product's tenancy tests.

Method

Same as the prior slices: focused structural compare across marten/src/Marten/Storage/, marten/src/Marten/Metadata/, polecat/src/Polecat/Storage/, polecat/src/Polecat/Metadata/, polecat/src/Polecat/Exceptions/, jasperfx/src/JasperFx/MultiTenancy/, and weasel/src/Weasel.Core/MultiTenancy/. Per-row decisions follow the propose-and-ratify pattern: propose target home + rule classification; ratify in conversation; reflect above.

Next slice

The pillar #214 inventory has four remaining clusters that could be candidate next slices:

  • Document store metadata / descriptors — most of this already moved during the CritterWatch descriptor work, but a sweep would confirm nothing slipped.
  • Schema migration & DDL diffing — Weasel charter territory; lower-priority because divergence is unlikely (both products use Weasel for DDL).
  • Async daemon abstractions — partially confirmed in the events slice; a focused re-audit could close any leftover gaps.
  • Helper utilities / JasperFx.Core overflow — any utility code that's grown product-side and is general-purpose.

Recommendation: document store metadata / descriptors as the next slice — the CritterWatch work was extensive and a verification pass closes the loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions