diff --git a/Directory.Build.props b/Directory.Build.props index 95416a2..68032b4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -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. --> - 2.6.1 + + 2.8.0 13 1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618 Jeremy D. Miller;Jaedyn Tonee diff --git a/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs b/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs index 33f972a..a0a583c 100644 --- a/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs +++ b/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs @@ -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)source).AddTenantAsync("acme"); + var resolved = await ((IDynamicTenantSource)source).AddTenantAsync("acme"); source.AutoAssigned.ShouldBe(["acme"]); + resolved.ShouldBe("shard-acme"); // resolved database id / partition suffix } [Fact] @@ -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] @@ -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"); @@ -167,10 +169,11 @@ internal class AutoAssignTenantSource : ValueOnlyTenantSource, IDynamicTenantSou { public List AutoAssigned { get; } = new(); - public Task AddTenantAsync(string tenantId, CancellationToken token = default) + public Task 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}"); } } diff --git a/src/JasperFx/Descriptors/HttpChainDescriptor.cs b/src/JasperFx/Descriptors/HttpChainDescriptor.cs index 3408c85..6b0f15b 100644 --- a/src/JasperFx/Descriptors/HttpChainDescriptor.cs +++ b/src/JasperFx/Descriptors/HttpChainDescriptor.cs @@ -109,11 +109,10 @@ public class HttpChainDescriptor : OptionsDescription /// OpenAPI tags applied to this chain. public new List Tags { get; set; } = new(); - /// Middleware steps in apply order. - public List Middleware { get; set; } = new(); - - /// Postprocessor steps in apply order. - public List 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. /// /// Cascading message types this chain may publish — the @@ -122,9 +121,6 @@ public class HttpChainDescriptor : OptionsDescription /// public List CascadingMessageTypes { get; set; } = new(); - /// Service dependencies the chain resolves at runtime. - public List ServiceDependencies { get; set; } = new(); - /// Full OpenAPI shape of the operation, when discoverable. public OpenApiOperationDescriptor? OpenApi { get; set; } diff --git a/src/JasperFx/Descriptors/HttpGraphUsage.cs b/src/JasperFx/Descriptors/HttpGraphUsage.cs index 01a7466..9c7fe0f 100644 --- a/src/JasperFx/Descriptors/HttpGraphUsage.cs +++ b/src/JasperFx/Descriptors/HttpGraphUsage.cs @@ -80,11 +80,8 @@ public class HttpGraphUsage : OptionsDescription /// public List PolicyNames { get; set; } = new(); - /// - /// Type names of middleware that may apply across the graph (the - /// WolverineHttpOptions.Middleware registry). - /// - public List 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. /// /// Names of tenant-detection strategies registered on the graph. diff --git a/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs b/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs index 42313a8..24a2dba 100644 --- a/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs +++ b/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs @@ -1,29 +1,8 @@ namespace JasperFx.Descriptors; -/// -/// One ordered step in a chain's middleware / postprocessor pipeline. -/// -public class MiddlewareStepDescriptor -{ - /// Type that owns the step (handler / middleware / policy). - public string TypeFullName { get; set; } = ""; - - /// Method name within the owning type. - public string MethodName { get; set; } = ""; - - /// - /// Step kind — "Middleware", "Postprocessor", - /// "Validation", "Audit", etc. Used for visual - /// grouping in the Pipeline tab. - /// - public string Kind { get; set; } = ""; - - /// - /// Compact display string for the row (e.g. - /// "OrderHandler.Before(Order)"). - /// - 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. /// /// Narrow per-namespace prefix applied via Wolverine's diff --git a/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs b/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs index a2ad8e2..a3449a3 100644 --- a/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs +++ b/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs @@ -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 from its default - /// . + /// . Returns the + /// resolved assignment (database id / partition suffix) from the first source that provisioned the + /// tenant, or when no dynamic source is registered. /// - public static Task AddTenantAsync(this IServiceProvider services, string tenantId, + public static async Task 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; + } /// /// Disable (soft delete) a tenant across every registered dynamic tenant source. @@ -91,7 +102,7 @@ public static Task AddTenantAsync(this IHost host, string tenantId, string conne => host.Services.AddTenantAsync(tenantId, connectionValue); /// - public static Task AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default) + public static Task AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default) => host.Services.AddTenantAsync(tenantId, token); /// diff --git a/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs b/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs index feaf735..acaff5d 100644 --- a/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs +++ b/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs @@ -14,15 +14,16 @@ public interface IDynamicTenantSource : ITenantedSource /// /// 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 - /// ; 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 ; auto-assign sources (e.g. Marten's sharded tenancy) + /// override it, while caller-supplies-value sources keep + /// . See jasperfx#413 (split from #409). /// - Task AddTenantAsync(string tenantId, CancellationToken token = default) + Task 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."); /// /// Disable a tenant (soft delete). The tenant data is preserved but