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
54 changes: 52 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,37 @@ jobs:
- name: Test with coverage
timeout-minutes: 45
run: |
dotnet test PatternKit.slnx \
dotnet test test/PatternKit.Tests/PatternKit.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=core-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Generators.Tests/PatternKit.Generators.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=generators-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Hosting.Extensions.Tests/PatternKit.Hosting.Extensions.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=hosting-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Examples.Tests/PatternKit.Examples.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=examples-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura

- name: Install ReportGenerator
Expand Down Expand Up @@ -171,12 +196,37 @@ jobs:
- name: Test with coverage (Release)
timeout-minutes: 60
run: |
dotnet test PatternKit.slnx \
dotnet test test/PatternKit.Tests/PatternKit.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=core-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Generators.Tests/PatternKit.Generators.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=generators-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Hosting.Extensions.Tests/PatternKit.Hosting.Extensions.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=hosting-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
dotnet test test/PatternKit.Examples.Tests/PatternKit.Examples.Tests.csproj \
--configuration Release \
--no-build \
-p:TestTfmsInParallel=false \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults \
--logger "trx;LogFilePrefix=examples-test-results" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura


Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ PatternKit currently tracks 115 production-readiness patterns. Each catalog patt
| Category | Count | Patterns |
| --- | ---: | --- |
| Application Architecture | 26 | Activity Tracker, Aggregate Root, Anti-Corruption Layer, Audit Log, Bounded Context, Context Map, CQRS, Data Mapper, Domain Event, Domain Service, Event Sourcing, Eventual Consistency Monitor, Feature Toggle, Identity Map, Manual Task Gate, Materialized View, Repository, Service Layer, Snapshot / Checkpoint Management, Specification, Table Data Gateway, Timeout Manager, Transaction Script, Unit of Work, Value Object, Workflow Orchestration |
| Behavioral | 11 | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor |
| Behavioral | 12 | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Null Object, Observer, State, Strategy, Template Method, Visitor |
| Cloud Architecture | 20 | Ambassador, Backends for Frontends, Bulkhead, Cache-Aside, Cache Stampede Protection, Circuit Breaker, External Configuration Store, Gateway Aggregation, Gateway Routing, Health Endpoint Monitoring, Leader Election, Priority Queue, Queue-Based Load Leveling, Rate Limiting, Read-Through Cache, Retry, Scheduler Agent Supervisor, Sidecar, Strangler Fig, Write-Through Cache |
| Creational | 6 | Abstract Factory, Builder, Factory Method, Object Pool, Prototype, Singleton |
| Enterprise Integration | 41 | 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, Guaranteed Delivery, 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 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using BenchmarkDotNet.Attributes;
using PatternKit.Behavioral.NullObject;
using PatternKit.Generators.NullObject;

namespace PatternKit.Benchmarks.Behavioral;

[BenchmarkCategory("Behavioral", "NullObject")]
public class NullObjectBenchmarks
Comment thread
JerrettDavis marked this conversation as resolved.
{
private static readonly Notification Notification = new("C-100", "Statement ready");

[Benchmark(Baseline = true, Description = "Fluent: create null object")]
[BenchmarkCategory("Fluent", "Construction")]
public NullObject<INullNotificationChannel> Fluent_CreateNullObject()
=> NullObject<INullNotificationChannel>
.Create(NullNotificationChannel.Instance)
.Build();

[Benchmark(Description = "Generated: get null object instance")]
[BenchmarkCategory("Generated", "Construction")]
public INullNotificationChannel Generated_GetInstance()
=> NullNotificationChannel.Instance;

[Benchmark(Description = "Fluent: invoke null object")]
[BenchmarkCategory("Fluent", "Execution")]
public string Fluent_Invoke()
=> Fluent_CreateNullObject().Instance.Send(Notification);

[Benchmark(Description = "Generated: invoke null object")]
[BenchmarkCategory("Generated", "Execution")]
public string Generated_Invoke()
=> NullNotificationChannel.Instance.Send(Notification);
}

public sealed record Notification(string CustomerId, string Subject);

[GenerateNullObject(TypeName = "NullNotificationChannel")]
public interface INullNotificationChannel
{
[NullObjectDefault("suppressed")]
string Send(Notification notification);
}
3 changes: 3 additions & 0 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ Welcome! This section collects small, focused demos that show **how to compose b
* **Order Projection Eventual Consistency Monitor**
A Generic Host importable monitor with fluent and source-generated routes for source/target watermark convergence. See [Order Projection Eventual Consistency Monitor](order-projection-eventual-consistency-monitor.md).

* **Customer Notification Null Object**
A Generic Host importable notification fallback with fluent and source-generated routes, `IServiceCollection` registration, and deterministic no-op behavior for optional collaborators. See [Customer Notification Null Object](null-object-notification.md).

* **Minimal Web Request Router**
A tiny "API gateway" that separates **first-match middleware** (side effects/logging/auth) from **first-match routes** and **content negotiation**. A crisp example of Strategy patterns in an HTTP-ish setting.

Expand Down
10 changes: 10 additions & 0 deletions docs/examples/null-object-notification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Null Object Notification Example
Comment thread
JerrettDavis marked this conversation as resolved.

The customer notification example shows how to keep a workflow production-safe when an optional notification provider is not configured.

The generated `NullCustomerNotificationChannel` implements `ICustomerNotificationChannel` and returns a deterministic `"suppressed"` status. The fluent `NullObject<ICustomerNotificationChannel>` wrapper is registered through `IServiceCollection` so importing applications can inject the fallback channel without special-case `null` logic.

Relevant files:

- `src/PatternKit.Examples/NullObjectDemo/CustomerNotificationNullObjectDemo.cs`
- `test/PatternKit.Examples.Tests/NullObjectDemo/CustomerNotificationNullObjectDemoTests.cs`
3 changes: 3 additions & 0 deletions docs/examples/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
- name: Order Projection Eventual Consistency Monitor
href: order-projection-eventual-consistency-monitor.md

- name: Customer Notification Null Object
href: null-object-notification.md

- name: Spreadsheet Formula Object Pool
href: spreadsheet-formula-object-pool.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 @@ -56,6 +56,7 @@ PatternKit includes a Roslyn incremental generator package (`PatternKit.Generato
| [**Interpreter**](interpreter.md) | DSL rule factories for terminal and non-terminal expressions | `[GenerateInterpreter]` |
| [**Iterator**](iterator.md) | Enumerable/async-enumerable iteration helpers | `[Iterator]` |
| [**Memento**](memento.md) | Immutable snapshots with optional undo/redo history | `[Memento]` |
| [**Null Object**](null-object.md) | Deterministic no-op implementations for optional contracts | `[GenerateNullObject]` |
| [**Observer**](observer.md) | Event hubs and observer dispatch | `[ObserverHub]` |
| [**State Machine**](state-machine.md) | Deterministic finite state machines | `[StateMachine]` |
| [**Strategy**](strategy.md) | Predicate-based dispatch with fluent builder | `[GenerateStrategy]` |
Expand Down
25 changes: 25 additions & 0 deletions docs/generators/null-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Null Object Generator
Comment thread
JerrettDavis marked this conversation as resolved.

`[GenerateNullObject]` generates a sealed Null Object implementation for an interface contract.

```csharp
[GenerateNullObject(TypeName = "NullInventoryNotifier")]
public interface IInventoryNotifier
{
[NullObjectDefault(false)]
bool CanDeliver { get; }

[NullObjectDefault("suppressed")]
string Notify(string sku);
}
```

Generated output includes:

- a sealed implementation of the contract
- a static `Instance` property
- no-op `void` methods
- deterministic defaults for strings, booleans, numeric values, arrays, `Task`, `Task<T>`, `ValueTask`, and `ValueTask<T>`
- per-member defaults through `[NullObjectDefault]`

Use the generated implementation directly or wrap it with `NullObject<TContract>` for consistent dependency injection registration.
3 changes: 3 additions & 0 deletions docs/generators/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
- name: Memento
href: memento.md

- name: Null Object
href: null-object.md

- name: Messaging Generators
href: messaging.md

Expand Down
23 changes: 14 additions & 9 deletions docs/guides/benchmark-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
| Timeout Manager | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
| Aggregate Root | Construction | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
| Aggregate Root | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
| Null Object | Construction | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
| Null Object | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
| Aggregator | Construction | 14.562 ns | 168 B | 15.235 ns | 168 B | Same allocation; fluent was slightly faster in this microbenchmark. |
| Aggregator | Execution | 188.000 ns | 1,088 B | 200.564 ns | 1,088 B | Same allocation; fluent was faster for order line aggregation. |
| Ambassador | Construction | 55.42 ns | 448 B | 48.03 ns | 360 B | Generated reduced construction time and allocation in this microbenchmark. |
Expand Down Expand Up @@ -250,19 +252,19 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149

## Coverage Matrix Summary

The coverage matrix currently publishes 115 catalog patterns and 460 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.
The coverage matrix currently publishes 115 catalog patterns and 460 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 10 reusable hosting integration route results for package-level `IServiceCollection` registrations.

| Category | Patterns | Published route results |
| --- | ---: | ---: |
| Application Architecture | 26 | 104 |
| Behavioral | 11 | 44 |
| Behavioral | 12 | 48 |
| Cloud Architecture | 20 | 80 |
| Creational | 6 | 24 |
| Enterprise Integration | 41 | 164 |
| Messaging Reliability | 3 | 12 |
| Structural | 7 | 28 |
| Structural | 7 | 28 |

The generator matrix currently publishes 109 generator source route results.
The generator matrix currently publishes 110 generator source route results.

## Hosting Integration Matrix Results

Expand All @@ -273,6 +275,7 @@ The generator matrix currently publishes 109 generator source route results.
| 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` |
| Null Object | `IServiceCollection` | `AddPatternKitNullObject<TContract>` | `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` |
Expand Down Expand Up @@ -312,9 +315,10 @@ The generator matrix currently publishes 109 generator source route results.
| Behavioral | Command | Covered | Covered | Covered | Covered |
| Behavioral | Interpreter | Covered | Covered | Covered | Covered |
| Behavioral | Iterator | Covered | Covered | Covered | Covered |
| Behavioral | Mediator | Covered | Covered | Covered | Covered |
| Behavioral | Memento | Covered | Covered | Covered | Covered |
| Behavioral | Observer | Covered | Covered | Covered | Covered |
| Behavioral | Mediator | Covered | Covered | Covered | Covered |
| Behavioral | Memento | Covered | Covered | Covered | Covered |
| Behavioral | Null Object | Covered | Covered | Covered | Covered |
| Behavioral | Observer | Covered | Covered | Covered | Covered |
| Behavioral | State | Covered | Covered | Covered | Covered |
| Behavioral | Strategy | Covered | Covered | Covered | Covered |
| Behavioral | Template Method | Covered | Covered | Covered | Covered |
Expand Down Expand Up @@ -447,8 +451,9 @@ The generator matrix currently publishes 109 generator source route results.
| InterpreterGenerator | `src/PatternKit.Generators/Interpreter/InterpreterGenerator.cs` | Covered |
| IteratorGenerator | `src/PatternKit.Generators/Iterator/IteratorGenerator.cs` | Covered |
| LeaderElectionGenerator | `src/PatternKit.Generators/LeaderElection/LeaderElectionGenerator.cs` | Covered |
| MaterializedViewGenerator | `src/PatternKit.Generators/MaterializedViews/MaterializedViewGenerator.cs` | Covered |
| MementoGenerator | `src/PatternKit.Generators/MementoGenerator.cs` | Covered |
| MaterializedViewGenerator | `src/PatternKit.Generators/MaterializedViews/MaterializedViewGenerator.cs` | Covered |
| MementoGenerator | `src/PatternKit.Generators/MementoGenerator.cs` | Covered |
| NullObjectGenerator | `src/PatternKit.Generators/NullObject/NullObjectGenerator.cs` | Covered |
| BackplaneTopologyGenerator | `src/PatternKit.Generators/Messaging/BackplaneTopologyGenerator.cs` | Covered |
| ChannelAdapterGenerator | `src/PatternKit.Generators/Messaging/ChannelAdapterGenerator.cs` | Covered |
| ChannelPurgerGenerator | `src/PatternKit.Generators/Messaging/ChannelPurgerGenerator.cs` | Covered |
Expand Down
Loading
Loading