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
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 98 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix.
PatternKit currently tracks 99 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 | 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 |
| Enterprise Integration | 40 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content Enricher, 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 @@ -506,6 +506,8 @@ BenchmarkDotNet guidance is documented in [docs/guides/benchmarks.md](docs/guide
| Circuit Breaker | Execution | 85.34 ns | 488 B | 85.19 ns | 488 B | Effectively equivalent for the accepted fulfillment workflow. |
| Content-Based Router | Construction | 42.913 ns | 400 B | 44.118 ns | 400 B | Same allocation; fluent was slightly faster in this microbenchmark. |
| Content-Based Router | Execution | 52.789 ns | 520 B | 60.816 ns | 576 B | Fluent was faster and allocated less for wholesale order routing. |
| Content Enricher | Construction | 36.39 ns | 312 B | 36.22 ns | 312 B | Effectively equivalent for this microbenchmark. |
| Content Enricher | Execution | 1.357 us | 1,400 B | 1.344 us | 1,400 B | Same allocation; generated was slightly faster for customer profile enrichment. |
| Control Bus | Construction | 115.64 ns | 880 B | 79.88 ns | 624 B | Generated reduced construction time and allocation in this microbenchmark. |
| Control Bus | Execution | 290.44 ns | 1,688 B | 232.48 ns | 1,432 B | Generated reduced execution time and allocation for operational command dispatch. |
| CQRS | Construction | 199.3 ns | 2.09 KB | 88.001 us | 309.24 KB | Fluent mediator construction is much lighter; generated measurement includes full IServiceCollection dispatcher composition. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using BenchmarkDotNet.Attributes;
using PatternKit.Examples.Messaging;
using PatternKit.Messaging;
using PatternKit.Messaging.Transformation;

namespace PatternKit.Benchmarks.Messaging;

[BenchmarkCategory("EnterpriseIntegration", "Messaging", "ContentEnricher")]
public class ContentEnricherBenchmarks
{
private static readonly CustomerProfileUpdate Update = new("customer-100", " USER@EXAMPLE.COM ", null, false);

[Benchmark(Baseline = true, Description = "Fluent: create content enricher")]
[BenchmarkCategory("Fluent", "Construction")]
public AsyncContentEnricher<CustomerProfileUpdate> Fluent_CreateContentEnricher()
=> CustomerProfileContentEnrichers.Create();

[Benchmark(Description = "Generated: create content enricher")]
[BenchmarkCategory("Generated", "Construction")]
public AsyncContentEnricher<CustomerProfileUpdate> Generated_CreateContentEnricher()
=> GeneratedCustomerProfileContentEnricher.Create();

[Benchmark(Description = "Fluent: enrich customer profile")]
[BenchmarkCategory("Fluent", "Execution")]
public CustomerProfileEnrichmentSummary Fluent_EnrichCustomerProfile()
=> CustomerProfileContentEnricherExampleRunner.RunFluentAsync(Update).AsTask().GetAwaiter().GetResult();

[Benchmark(Description = "Generated: enrich customer profile")]
[BenchmarkCategory("Generated", "Execution")]
public CustomerProfileEnrichmentSummary Generated_EnrichCustomerProfile()
{
var result = GeneratedCustomerProfileContentEnricher.Create()
.EnrichAsync(Message<CustomerProfileUpdate>.Create(Update))
.AsTask()
.GetAwaiter()
.GetResult();
var payload = result.Message.Payload;
return new CustomerProfileEnrichmentSummary(
payload.CustomerId,
payload.Email ?? string.Empty,
payload.Tier ?? string.Empty,
payload.MarketingOptIn,
result.StepResults.Count(step => step.Applied));
}
Comment on lines +23 to +44
}
10 changes: 10 additions & 0 deletions docs/examples/customer-profile-content-enricher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Customer Profile Content Enricher

The customer profile content-enricher example shows an importable enrichment pipeline for profile updates.

- Normalizes inbound email addresses.
- Applies a safe default tier when upstream data is incomplete.
- Derives marketing opt-in state from the enriched tier.
- Registers the generated `AsyncContentEnricher<CustomerProfileUpdate>` with `IServiceCollection`.

Use `AddCustomerProfileContentEnricherDemo()` for the focused example or `AddPatternKitExamples()` for the aggregate catalog registration.
3 changes: 3 additions & 0 deletions docs/examples/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
- name: Generated Message Translator
href: generated-message-translator.md

- name: Customer Profile Content Enricher
href: customer-profile-content-enricher.md

- name: Order Canonical Data Model
href: order-canonical-data-model.md

Expand Down
24 changes: 24 additions & 0 deletions docs/generators/content-enricher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Content Enricher Generator

`[GenerateContentEnricher]` creates an `AsyncContentEnricher<TPayload>` factory from attributed async enrichment methods.

```csharp
[GenerateContentEnricher(typeof(CustomerProfileUpdate), EnricherName = "customer-profile-enrichment")]
public static partial class GeneratedCustomerProfileContentEnricher
{
[ContentEnrichmentStep("normalize-email", Order = 10)]
private static ValueTask<CustomerProfileUpdate> NormalizeEmail(
CustomerProfileUpdate profile,
MessageContext context,
CancellationToken cancellationToken)
=> ValueTask.FromResult(profile with { Email = profile.Email?.Trim().ToLowerInvariant() });
}
```

Step methods must be static, return `ValueTask<TPayload>`, and accept `TPayload`, `MessageContext`, and `CancellationToken`. `ContentEnrichmentErrorPolicy.UseDefault` requires `DefaultFactoryName` to point at a static method that accepts and returns the payload type.

The generated factory is suitable for DI registration:

```csharp
services.AddSingleton(_ => GeneratedCustomerProfileContentEnricher.Create());
```
1 change: 1 addition & 0 deletions docs/generators/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ PatternKit includes a Roslyn incremental generator package (`PatternKit.Generato
| [**Service Activator**](service-activator.md) | Message-to-service operation factories | `[GenerateServiceActivator]` |
| [**Message Envelope**](messaging.md#generated-message-envelope) | Required message metadata contracts | `[GenerateMessageEnvelope]` |
| [**Message Translator**](message-translator.md) | Partner and transport event normalization | `[GenerateMessageTranslator]` |
| [**Content Enricher**](content-enricher.md) | Ordered async payload enrichment pipelines | `[GenerateContentEnricher]` |
| [**Canonical Data Model**](canonical-data-model.md) | Source-to-canonical contract normalization | `[GenerateCanonicalDataModel]` |
| [**Event-Carried State Transfer**](event-carried-state-transfer.md) | State-rich event projection factories | `[GenerateEventCarriedStateTransfer]` |
| [**Event Notification**](event-notification.md) | Compact event notification factories | `[GenerateEventNotification]` |
Expand Down
3 changes: 3 additions & 0 deletions docs/generators/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@
- name: Message Translator
href: message-translator.md

- name: Content Enricher
href: content-enricher.md

- name: Claim Check
href: claim-check.md

Expand Down
16 changes: 10 additions & 6 deletions docs/guides/benchmark-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
| Circuit Breaker | Execution | 85.34 ns | 488 B | 85.19 ns | 488 B | Effectively equivalent for the accepted fulfillment workflow. |
| Content-Based Router | Construction | 42.913 ns | 400 B | 44.118 ns | 400 B | Same allocation; fluent was slightly faster in this microbenchmark. |
| Content-Based Router | Execution | 52.789 ns | 520 B | 60.816 ns | 576 B | Fluent was faster and allocated less for wholesale order routing. |
| Content Enricher | Construction | 36.39 ns | 312 B | 36.22 ns | 312 B | Effectively equivalent for this microbenchmark. |
| Content Enricher | Execution | 1.357 us | 1,400 B | 1.344 us | 1,400 B | Same allocation; generated was slightly faster for customer profile enrichment. |
| Control Bus | Construction | 115.64 ns | 880 B | 79.88 ns | 624 B | Generated reduced construction time and allocation in this microbenchmark. |
| Control Bus | Execution | 290.44 ns | 1,688 B | 232.48 ns | 1,432 B | Generated reduced execution time and allocation for operational command dispatch. |
| CQRS | Construction | 199.3 ns | 2.09 KB | 88.001 us | 309.24 KB | Fluent mediator construction is much lighter; generated measurement includes full IServiceCollection dispatcher composition. |
Expand Down Expand Up @@ -214,19 +216,19 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149

## Coverage Matrix Summary

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.
The coverage matrix currently publishes 99 catalog patterns and 396 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 | 39 | 156 |
| Enterprise Integration | 40 | 160 |
| Messaging Reliability | 3 | 12 |
| Structural | 7 | 28 |

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

## Pattern Matrix Results

Expand Down Expand Up @@ -288,7 +290,8 @@ The generator matrix currently publishes 94 generator source route results.
| Enterprise Integration | Invalid Message Channel | Covered | Covered | Covered | Covered |
| Enterprise Integration | Claim Check | Covered | Covered | Covered | Covered |
| Enterprise Integration | Competing Consumers | Covered | Covered | Covered | Covered |
| Enterprise Integration | Content-Based Router | Covered | Covered | Covered | Covered |
| Enterprise Integration | Content-Based Router | Covered | Covered | Covered | Covered |
| Enterprise Integration | Content Enricher | Covered | Covered | Covered | Covered |
| Enterprise Integration | Control Bus | Covered | Covered | Covered | Covered |
| Enterprise Integration | Dead Letter Channel | Covered | Covered | Covered | Covered |
| Enterprise Integration | Durable Subscriber | Covered | Covered | Covered | Covered |
Expand Down Expand Up @@ -377,8 +380,9 @@ The generator matrix currently publishes 94 generator source route results.
| ChannelPurgerGenerator | `src/PatternKit.Generators/Messaging/ChannelPurgerGenerator.cs` | Covered |
| InvalidMessageChannelGenerator | `src/PatternKit.Generators/Messaging/InvalidMessageChannelGenerator.cs` | Covered |
| ClaimCheckGenerator | `src/PatternKit.Generators/Messaging/ClaimCheckGenerator.cs` | Covered |
| CompetingConsumerGroupGenerator | `src/PatternKit.Generators/Messaging/CompetingConsumerGroupGenerator.cs` | Covered |
| ContentRouterGenerator | `src/PatternKit.Generators/Messaging/ContentRouterGenerator.cs` | Covered |
| CompetingConsumerGroupGenerator | `src/PatternKit.Generators/Messaging/CompetingConsumerGroupGenerator.cs` | Covered |
| ContentEnricherGenerator | `src/PatternKit.Generators/Messaging/ContentEnricherGenerator.cs` | Covered |
| ContentRouterGenerator | `src/PatternKit.Generators/Messaging/ContentRouterGenerator.cs` | Covered |
| ControlBusGenerator | `src/PatternKit.Generators/Messaging/ControlBusGenerator.cs` | Covered |
| DeadLetterChannelGenerator | `src/PatternKit.Generators/Messaging/DeadLetterChannelGenerator.cs` | Covered |
| DispatcherGenerator | `src/PatternKit.Generators/Messaging/DispatcherGenerator.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 @@ -70,6 +70,8 @@ The following numbers were captured on Windows 11, Intel Core i9-14900K, .NET SD
| Circuit Breaker | Execution | 85.34 ns | 488 B | 85.19 ns | 488 B | Effectively equivalent for the accepted fulfillment workflow. |
| Content-Based Router | Construction | 42.913 ns | 400 B | 44.118 ns | 400 B | Same allocation; fluent was slightly faster in this microbenchmark. |
| Content-Based Router | Execution | 52.789 ns | 520 B | 60.816 ns | 576 B | Fluent was faster and allocated less for wholesale order routing. |
| Content Enricher | Construction | 36.39 ns | 312 B | 36.22 ns | 312 B | Effectively equivalent for this microbenchmark. |
| Content Enricher | Execution | 1.357 us | 1,400 B | 1.344 us | 1,400 B | Same allocation; generated was slightly faster for customer profile enrichment. |
| Control Bus | Construction | 115.64 ns | 880 B | 79.88 ns | 624 B | Generated reduced construction time and allocation in this microbenchmark. |
| Control Bus | Execution | 290.44 ns | 1,688 B | 232.48 ns | 1,432 B | Generated reduced execution time and allocation for operational command dispatch. |
| CQRS | Construction | 199.3 ns | 2.09 KB | 88.001 us | 309.24 KB | Fluent mediator construction is much lighter; generated measurement includes full IServiceCollection dispatcher composition. |
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 @@ -54,6 +54,7 @@ The source of truth is `PatternKitPatternCatalog` in `src/PatternKit.Examples/Pr
| Enterprise Integration | Service Activator | `ServiceActivator<TRequest, TResponse>` | Service Activator generator |
| Enterprise Integration | Message Envelope | `Message<TPayload>`, headers, context | Messaging generator |
| Enterprise Integration | Message Translator | `MessageTranslator<TInput, TOutput>` | Message Translator generator |
| Enterprise Integration | Content Enricher | `AsyncContentEnricher<TPayload>` | Content Enricher generator |
| Enterprise Integration | Canonical Data Model | `CanonicalDataModel<TCanonical>` | Canonical Data Model generator |
| Enterprise Integration | Event-Carried State Transfer | `EventCarriedStateTransfer<TEvent,TKey,TState>` | Event-Carried State Transfer generator |
| Enterprise Integration | Event Notification | `EventNotification<TEvent,TKey>` | Event Notification generator |
Expand Down
17 changes: 17 additions & 0 deletions docs/patterns/messaging/content-enricher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Content Enricher

Content Enricher augments a message payload with computed, normalized, or externally sourced data before a handler mutates state. Use it at message intake boundaries when downstream consumers require a complete payload but upstream systems provide partial data.

## Fluent Path

`AsyncContentEnricher<TPayload>.Create()` builds an ordered async enrichment pipeline. Each step receives the current payload, `MessageContext`, and `CancellationToken`, then returns the enriched payload copy. Per-step error policies let production pipelines throw, skip, or apply a default payload transform.

## Source-Generated Path

Use `[GenerateContentEnricher]` on a partial host and mark static `ValueTask<TPayload>` enrichment methods with `[ContentEnrichmentStep]`. The generator emits an `AsyncContentEnricher<TPayload>` factory with ordered steps and optional `UseDefault` fallback factories.

## Production Notes

- Keep enrichment steps deterministic and idempotent where possible.
- Use `UseDefault` only for safe business defaults.
- Register the generated factory with `IServiceCollection` when importing the pipeline into hosted services or ASP.NET Core handlers.
2 changes: 2 additions & 0 deletions docs/patterns/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@
href: messaging/message-envelope.md
- name: Message Translator
href: messaging/message-translator.md
- name: Content Enricher
href: messaging/content-enricher.md
- name: Canonical Data Model
href: messaging/canonical-data-model.md
- name: Event-Carried State Transfer
Expand Down
Loading
Loading