Skip to content

#4668 — tenant-aware RebuildSingleStreamAsync overloads on AdvancedOperations#4674

Merged
jeremydmiller merged 1 commit into
masterfrom
feature/4668-rebuild-single-stream-tenant-overloads
Jun 5, 2026
Merged

#4668 — tenant-aware RebuildSingleStreamAsync overloads on AdvancedOperations#4674
jeremydmiller merged 1 commit into
masterfrom
feature/4668-rebuild-single-stream-tenant-overloads

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #4668.

Adds two new overloads on AdvancedOperations:

Task RebuildSingleStreamAsync<T>(string streamKey, string tenantId, CancellationToken token = default)
Task RebuildSingleStreamAsync<T>(Guid   streamId,  string tenantId, CancellationToken token = default)

Backwards-compatibility shape

New methods, not default-argument additions on the existing signatures.
Per the issue's explicit request — keeping (string id, CT) and (Guid id, CT)
bind-compatible at the call site for every existing caller. No optional
argument anywhere on this path.

Implementation

Overload Session Notes
(string streamKey, string tenantId, CT) LightweightSession(tenantId) Mirrors the existing (string, CT) overload's session shape, scoped to the tenant.
(Guid streamId, string tenantId, CT) LightweightSession(new SessionOptions { ConcurrencyChecks = Disabled, TenantId = tenantId }) Mirrors the existing Guid overload's ConcurrencyChecks.Disabled posture, with TenantId set on the options.

Tenant id flows through both the AggregateStreamAsync read and the
projected-document upsert.

Tests

src/MultiTenancyTests/rebuild_single_stream_tenant_overloads.cs — four
tests covering the matrix of tenancy style × stream-identity:

Test Tenancy Stream identity
conjoined_rebuild_by_guid_with_tenant_id Conjoined Guid
conjoined_rebuild_by_string_key_with_tenant_id Conjoined String
per_database_rebuild_by_guid_with_tenant_id MultiTenantedWithSingleServer Guid
per_database_rebuild_by_string_key_with_tenant_id MultiTenantedWithSingleServer String

Each test:

  1. Seeds two tenants with different event shapes (e.g. 3× AEvent vs 2× BEvent).
  2. Calls the new overload to rebuild tenant A's stream.
  3. Asserts tenant A's projection reflects only A's events.
  4. Repeats for tenant B.
  5. (Conjoined-Guid only) pins cross-tenant isolation: loading tenant B's
    stream id through a tenant A query session returns null.

The per-database variants exercise MultiTenantedWithSingleServer
routing end-to-end (a host + AddMarten + ApplyAllDatabaseChangesOnStartup
bootstrap), so the rebuild's session has to route to each tenant's
physical database for both the event load and the upsert.

Verification

Local on net9.0:

Step Result
Build (Marten + MultiTenancyTests) clean
4 new tests all pass, total 1.7s

🤖 Generated with Claude Code

…erations

Adds two new overloads on AdvancedOperations:

  Task RebuildSingleStreamAsync<T>(string streamKey, string tenantId, CancellationToken token = default)
  Task RebuildSingleStreamAsync<T>(Guid streamId,     string tenantId, CancellationToken token = default)

Per the issue: under conjoined multi-tenancy and under per-database
multi-tenancy users can open tenant-scoped sessions and append/aggregate
events by tenant, but the store-level rebuild API had no way to express
"rebuild this stream for tenant X" — forcing users to drop out of
RebuildSingleStreamAsync and aggregate-then-persist manually.

Implementation:

* string overload: LightweightSession(tenantId) + AggregateStreamAsync(key) +
  Store + Save. Tenant id flows through both the read and the upsert.
* Guid overload: mirrors the existing Guid overload's SessionOptions
  posture (ConcurrencyChecks = Disabled) + carries TenantId on the
  options object. Same read/upsert tenant routing as the string overload.

Backwards-compat shape:
NEW METHODS, NOT default-argument additions on the existing signatures.
Per the issue's explicit request — keeping the (string id, CT) and
(Guid id, CT) overloads bind-compatible at the call site for every
existing caller. No optional argument anywhere on this path.

Tests (src/MultiTenancyTests/rebuild_single_stream_tenant_overloads.cs):

Four tests covering the matrix of tenancy style × stream-identity:

* conjoined_rebuild_by_guid_with_tenant_id
* conjoined_rebuild_by_string_key_with_tenant_id
* per_database_rebuild_by_guid_with_tenant_id (MultiTenantedWithSingleServer)
* per_database_rebuild_by_string_key_with_tenant_id (MultiTenantedWithSingleServer)

Each test:
* Seeds two tenants with different event shapes (3x AEvent vs 2x BEvent).
* Calls the new overload to rebuild tenant A's stream.
* Asserts tenant A's projection reflects ONLY A's events.
* Repeats for tenant B; asserts B's projection reflects only B's events.
* Conjoined-Guid test additionally pins cross-tenant isolation:
  loading tenant B's stream id through a tenant A session returns null
  (the rebuild stayed scoped to the supplied tenant).

The per-database variants exercise MultiTenantedWithSingleServer routing
end-to-end (a host + AddMarten + ApplyAllDatabaseChangesOnStartup
bootstrap), so the rebuild's session has to route to each tenant's
physical database for both the event load and the upsert.

Verified locally on net9.0:
* Build clean (Marten + MultiTenancyTests).
* All 4 new tests pass; total 1.7s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tenant-aware overloads for RebuildSingleStreamAsync

1 participant