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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.8" />
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,15 +446,15 @@ var cachedRemoteProxy = Proxy<int, string>.Create(id => remoteProxy.Execute(id))
---

## Patterns Table
PatternKit currently tracks 97 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix.
PatternKit currently tracks 98 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix.

| Category | Count | Patterns |
| --- | ---: | --- |
| Application Architecture | 16 | Activity Tracker, Anti-Corruption Layer, Audit Log, CQRS, Data Mapper, Domain Event, Event Sourcing, Feature Toggle, Identity Map, Materialized View, Repository, Service Layer, Specification, Table Data Gateway, Transaction Script, Unit of Work |
| Behavioral | 11 | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor |
| Cloud Architecture | 17 | Ambassador, Backends for Frontends, Bulkhead, Cache-Aside, Circuit Breaker, External Configuration Store, Gateway Aggregation, Gateway Routing, Health Endpoint Monitoring, Leader Election, Priority Queue, Queue-Based Load Leveling, Rate Limiting, Retry, Scheduler Agent Supervisor, Sidecar, Strangler Fig |
| Creational | 5 | Abstract Factory, Builder, Factory Method, Prototype, Singleton |
| Enterprise Integration | 38 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content-Based Router, Control Bus, Correlation Identifier, Dead Letter Channel, Durable Subscriber, Dynamic Router, Event Notification, Event-Carried State Transfer, Event-Driven Consumer, Invalid Message Channel, Mailbox, Message Bus, Message Channel, Message Envelope, Message Filter, Message History, Message Store, Message Translator, Messaging Bridge, Messaging Gateway, Pipes and Filters, Polling Consumer, Publish-Subscribe, Recipient List, Request-Reply, Resequencer, Routing Slip, Saga / Process Manager, Scatter-Gather, Service Activator, Splitter, Wire Tap |
| Enterprise Integration | 39 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content-Based Router, Control Bus, Correlation Identifier, Dead Letter Channel, Durable Subscriber, Dynamic Router, Event Notification, Event-Carried State Transfer, Event-Driven Consumer, Invalid Message Channel, Mailbox, Message Bus, Message Channel, Message Envelope, Message Expiration, Message Filter, Message History, Message Store, Message Translator, Messaging Bridge, Messaging Gateway, Pipes and Filters, Polling Consumer, Publish-Subscribe, Recipient List, Request-Reply, Resequencer, Routing Slip, Saga / Process Manager, Scatter-Gather, Service Activator, Splitter, Wire Tap |
| Messaging Reliability | 3 | Idempotent Receiver, Inbox, Outbox |
| Structural | 7 | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy |

Expand Down Expand Up @@ -572,6 +572,8 @@ BenchmarkDotNet guidance is documented in [docs/guides/benchmarks.md](docs/guide
| Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. |
| Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. |
| Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. |
| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. |
| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. |
| Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. |
| Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. |
| Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using BenchmarkDotNet.Attributes;
using PatternKit.Examples.Messaging;
using PatternKit.Messaging;
using PatternKit.Messaging.Reliability;

namespace PatternKit.Benchmarks.Messaging;

[BenchmarkCategory("EnterpriseIntegration", "Messaging", "MessageExpiration")]
public class MessageExpirationBenchmarks
{
private static readonly DateTimeOffset Now = new(2026, 5, 27, 12, 0, 0, TimeSpan.Zero);
private static readonly ExpiringOrderCommand Command = new("order-100", "customer-42");

[Benchmark(Baseline = true, Description = "Fluent: create message expiration")]
[BenchmarkCategory("Fluent", "Construction")]
public MessageExpiration<ExpiringOrderCommand> Fluent_CreateMessageExpiration()
=> OrderMessageExpirations.Create(Now);

[Benchmark(Description = "Generated: create message expiration")]
[BenchmarkCategory("Generated", "Construction")]
public MessageExpiration<ExpiringOrderCommand> Generated_CreateMessageExpiration()
=> GeneratedOrderMessageExpiration.Create();

[Benchmark(Description = "Fluent: stamp and evaluate order command")]
[BenchmarkCategory("Fluent", "Execution")]
public OrderExpirationSummary Fluent_StampAndEvaluate()
=> OrderMessageExpirationExampleRunner.RunFluent(Command, Now);

[Benchmark(Description = "Generated: stamp and evaluate order command")]
[BenchmarkCategory("Generated", "Execution")]
public OrderExpirationSummary Generated_StampAndEvaluate()
{
var expiration = GeneratedOrderMessageExpiration.Create();
var message = expiration.Stamp(Message<ExpiringOrderCommand>.Create(Command));
var result = expiration.Evaluate(message);
return new OrderExpirationSummary(result.Expired, result.ExpiresAt, result.Reason);
}
}
20 changes: 20 additions & 0 deletions docs/examples/order-message-expiration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Order Message Expiration

The order message-expiration example shows how to stamp fulfillment commands with a deadline and reject stale commands before processing.

```csharp
var services = new ServiceCollection()
.AddOrderMessageExpirationDemo()
.BuildServiceProvider();

var service = services.GetRequiredService<OrderMessageExpirationService>();
var message = service.Accept(new ExpiringOrderCommand("o-1", "c-1"));
var summary = service.Evaluate(message);
```

The example exposes both paths:

- `OrderMessageExpirations.Create(...)` for fluent construction and deterministic tests.
- `GeneratedOrderMessageExpiration.Create()` for source-generated, contract-style configuration.

The DI extension registers the generated policy, the service, and an example runner so the same shape can be imported into `IServiceCollection` in a worker, ASP.NET Core app, or test host.
3 changes: 3 additions & 0 deletions docs/examples/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
- name: Order Message Filter
href: order-message-filter.md

- name: Order Message Expiration
href: order-message-expiration.md

- name: Order Message Store
href: order-message-store.md

Expand Down
1 change: 1 addition & 0 deletions docs/generators/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ PatternKit includes a Roslyn incremental generator package (`PatternKit.Generato
| [**Dead Letter Channel**](dead-letter-channel.md) | Failed-message capture and replay handoff | `[GenerateDeadLetterChannel]` |
| [**Content Router**](messaging.md#generated-content-router) | Content-based message routing factories | `[GenerateContentRouter]` |
| [**Message Filter**](message-filter.md) | Named allow-rule filters for message consumers | `[GenerateMessageFilter]` |
| [**Message Expiration**](message-expiration.md) | Deadline stamping and stale-message evaluation policies | `[GenerateMessageExpiration]` |
| [**Message Store**](message-store.md) | Message audit, lookup, and replay store factories | `[GenerateMessageStore]` |
| [**Wire Tap**](wire-tap.md) | Side-channel message observability factories | `[GenerateWireTap]` |
| [**Control Bus**](control-bus.md) | Operational command bus factories for message processors | `[GenerateControlBus]` |
Expand Down
27 changes: 27 additions & 0 deletions docs/generators/message-expiration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Message Expiration Generator

`[GenerateMessageExpiration]` emits a typed factory for `MessageExpiration<TPayload>`.

```csharp
[GenerateMessageExpiration(
typeof(OrderCommand),
FactoryName = "Create",
PolicyName = "order-message-expiration",
HeaderName = "x-order-expires-at",
DefaultTtlMilliseconds = 1200000,
ExpiredReason = "Order command expired before fulfillment accepted it.")]
public static partial class GeneratedOrderMessageExpiration;
```

The generated route is useful when expiration metadata is part of a stable messaging contract. It keeps policy names, header names, TTLs, and rejection reasons declared once and benchmarkable.

## Options

| Option | Default | Purpose |
| --- | --- | --- |
| `FactoryName` | `Create` | Generated factory method name. |
| `PolicyName` | `message-expiration` | Name returned in evaluation results. |
| `HeaderName` | `expires-at` | Deadline header. |
| `DefaultTtlMilliseconds` | `0` | Positive values configure the default `Stamp` TTL. |
| `PreserveExisting` | `true` | Keeps existing deadlines when stamping. |
| `ExpiredReason` | `Message expired before processing.` | Reason returned for expired messages. |
3 changes: 3 additions & 0 deletions docs/generators/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
- name: Message Filter
href: message-filter.md

- name: Message Expiration
href: message-expiration.md

- name: Message Store
href: message-store.md

Expand Down
14 changes: 9 additions & 5 deletions docs/guides/benchmark-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
| Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. |
| Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. |
| Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. |
| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. |
| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. |
| Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. |
| Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. |
| Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. |
Expand Down Expand Up @@ -212,19 +214,19 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149

## Coverage Matrix Summary

The coverage matrix currently publishes 97 catalog patterns and 388 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 98 catalog patterns and 392 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution.

| Category | Patterns | Published route results |
| --- | ---: | ---: |
| Application Architecture | 16 | 64 |
| Behavioral | 11 | 44 |
| Cloud Architecture | 17 | 68 |
| Creational | 5 | 20 |
| Enterprise Integration | 38 | 152 |
| Enterprise Integration | 39 | 156 |
| Messaging Reliability | 3 | 12 |
| Structural | 7 | 28 |

The generator matrix currently publishes 93 generator source route results.
The generator matrix currently publishes 94 generator source route results.

## Pattern Matrix Results

Expand Down Expand Up @@ -301,8 +303,9 @@ The generator matrix currently publishes 93 generator source route results.
| Enterprise Integration | Mailbox | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Channel | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Envelope | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Filter | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Store | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Filter | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Expiration | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Store | Covered | Covered | Covered | Covered |
| Enterprise Integration | Message Translator | Covered | Covered | Covered | Covered |
| Enterprise Integration | Messaging Gateway | Covered | Covered | Covered | Covered |
| Enterprise Integration | Pipes and Filters | Covered | Covered | Covered | Covered |
Expand Down Expand Up @@ -388,6 +391,7 @@ The generator matrix currently publishes 93 generator source route results.
| MessageChannelGenerator | `src/PatternKit.Generators/Messaging/MessageChannelGenerator.cs` | Covered |
| MessageEnvelopeGenerator | `src/PatternKit.Generators/Messaging/MessageEnvelopeGenerator.cs` | Covered |
| MessageFilterGenerator | `src/PatternKit.Generators/Messaging/MessageFilterGenerator.cs` | Covered |
| MessageExpirationGenerator | `src/PatternKit.Generators/Messaging/MessageExpirationGenerator.cs` | Covered |
| CorrelationIdentifierGenerator | `src/PatternKit.Generators/Messaging/CorrelationIdentifierGenerator.cs` | Covered |
| MessageHistoryGenerator | `src/PatternKit.Generators/Messaging/MessageHistoryGenerator.cs` | Covered |
| MessageStoreGenerator | `src/PatternKit.Generators/Messaging/MessageStoreGenerator.cs` | Covered |
Expand Down
2 changes: 2 additions & 0 deletions docs/guides/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ The following numbers were captured on Windows 11, Intel Core i9-14900K, .NET SD
| Message Envelope | Execution | 455.486 ns | 2,752 B | 427.664 ns | 2,752 B | Same allocation; generated was slightly faster for message context enrichment. |
| Message Filter | Construction | 25.431 ns | 232 B | 25.626 ns | 232 B | Effectively equivalent for this microbenchmark. |
| Message Filter | Execution | 44.637 ns | 424 B | 45.826 ns | 424 B | Same allocation; fluent was slightly faster for order fraud screening. |
| Message Expiration | Construction | 22.55 ns | 248 B | 14.63 ns | 144 B | Generated policy construction reduced time and allocation. |
| Message Expiration | Execution | 88.54 ns | 728 B | 103.86 ns | 536 B | Generated reduced allocation; fluent was faster for the stamp-and-evaluate flow. |
| Message Routing | Construction | 23.42 ns | 224 B | 23.33 ns | 224 B | Effectively equivalent for this microbenchmark. |
| Message Routing | Execution | 707.34 ns | 4,744 B | 679.97 ns | 4,632 B | Generated reduced execution time and allocation for the route/split/aggregate workflow. |
| Message Store | Construction | 18.824 ns | 216 B | 18.721 ns | 216 B | Effectively equivalent for this microbenchmark. |
Expand Down
1 change: 1 addition & 0 deletions docs/guides/pattern-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The source of truth is `PatternKitPatternCatalog` in `src/PatternKit.Examples/Pr
| Enterprise Integration | Correlation Identifier | `CorrelationIdentifier<TPayload>` | Correlation Identifier generator |
| Enterprise Integration | Message History | `MessageHistory<TPayload>` | Message History generator |
| Enterprise Integration | Message Filter | `MessageFilter<TPayload>` | Message Filter generator |
| Enterprise Integration | Message Expiration | `MessageExpiration<TPayload>` | Message Expiration generator |
| Enterprise Integration | Message Store | `MessageStore<TPayload>` | Message Store generator |
| Enterprise Integration | Wire Tap | `WireTap<TPayload>` | Wire Tap generator |
| Enterprise Integration | Control Bus | `ControlBus<TCommand>` | Control Bus generator |
Expand Down
23 changes: 23 additions & 0 deletions docs/patterns/messaging/message-expiration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Message Expiration

Message Expiration stamps messages with a deadline and lets consumers reject stale work before it mutates state. Use it at queue, inbox, handler, or workflow boundaries where a command has business value only for a limited time.

```csharp
var expiration = MessageExpiration<OrderCommand>.Create()
.Name("order-message-expiration")
.Header("x-order-expires-at")
.DefaultTtl(TimeSpan.FromMinutes(20))
.ExpiredReason("Order command expired before fulfillment accepted it.")
.Build();

var accepted = expiration.Stamp(Message<OrderCommand>.Create(command));
var result = expiration.Evaluate(accepted);
```

`Stamp` preserves an existing deadline by default so upstream transport or gateway policies keep precedence. Call `PreserveExisting(false)` when the local boundary owns the deadline.

## Production Notes

- Store the expiration deadline as message metadata, not payload state, so routers, filters, and consumers can evaluate it consistently.
- Use a deterministic `Clock` in tests and a UTC clock in production.
- Treat expired messages as rejected work: route to a dead-letter channel, audit log, or compensating workflow instead of silently dropping state-changing commands.
2 changes: 2 additions & 0 deletions docs/patterns/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@
href: messaging/dead-letter-channel.md
- name: Message Filter
href: messaging/message-filter.md
- name: Message Expiration
href: messaging/message-expiration.md
- name: Message Store
href: messaging/message-store.md
- name: Wire Tap
Expand Down
Loading
Loading