Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@
admin entrypoint (DynamicTenancyAdminExtensions) that dispatches add/disable/enable/remove
to every registered dynamic tenant source — graceful no-op when none — so CritterWatch never
sniffs the concrete tenancy type. -->
<JasperFxVersion>2.6.1</JasperFxVersion>
<!-- 2.8.0: consolidated release. Folds in #411 (remove redundant HttpChainDescriptor
pipeline-introspection fields — Middleware/Postprocessors/ServiceDependencies, the
MiddlewareStepDescriptor type, and graph-level HttpGraphUsage.MiddlewareTypes; the same
pipeline info is available on demand via generated source code) and #413 (the auto-assign
IDynamicTenantSource<T>.AddTenantAsync(tenantId, CancellationToken) overload now returns
Task<string> — the resolved database id / partition suffix — for pooled/sharded models).
#411 and #413 had merged into stacked PR bases rather than main; this brings their content
onto main. Single published version supersedes the interim 2.6.0/2.6.1/2.7.0 bumps. -->
<JasperFxVersion>2.8.0</JasperFxVersion>
<LangVersion>13</LangVersion>
<NoWarn>1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618</NoWarn>
<Authors>Jeremy D. Miller;Jaedyn Tonee</Authors>
Expand Down
13 changes: 8 additions & 5 deletions src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public async Task auto_assign_overload_throws_by_default()
public async Task auto_assign_overload_is_used_when_a_source_supports_it()
{
var source = new AutoAssignTenantSource();
await ((IDynamicTenantSource<string>)source).AddTenantAsync("acme");
var resolved = await ((IDynamicTenantSource<string>)source).AddTenantAsync("acme");
source.AutoAssigned.ShouldBe(["acme"]);
resolved.ShouldBe("shard-acme"); // resolved database id / partition suffix
}

[Fact]
Expand All @@ -41,9 +42,10 @@ public async Task auto_assign_add_dispatches_to_registered_sources()
var source = new AutoAssignTenantSource();
var services = provider(source);

await services.AddTenantAsync("acme");
var resolved = await services.AddTenantAsync("acme");

source.AutoAssigned.ShouldBe(["acme"]);
resolved.ShouldBe("shard-acme");
}

[Fact]
Expand Down Expand Up @@ -87,7 +89,7 @@ public async Task is_a_graceful_no_op_when_no_dynamic_source_is_registered()

// None of these should throw with no source registered
await services.AddTenantAsync("acme", "Host=db1");
await services.AddTenantAsync("acme");
(await services.AddTenantAsync("acme")).ShouldBeNull(); // no source -> no resolved assignment
await services.DisableTenantAsync("acme");
await services.EnableTenantAsync("acme");
await services.RemoveTenantAsync("acme");
Expand Down Expand Up @@ -167,10 +169,11 @@ internal class AutoAssignTenantSource : ValueOnlyTenantSource, IDynamicTenantSou
{
public List<string> AutoAssigned { get; } = new();

public Task AddTenantAsync(string tenantId, CancellationToken token = default)
public Task<string> AddTenantAsync(string tenantId, CancellationToken token = default)
{
AutoAssigned.Add(tenantId);
return Task.CompletedTask;
// Simulate the assignment strategy resolving the tenant onto a shard / partition.
return Task.FromResult($"shard-{tenantId}");
}
}

Expand Down
12 changes: 4 additions & 8 deletions src/JasperFx/Descriptors/HttpChainDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,10 @@ public class HttpChainDescriptor : OptionsDescription
/// <summary>OpenAPI tags applied to this chain.</summary>
public new List<string> Tags { get; set; } = new();

/// <summary>Middleware steps in apply order.</summary>
public List<MiddlewareStepDescriptor> Middleware { get; set; } = new();

/// <summary>Postprocessor steps in apply order.</summary>
public List<MiddlewareStepDescriptor> Postprocessors { get; set; } = new();
// jasperfx#411: the Middleware / Postprocessors / ServiceDependencies pipeline-introspection fields
// were removed. The same operator-facing information is available on demand via the chain's generated
// source code (RequestHandlerSourceCode in Wolverine.CritterWatch) — the generated C# IS the compiled
// pipeline — so the descriptor copies were redundant payload.

/// <summary>
/// Cascading message types this chain may publish — the
Expand All @@ -122,9 +121,6 @@ public class HttpChainDescriptor : OptionsDescription
/// </summary>
public List<TypeDescriptor> CascadingMessageTypes { get; set; } = new();

/// <summary>Service dependencies the chain resolves at runtime.</summary>
public List<TypeDescriptor> ServiceDependencies { get; set; } = new();

/// <summary>Full OpenAPI shape of the operation, when discoverable.</summary>
public OpenApiOperationDescriptor? OpenApi { get; set; }

Expand Down
7 changes: 2 additions & 5 deletions src/JasperFx/Descriptors/HttpGraphUsage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,8 @@ public class HttpGraphUsage : OptionsDescription
/// </summary>
public List<string> PolicyNames { get; set; } = new();

/// <summary>
/// Type names of middleware that may apply across the graph (the
/// <c>WolverineHttpOptions.Middleware</c> registry).
/// </summary>
public List<string> MiddlewareTypes { get; set; } = new();
// jasperfx#411: graph-level MiddlewareTypes (the WolverineHttpOptions.Middleware registry) was removed
// for API consistency with the per-chain pipeline-introspection removal.

/// <summary>
/// Names of tenant-detection strategies registered on the graph.
Expand Down
27 changes: 3 additions & 24 deletions src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
namespace JasperFx.Descriptors;

/// <summary>
/// One ordered step in a chain's middleware / postprocessor pipeline.
/// </summary>
public class MiddlewareStepDescriptor
{
/// <summary>Type that owns the step (handler / middleware / policy).</summary>
public string TypeFullName { get; set; } = "";

/// <summary>Method name within the owning type.</summary>
public string MethodName { get; set; } = "";

/// <summary>
/// Step kind — <c>"Middleware"</c>, <c>"Postprocessor"</c>,
/// <c>"Validation"</c>, <c>"Audit"</c>, etc. Used for visual
/// grouping in the Pipeline tab.
/// </summary>
public string Kind { get; set; } = "";

/// <summary>
/// Compact display string for the row (e.g.
/// <c>"OrderHandler.Before(Order)"</c>).
/// </summary>
public string Description { get; set; } = "";
}
// jasperfx#411: MiddlewareStepDescriptor (one step in a chain's middleware/postprocessor pipeline) was
// removed along with HttpChainDescriptor.Middleware/Postprocessors. Pipeline information is available on
// demand via the chain's generated source code rather than mirrored into the descriptor payload.

/// <summary>
/// Narrow per-namespace prefix applied via Wolverine's
Expand Down
19 changes: 15 additions & 4 deletions src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,22 @@ public static Task AddTenantAsync(this IServiceProvider services, string tenantI
/// Add a tenant whose connection/partition is auto-assigned by the source (sharded/partitioned style;
/// no caller-supplied value). Dispatched to every registered dynamic tenant source — a source that
/// requires a connection value will surface <see cref="NotSupportedException" /> from its default
/// <see cref="IDynamicTenantSource{T}.AddTenantAsync(string,CancellationToken)" />.
/// <see cref="IDynamicTenantSource{T}.AddTenantAsync(string,CancellationToken)" />. Returns the
/// resolved assignment (database id / partition suffix) from the first source that provisioned the
/// tenant, or <see langword="null" /> when no dynamic source is registered.
/// </summary>
public static Task AddTenantAsync(this IServiceProvider services, string tenantId,
public static async Task<string?> AddTenantAsync(this IServiceProvider services, string tenantId,
CancellationToken token = default)
=> forEachSource(services, source => source.AddTenantAsync(tenantId, token));
{
string? resolved = null;
foreach (var source in services.DynamicTenantSources())
{
var assignment = await source.AddTenantAsync(tenantId, token).ConfigureAwait(false);
resolved ??= assignment;
}

return resolved;
}

/// <summary>
/// Disable (soft delete) a tenant across every registered dynamic tenant source.
Expand Down Expand Up @@ -91,7 +102,7 @@ public static Task AddTenantAsync(this IHost host, string tenantId, string conne
=> host.Services.AddTenantAsync(tenantId, connectionValue);

/// <summary><inheritdoc cref="AddTenantAsync(IServiceProvider,string,CancellationToken)" /></summary>
public static Task AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default)
public static Task<string?> AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default)
=> host.Services.AddTenantAsync(tenantId, token);

/// <summary><inheritdoc cref="DisableTenantAsync(IServiceProvider,string)" /></summary>
Expand Down
15 changes: 8 additions & 7 deletions src/JasperFx/MultiTenancy/IDynamicTenantSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ public interface IDynamicTenantSource<T> : ITenantedSource<T>
/// <summary>
/// Provision a new tenant whose connection/partition is auto-assigned by the source's configured
/// strategy — e.g. sharded-database pooling or per-tenant event partitions — rather than a
/// caller-supplied connection value. The default implementation throws
/// <see cref="NotSupportedException" />; sources that auto-assign (e.g. Marten's sharded tenancy)
/// override it. This is the uniform "add a tenant" entrypoint for provisioning models that own the
/// physical assignment, so a tool such as CritterWatch never has to sniff the concrete tenancy type.
/// See jasperfx#409.
/// caller-supplied connection value. Returns the resolved assignment: the database id (sharded pool)
/// or partition suffix (managed partitions) the tenant landed on, so the caller (e.g. CritterWatch)
/// learns where it was placed without sniffing the concrete tenancy type. The default implementation
/// throws <see cref="NotSupportedException" />; auto-assign sources (e.g. Marten's sharded tenancy)
/// override it, while caller-supplies-value sources keep
/// <see cref="AddTenantAsync(string,T)" />. See jasperfx#413 (split from #409).
/// </summary>
Task AddTenantAsync(string tenantId, CancellationToken token = default)
Task<string> AddTenantAsync(string tenantId, CancellationToken token = default)
=> throw new NotSupportedException(
$"This tenant source ({GetType().FullName}) requires a caller-supplied connection value; call AddTenantAsync(tenantId, connectionValue) instead, or use a source that supports auto-assignment.");
$"This tenant source ({GetType().FullName}) does not support auto-assignment; call AddTenantAsync(tenantId, connectionValue) with a caller-supplied connection value instead.");

/// <summary>
/// Disable a tenant (soft delete). The tenant data is preserved but
Expand Down
Loading