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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace PatternKit.Benchmarks.Coverage;
public enum BenchmarkRoute
{
Fluent,
SourceGenerated
SourceGenerated,
HostingIntegration
}

public enum BenchmarkPhase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ public static class PatternBenchmarkCoverage
public static IEnumerable<PatternBenchmarkRoute> SourceGeneratedExecutionRoutes => Routes
.Where(static route => route.Route == BenchmarkRoute.SourceGenerated && route.Phase == BenchmarkPhase.Execution);

public static IEnumerable<PatternBenchmarkRoute> HostingIntegrationRoutes => Routes
.Where(static route => route.Route == BenchmarkRoute.HostingIntegration);

private static IReadOnlyList<PatternBenchmarkRoute> CreateRoutes()
{
var catalog = new PatternKitPatternCatalog();
var hostingCatalog = new PatternKitHostingIntegrationCatalog();
var hostingIntegrations = hostingCatalog.Integrations
.Where(static integration => integration.Kind == PatternHostingIntegrationKind.ReusableHostingExtension)
.ToDictionary(static integration => integration.PatternName, StringComparer.Ordinal);
var routes = new List<PatternBenchmarkRoute>(catalog.Patterns.Count * 4);

foreach (var pattern in catalog.Patterns)
Expand Down Expand Up @@ -64,6 +71,18 @@ private static IReadOnlyList<PatternBenchmarkRoute> CreateRoutes()
implementation.ExampleSourcePath,
implementation.ExampleTestPath,
implementation.ExampleDocumentationPath));

if (hostingIntegrations.TryGetValue(pattern.Name, out var hostingIntegration))
{
routes.Add(new(
pattern.Name,
pattern.Family,
BenchmarkRoute.HostingIntegration,
BenchmarkPhase.Construction,
"src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs",
hostingIntegration.TestPath,
hostingIntegration.DocumentationPath));
}
}

return routes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,16 @@ public class SourceGeneratedExecutionPatternMatrixBenchmarks : PatternMatrixBenc
public int SourceGenerated_Execution_Route()
=> ValidateRoute(Route);
}

[BenchmarkCategory("Coverage", "PatternMatrix", "HostingIntegration")]
public class HostingIntegrationPatternMatrixBenchmarks : PatternMatrixBenchmarkBase
{
[ParamsSource(nameof(Routes))]
public PatternBenchmarkRoute Route { get; set; } = default!;

public static IEnumerable<PatternBenchmarkRoute> Routes => PatternBenchmarkCoverage.HostingIntegrationRoutes;

[Benchmark(Description = "Hosting: IServiceCollection integration coverage route")]
public int HostingIntegration_Route()
=> ValidateRoute(Route);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
using PatternKit.Cloud.Bulkhead;
using PatternKit.Cloud.CircuitBreaker;
using PatternKit.Cloud.PriorityQueue;
using PatternKit.Cloud.QueueLoadLeveling;
using PatternKit.Cloud.RateLimiting;
using PatternKit.Cloud.Retry;
using PatternKit.Hosting.DependencyInjection;
using PatternKit.Messaging;
using PatternKit.Messaging.Channels;
using PatternKit.Messaging.Reliability;
using PatternKit.Messaging.Storage;

namespace PatternKit.Benchmarks.Hosting;

[BenchmarkCategory("Hosting", "IServiceCollection")]
public class HostingExtensionsBenchmarks
{
private ServiceProvider _provider = default!;

[GlobalSetup]
public void Setup()
{
_provider = CreateConfiguredServices().BuildServiceProvider(validateScopes: true);
}

[GlobalCleanup]
public void Cleanup()
{
_provider.Dispose();
}

[Benchmark(Baseline = true, Description = "Hosting: register reusable PatternKit primitives")]
[BenchmarkCategory("Construction", "Registration")]
public IServiceCollection Construction_RegisterReusablePrimitives()
=> CreateConfiguredServices();

[Benchmark(Description = "Hosting: build provider for reusable PatternKit primitives")]
[BenchmarkCategory("Construction", "Provider")]
public ServiceProvider Construction_BuildProvider()
=> CreateConfiguredServices().BuildServiceProvider(validateScopes: true);
Comment on lines +41 to +42

[Benchmark(Description = "Hosting: resolve and execute reusable PatternKit primitives")]
[BenchmarkCategory("Execution", "Resolve")]
public async Task<int> Execution_ResolveAndExecuteAsync()
{
var channel = _provider.GetRequiredService<MessageChannel<OrderCommand>>();
var store = _provider.GetRequiredService<MessageStore<OrderCommand>>();
var delivery = _provider.GetRequiredService<GuaranteedDeliveryQueue<OrderCommand>>();
var retry = _provider.GetRequiredService<RetryPolicy<ServiceReply>>();
var breaker = _provider.GetRequiredService<CircuitBreakerPolicy<ServiceReply>>();
var bulkhead = _provider.GetRequiredService<BulkheadPolicy<ServiceReply>>();
var rateLimit = _provider.GetRequiredService<RateLimitPolicy<ServiceReply>>();
var leveling = _provider.GetRequiredService<QueueLoadLevelingPolicy<ServiceReply>>();
var priority = _provider.GetRequiredService<PriorityQueuePolicy<WorkItem, int>>();

var message = Message<OrderCommand>.Create(new("order-1", 125m));
var send = channel.Send(message);
var append = store.Append(message);
await delivery.EnqueueAsync(message);
var lease = await delivery.TryReceiveAsync();

var retryResult = retry.Execute(static () => new ServiceReply(true));
var breakerResult = breaker.Execute(static () => new ServiceReply(true));
var bulkheadResult = bulkhead.Execute(static () => new ServiceReply(true));
var rateLimitResult = rateLimit.Execute("tenant-a", static () => new ServiceReply(true));
var levelingResult = leveling.Execute(static () => new ServiceReply(true));
priority.Enqueue(new("slow", 1));
priority.Enqueue(new("fast", 10));
var next = priority.Dequeue();

return (send.Accepted ? 1 : 0)
+ append.StoredMessage.Message.Payload.OrderId.Length
+ (lease is null ? 0 : 1)
+ (retryResult.Succeeded ? 1 : 0)
+ (breakerResult.Succeeded ? 1 : 0)
+ (bulkheadResult.Succeeded ? 1 : 0)
+ (rateLimitResult.Allowed ? 1 : 0)
+ (levelingResult.Accepted ? 1 : 0)
+ (next.Item?.Priority ?? 0);
}

private static IServiceCollection CreateConfiguredServices()
{
var services = new ServiceCollection();
services
.AddPatternKitMessageChannel<OrderCommand>(
"orders",
builder => builder.WithCapacity(32, MessageChannelBackpressurePolicy.Reject))
.AddPatternKitMessageStore<OrderCommand>(
"order-store",
builder => builder.IdentifyBy(static (message, _) => message.Payload.OrderId))
.AddPatternKitGuaranteedDelivery<OrderCommand>(
builder => builder
.Name("order-delivery")
.LeaseDuration(TimeSpan.FromSeconds(5))
.MaxDeliveryAttempts(2))
.AddPatternKitRetryPolicy<ServiceReply>(
"inventory-retry",
builder => builder.WithMaxAttempts(2).HandleResult(static reply => !reply.Available))
.AddPatternKitCircuitBreakerPolicy<ServiceReply>(
"inventory-breaker",
builder => builder.WithFailureThreshold(1).HandleResult(static reply => !reply.Available))
.AddPatternKitBulkheadPolicy<ServiceReply>(
"inventory-bulkhead",
builder => builder.WithMaxConcurrency(2))
.AddPatternKitRateLimitPolicy<ServiceReply>(
"inventory-rate-limit",
builder => builder.WithPermitLimit(64).WithWindow(TimeSpan.FromMinutes(1)))
.AddPatternKitQueueLoadLevelingPolicy<ServiceReply>(
"inventory-leveling",
builder => builder.WithMaxConcurrentWorkers(2).WithMaxQueueLength(32))
.AddPatternKitPriorityQueue<WorkItem, int>(
static item => item.Priority,
"inventory-priority");

return services;
}

private sealed record OrderCommand(string OrderId, decimal Total);
private sealed record ServiceReply(bool Available);
private sealed record WorkItem(string Id, int Priority);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\PatternKit.Examples\PatternKit.Examples.csproj" />
<ProjectReference Include="..\..\src\PatternKit.Core\PatternKit.Core.csproj" />
<ProjectReference Include="..\..\src\PatternKit.Hosting.Extensions\PatternKit.Hosting.Extensions.csproj" />
<ProjectReference Include="..\..\src\PatternKit.Generators.Abstractions\PatternKit.Generators.Abstractions.csproj" />
<ProjectReference Include="..\..\src\PatternKit.Generators\PatternKit.Generators.csproj"
OutputItemType="Analyzer"
Expand Down
22 changes: 19 additions & 3 deletions docs/guides/benchmark-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
| Gateway Routing | Execution | 23.63 ns | 200 B | 20.13 ns | 168 B | Generated reduced execution time and allocation for the inventory routing workflow. |
| Health Endpoint Monitoring | Construction | 35.68 ns | 264 B | 35.25 ns | 264 B | Same allocation; generated was slightly faster in this microbenchmark. |
| Health Endpoint Monitoring | Execution | 38.50 ns | 248 B | 31.67 ns | 248 B | Same allocation; generated was faster for the fulfillment health evaluation workflow. |
| Hosting Extensions | Construction | reportable | reportable | reportable | reportable | Dedicated route measures reusable `IServiceCollection` registration and provider build overhead for package-level hosting APIs. |
| Hosting Extensions | Execution | reportable | reportable | reportable | reportable | Dedicated route measures resolving and executing reusable PatternKit primitives through Microsoft.Extensions.DependencyInjection. |
| Idempotent Receiver | Construction | 17.022 ns | 184 B | 17.021 ns | 184 B | Effectively equivalent for this microbenchmark. |
| Idempotent Receiver | Execution | 99.419 ns | 608 B | 99.051 ns | 608 B | Effectively equivalent for idempotent command handling. |
| Inbox | Construction | 22.259 ns | 208 B | 21.675 ns | 208 B | Same allocation; generated was slightly faster in this microbenchmark. |
Expand Down Expand Up @@ -218,7 +220,7 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149

## Coverage Matrix Summary

The coverage matrix currently publishes 101 catalog patterns and 404 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution.
The coverage matrix currently publishes 101 catalog patterns and 404 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution. The reusable hosting integration matrix publishes 9 reusable hosting integration route results for package-level `IServiceCollection` registrations.

| Category | Patterns | Published route results |
| --- | ---: | ---: |
Expand All @@ -231,8 +233,22 @@ The coverage matrix currently publishes 101 catalog patterns and 404 pattern rou
| Structural | 7 | 28 |

The generator matrix currently publishes 96 generator source route results.

## Pattern Matrix Results

## Hosting Integration Matrix Results

| Pattern | Route | Registration | Source | Tests | Docs |
| --- | --- | --- | --- | --- | --- |
| Bulkhead | `IServiceCollection` | `AddPatternKitBulkheadPolicy<TResult>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Circuit Breaker | `IServiceCollection` | `AddPatternKitCircuitBreakerPolicy<TResult>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Guaranteed Delivery | `IServiceCollection` | `AddPatternKitGuaranteedDelivery<TPayload>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Message Channel | `IServiceCollection` | `AddPatternKitMessageChannel<TPayload>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Message Store | `IServiceCollection` | `AddPatternKitMessageStore<TPayload>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Priority Queue | `IServiceCollection` | `AddPatternKitPriorityQueue<TItem, TPriority>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Queue-Based Load Leveling | `IServiceCollection` | `AddPatternKitQueueLoadLevelingPolicy<TResult>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Rate Limiting | `IServiceCollection` | `AddPatternKitRateLimitPolicy<TResult>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |
| Retry | `IServiceCollection` | `AddPatternKitRetryPolicy<TResult>` | `src/PatternKit.Hosting.Extensions/DependencyInjection/PatternKitServiceCollectionExtensions.cs` | `test/PatternKit.Hosting.Extensions.Tests/DependencyInjection/PatternKitServiceCollectionExtensionsTests.cs` | `docs/guides/hosting-extensions.md` |

## Pattern Matrix Results

| Category | Pattern | Fluent construction | Fluent execution | Generated construction | Generated execution |
| --- | --- | --- | --- | --- | --- |
Expand Down
18 changes: 18 additions & 0 deletions docs/guides/hosting-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,22 @@ services.AddPatternKitMessageChannel<OrderCommand>(
lifetime: ServiceLifetime.Scoped);
```

## Current Reusable Coverage

Every catalog pattern is importable through the production example catalog. The package-level reusable hosting surface currently covers the patterns below directly from `PatternKit.Hosting.Extensions`:

| Pattern | Registration API | Primary use |
| --- | --- | --- |
| Message Channel | `AddPatternKitMessageChannel<TPayload>` | Register bounded in-memory channels for application-owned message contracts. |
| Message Store | `AddPatternKitMessageStore<TPayload>` | Register queryable message history or audit stores. |
| Guaranteed Delivery | `AddPatternKitGuaranteedDelivery<TPayload>` | Register durable-delivery queues with custom stores when needed. |
| Retry | `AddPatternKitRetryPolicy<TResult>` | Register named retry policies for synchronous service calls. |
| Circuit Breaker | `AddPatternKitCircuitBreakerPolicy<TResult>` | Register named circuit breakers with shared state. |
| Bulkhead | `AddPatternKitBulkheadPolicy<TResult>` | Register concurrency and queue isolation policies. |
| Rate Limiting | `AddPatternKitRateLimitPolicy<TResult>` | Register per-key rate windows. |
| Queue-Based Load Leveling | `AddPatternKitQueueLoadLevelingPolicy<TResult>` | Register queue-backed worker policies. |
| Priority Queue | `AddPatternKitPriorityQueue<TItem, TPriority>` | Register priority-ordered work queues. |

The hosting integration catalog in `PatternKit.Examples.ProductionReadiness` audits every catalog pattern against this reusable surface and the example-level `AddPatternKitExamples()` import path. BenchmarkDotNet coverage includes a dedicated `HostingIntegration` matrix route for every reusable registration above.

The package is intentionally separate from `PatternKit.Examples`. Example registrations remain useful for demos and documentation, while `PatternKit.Hosting.Extensions` is the production-oriented integration surface for existing ASP.NET Core, worker service, and generic host applications.
Loading
Loading