feat: add Dead Letter Channel pattern#248
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Test Results655 tests 655 ✅ 31s ⏱️ Results for commit c5802c2. |
Code Coverage |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #248 +/- ##
==========================================
+ Coverage 91.01% 96.26% +5.24%
==========================================
Files 308 312 +4
Lines 28738 29089 +351
Branches 4008 4056 +48
==========================================
+ Hits 26156 28002 +1846
+ Misses 1148 1087 -61
+ Partials 1434 0 -1434
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🔍 PR Validation ResultsVersion: `` ✅ Validation Steps
📊 ArtifactsDry-run artifacts have been uploaded and will be available for 7 days. This comment was automatically generated by the PR validation workflow. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “Dead Letter Channel” messaging reliability pattern slice to PatternKit, including a runtime API for capturing failed messages, a Roslyn incremental source generator for compile-time configuration, and end-to-end examples/docs to make the pattern discoverable and importable via DI.
Changes:
- Adds
DeadLetterChannel<TPayload>runtime implementation with anIDeadLetterStore<TPayload>abstraction, in-memory store, failure metadata capture, and replay handoff. - Adds
[GenerateDeadLetterChannel]+DeadLetterChannelGeneratorwith diagnostics (PKDL001–PKDL003) and corresponding generator tests/attribute-coverage tests. - Adds fulfillment dead-letter example, DI registration, catalog entries, and documentation pages/TOC updates.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/PatternKit.Core/Messaging/Reliability/DeadLetterChannel.cs |
New runtime dead-letter channel API, store abstraction, replay result type, and in-memory store. |
src/PatternKit.Generators.Abstractions/Messaging/DeadLetterChannelAttributes.cs |
New generator-facing attributes for dead-letter channel generation and store factory marking. |
src/PatternKit.Generators/Messaging/DeadLetterChannelGenerator.cs |
New incremental generator emitting dead-letter channel factories + diagnostics. |
src/PatternKit.Generators/AnalyzerReleases.Unshipped.md |
Registers new generator diagnostics (PKDL001–PKDL003). |
src/PatternKit.Examples/Messaging/FulfillmentDeadLetterChannelExample.cs |
Adds fluent + generated example plus DI-importable workflow/services. |
src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs |
Wires the new example into the example DI catalog. |
src/PatternKit.Examples/ProductionReadiness/PatternKitPatternCatalog.cs |
Adds Dead Letter Channel to the production-readiness pattern catalog. |
src/PatternKit.Examples/ProductionReadiness/PatternKitExampleCatalog.cs |
Adds a “Generated Dead Letter Channel” example descriptor. |
test/PatternKit.Tests/Messaging/Reliability/DeadLetterChannelTests.cs |
New runtime tests covering capture, replay, validation, cancellation behavior. |
test/PatternKit.Generators.Tests/DeadLetterChannelGeneratorTests.cs |
New generator tests for successful generation + diagnostics. |
test/PatternKit.Generators.Tests/AbstractionsAttributeCoverageTests.cs |
Extends attribute coverage suite for new attributes and defaults. |
test/PatternKit.Examples.Tests/Messaging/FulfillmentDeadLetterChannelExampleTests.cs |
Example tests for fluent, generated, and DI-imported workflows. |
test/PatternKit.Examples.Tests/DependencyInjection/PatternKitExampleDependencyInjectionTests.cs |
Ensures generated dead-letter example is resolvable/usable from IoC suite. |
test/PatternKit.Examples.Tests/ProductionReadiness/PatternKitPatternCatalogTests.cs |
Updates catalog expectations for the new pattern entry. |
docs/patterns/toc.yml |
Adds Dead Letter Channel to patterns TOC. |
docs/patterns/messaging/dead-letter-channel.md |
New pattern documentation page (runtime usage + replay handoff + generator pointer). |
docs/guides/pattern-coverage.md |
Updates pattern coverage table with Dead Letter Channel. |
docs/generators/toc.yml |
Adds Dead Letter Channel generator page to generators TOC. |
docs/generators/index.md |
Adds Dead Letter Channel to generator index listing. |
docs/generators/messaging.md |
Updates messaging generator inventory and includes Dead Letter Channel section/diagnostics. |
docs/generators/dead-letter-channel.md |
New generator documentation page (usage + diagnostics). |
docs/examples/toc.yml |
Adds Generated Dead Letter Channel to examples TOC. |
docs/examples/generated-dead-letter-channel.md |
New example documentation page for the generated dead-letter channel example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| context ??= MessageContext.From(message, cancellationToken); | ||
| var id = _idFactory(message, reason, context); |
| var headers = message.Headers | ||
| .With("dead-letter-id", id) | ||
| .With("dead-letter-channel", _name) | ||
| .With("dead-letter-reason", reason) | ||
| .With("dead-letter-attempts", attempts) | ||
| .With("dead-letter-failed-at", _clock()); | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(_source)) | ||
| headers = headers.With("dead-letter-source", _source); | ||
|
|
||
| var deadLetter = new DeadLetterMessage<TPayload>( | ||
| id, | ||
| message.WithHeaders(headers), | ||
| reason, | ||
| headers.TryGetDateTimeOffset("dead-letter-failed-at", out var failedAt) ? failedAt : _clock(), | ||
| attempts, |
| public sealed class InMemoryDeadLetterStore<TPayload> : IDeadLetterStore<TPayload> | ||
| { | ||
| private readonly List<DeadLetterMessage<TPayload>> _messages = new(); | ||
|
|
||
| /// <summary>Captured dead-letter messages.</summary> | ||
| public IReadOnlyList<DeadLetterMessage<TPayload>> Messages => _messages; | ||
|
|
| /// <inheritdoc /> | ||
| public ValueTask<DeadLetterMessage<TPayload>?> TryLoadAsync(string id, CancellationToken cancellationToken = default) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(id)) | ||
| throw new ArgumentException("Dead-letter id cannot be null, empty, or whitespace.", nameof(id)); | ||
|
|
||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| return new ValueTask<DeadLetterMessage<TPayload>?>(_messages.LastOrDefault(message => | ||
| string.Equals(message.Id, id, StringComparison.Ordinal))); | ||
| } |
| sb.Append(GetAccessibility(type.DeclaredAccessibility)).Append(' '); | ||
| if (type.IsStatic) | ||
| sb.Append("static "); | ||
| else if (type.IsAbstract && type.TypeKind == TypeKind.Class) | ||
| sb.Append("abstract "); | ||
| else if (type.IsSealed && type.TypeKind == TypeKind.Class) | ||
| sb.Append("sealed "); | ||
| sb.Append("partial ").Append(type.TypeKind == TypeKind.Struct ? "struct" : "class").Append(' ').Append(type.Name).AppendLine(); | ||
| sb.AppendLine("{"); |
| - creates a named dead-letter channel | ||
| - records the configured source in captured metadata | ||
| - uses the marked store factory for application-owned persistence | ||
| - generates deterministic ids from the configured prefix and message id |
Summary
Closes #235.
Validation
Note: local examples build still hits the existing Roslyn analyzer/compiler mismatch (CS9057) and generated-type cascade; hosted CI validates the examples matrix.