Skip to content
Draft
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion docs/adr/0057-box-schema-versioning-and-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ The advisory-lock primitive that wraps the runner's chain (§3, §5a) is exposed
- SQLite is **exempt** from the abstraction — it has no advisory lock; serialization is provided by `BEGIN IMMEDIATE`'s writer slot (per §5 table). The runner's existing `BeginImmediateWithRetryAsync` handles `SQLITE_BUSY` retry directly; introducing an `ISqliteAdvisoryLock` would invent a primitive that does not exist in the database.
- Spanner is **exempt** — degenerate runner per §6, no concurrency primitive of its own.

**Constructor injection (additive)**: each runner ctor gains two optional parameters — `I*AdvisoryLock? advisoryLock = null` (default: `new *AdvisoryLock()`) and `ILogger? logger = null` (default: `ApplicationLogging.CreateLogger<*BoxMigrationRunner>()`). Both are non-breaking additions; existing call sites continue to compile and run with default behaviour. The DI extensions (`UseBoxProvisioning`) do not register the abstractions — operators wanting custom impls construct the runner explicitly. This matches the existing wiring approach for `IAmABoxMigrationRunner` itself.
**Constructor injection**: each runner ctor accepts an optional `I*AdvisoryLock? advisoryLock = null` (default: `new *AdvisoryLock()`) and requires an `ILoggerFactory`. An optional `ILogger? logger = null` can override the logger created by that factory. There is no implicit null-logger fallback; callers that want logging disabled must explicitly supply `NullLoggerFactory.Instance` or a null logger. The DI extensions obtain `ILoggerFactory` from the container. Operators wanting custom advisory-lock implementations construct the runner explicitly.

**What does not absorb into the abstraction**:

Expand Down
2 changes: 0 additions & 2 deletions docs/adr/0064-pipeline-cache-type-key.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ All three are converted in one change (FR-3, FR-4, NFR-3, AC-11).
3. Leave everything else untouched: `ClearPipelineCache()` (`PipelineBuilder.cs:253`; `TransformPipelineBuilder.cs:223`; `TransformPipelineBuilderAsync.cs:186`), `Describe`/`Describe()` (which do not read the cache, `PipelineBuilder.cs:106-137, 144-155`), `DescribeTransforms` (`TransformPipelineBuilder.cs:197-221`), the `AddGlobalInboxAttributes`/`AddGlobalInboxAttributesAsync` `UseInbox` construction (`PipelineBuilder.cs:350-389`), `HandlerName`, and `RequestHandler.Name` — no public-API change (FR-7, AC-8, AC-9, OOS-3).
4. Update the one white-box test that inspects memento keys, `tests/Paramore.Brighter.Core.Tests/CommandProcessors/Pipeline/When_Building_A_Pipeline_Post_Attributes_Are_Cached.cs`: the helper `GetPostAttributesCacheKeys` reflects `s_postAttributesMemento` and returns `cache.Keys.Cast<string>()` as `IEnumerable<string>`, asserted via `Assert.Contains(nameof(MyPreAndPostDecoratedHandler), …)`. After the change the keys are `Type`, so the helper's return type moves to `IEnumerable<Type>` and its body to `cache.Keys.Cast<Type>()`, and each assertion moves to `Assert.Contains(typeof(MyPreAndPostDecoratedHandler), …)` (and the async variant). Re-typing the helper is mandatory, not optional: `Cast<string>()` over `Type` keys is not a compile error — it throws `InvalidCastException` at runtime. This is a test internal-detail update only; it is permitted because it asserts the cache's internal representation, which C-5 leaves unconstrained.

The pre-existing mis-typed logger generic in the async builder (`ApplicationLogging.CreateLogger<TransformPipelineBuilder>` at `TransformPipelineBuilderAsync.cs:50`) is deliberately left untouched (OOS-2).

## Consequences

### Positive
Expand Down
6 changes: 3 additions & 3 deletions samples/AsyncAPI/KafkaAsyncAPI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Licence
#region Licence
/* The MIT License (MIT)
Copyright © 2026 Jonny Olliff-Lee <jonny.ollifflee@gmail.com>

Expand Down Expand Up @@ -47,7 +47,7 @@ THE SOFTWARE. */
BootStrapServers = new[] { "localhost:9092" }
};

var kafkaMessageConsumerFactory = new KafkaMessageConsumerFactory(kafkaConfig);
var kafkaMessageConsumerFactory = new KafkaMessageConsumerFactory(kafkaConfig, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);

// KafkaProducerRegistryFactory.Create() opens a real broker connection at construction
// time, so we only build the registry when actually starting the producer side. In
Expand Down Expand Up @@ -86,7 +86,7 @@ THE SOFTWARE. */
MessageTimeoutMs = 1000,
MaxInFlightRequestsPerConnection = 1
}
}).Create();
}, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

brighter.AddProducers(configure =>
{
Expand Down
6 changes: 3 additions & 3 deletions samples/AsyncAPI/RMQAsyncAPI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Licence
#region Licence
/* The MIT License (MIT)
Copyright © 2026 Jonny Olliff-Lee <jonny.ollifflee@gmail.com>

Expand Down Expand Up @@ -45,7 +45,7 @@ THE SOFTWARE. */
Exchange = new Exchange("paramore.brighter.asyncapi.exchange"),
};

var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);

// Build a producer registry with a typed Publication<T> to demonstrate RequestType auto-discovery
var producerRegistry = new RmqProducerRegistryFactory(
Expand All @@ -58,7 +58,7 @@ THE SOFTWARE. */
MakeChannels = OnMissingChannel.Create,
Topic = new RoutingKey("order.created")
}
}).Create();
}, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

var host = new HostBuilder()
.ConfigureServices((_, services) =>
Expand Down
6 changes: 3 additions & 3 deletions samples/CommandProcessor/HelloWorldInternalBus/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ THE SOFTWARE. */

var bus = new InternalBus();

var publications = new[] { new Publication { Topic = routingKey, RequestType = typeof(GreetingCommand)} };
var publications = new[] { new Publication { Topic = routingKey, RequestType = typeof(GreetingCommand) } };

var subscriptions = new[]
{
Expand All @@ -52,14 +52,14 @@ THE SOFTWARE. */
builder.Services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
options.DefaultChannelFactory = new InMemoryChannelFactory(bus, TimeProvider.System);
options.DefaultChannelFactory = new InMemoryChannelFactory(bus, TimeProvider.System, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
options.HandlerLifetime = ServiceLifetime.Scoped;
options.MapperLifetime = ServiceLifetime.Singleton;
options.InboxConfiguration = new InboxConfiguration(new InMemoryInbox(TimeProvider.System));
})
.AddProducers((config) =>
{
config.ProducerRegistry = new InMemoryProducerRegistryFactory(bus, publications, InstrumentationOptions.All).Create();
config.ProducerRegistry = new InMemoryProducerRegistryFactory(bus, publications, global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance, InstrumentationOptions.All).Create();
config.Outbox = new InMemoryOutbox(TimeProvider.System);
})
.AutoFromAssemblies();
Expand Down
6 changes: 3 additions & 3 deletions samples/Scheduler/AwsTaskQueue/GreetingsPumper/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ private static async Task Main(string[] args)
Topic = new RoutingKey("message-scheduler-topic"),
RequestType = typeof(FireAwsScheduler)
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

services.AddBrighter()
.AddProducers(configure =>
Expand Down Expand Up @@ -125,7 +125,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
{
continue;
}

logger.LogInformation("Pausing for breath...");
await Task.Delay(TimeSpan.FromMinutes(2), cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static async Task Main(string[] args)
services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
options.DefaultChannelFactory = new ChannelFactory(awsConnection);
options.DefaultChannelFactory = new ChannelFactory(awsConnection, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
})
.AddProducers(configure =>
{
Expand All @@ -132,8 +132,8 @@ public static async Task Main(string[] args)
Topic = new RoutingKey("message-scheduler-topic"),
RequestType = typeof(FireAwsScheduler)
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();
})
.AutoFromAssemblies();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName()),
RequestType = typeof(GreetingEvent)
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

services.AddBrighter()
.AddProducers((configure) =>
Expand Down Expand Up @@ -96,7 +96,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

logger.LogInformation("Scheduling message #{Loop}", loop);
commandProcessor.Post(TimeSpan.FromSeconds(10), new GreetingEvent($"Scheduler message Ian #{loop}"));

if (loop % 100 != 0)
{
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ THE SOFTWARE. */
};

//create the gateway
var serviceURL = "http://localhost:4566/";
var serviceURL = "http://localhost:4566/";
var region = RegionEndpoint.USEast1;
var awsConnection = new AWSMessagingGatewayConnection(new BasicAWSCredentials("test", "test"), region,
cfg => { cfg.ServiceURL = serviceURL; });

services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
options.DefaultChannelFactory = new ChannelFactory(awsConnection);
options.DefaultChannelFactory = new ChannelFactory(awsConnection, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
})
.AutoFromAssemblies();

Expand Down
4 changes: 2 additions & 2 deletions samples/Scheduler/TickerQ/Greeting.Consumer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
};

opt.DefaultChannelFactory = new ChannelFactory(
new RmqMessageConsumerFactory(rmqConnection)
new RmqMessageConsumerFactory(rmqConnection, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance)
);

})
Expand All @@ -53,7 +53,7 @@

app.MapGet("/", () =>
{
return "helloConsumer";
return "helloConsumer";
});

app.Run();
Expand Down
4 changes: 2 additions & 2 deletions samples/Scheduler/TickerQ/Greeting.Producer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
RequestType = typeof(GreetingEvent),
MakeChannels = OnMissingChannel.Create
}
]).Create();
], loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

}).UseScheduler(provider =>
{
Expand Down Expand Up @@ -92,7 +92,7 @@

app.MapPost("/send-multiple", async (IAmACommandProcessor commandProcessor) =>
{
var iterations = 5;
var iterations = 5;
for (int i = 1; i <= iterations; i++)
{
var content = $"Manual multiple message #{i}";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using Greetings.Ports.CommandHandlers;
using Greetings.Ports.Events;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -42,7 +42,7 @@
//TODO: add your ASB qualified name here
var clientProvider = new ServiceBusConnectionStringClientProvider("Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;");

var asbConsumerFactory = new AzureServiceBusConsumerFactory(clientProvider);
var asbConsumerFactory = new AzureServiceBusConsumerFactory(clientProvider, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
builder.Services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// If you run this receiver with the other receiver, and send you'll see different behaviours.
// If you run this receiver with the other receiver, and send you'll see different behaviours.
// This scoped receiver will refresh the scoped dependency for each pipeline (Event/Command dispatch)

using System;
Expand Down Expand Up @@ -44,7 +44,7 @@

//TODO: add your ASB qualified name here
var asbClientProvider = new ServiceBusConnectionStringClientProvider("Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;");
var asbConsumerFactory = new AzureServiceBusConsumerFactory(asbClientProvider);
var asbConsumerFactory = new AzureServiceBusConsumerFactory(asbClientProvider, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
builder.Services
.AddConsumers(options =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

var asbConnection = new ServiceBusConnectionStringClientProvider(asbEndpoint);

var outboxConfig = new RelationalDatabaseConfiguration(dbConnString,
var outboxConfig = new RelationalDatabaseConfiguration(dbConnString,
databaseName: "BrighterTests", outBoxTableName: "BrighterOutbox");

var producerRegistry = new AzureServiceBusProducerRegistryFactory(
Expand All @@ -46,8 +46,8 @@
new() { Topic = new RoutingKey("greeting.event"), MakeChannels = OnMissingChannel.Assume},
new() { Topic = new RoutingKey("greeting.addGreetingCommand"), MakeChannels = OnMissingChannel.Assume },
new() { Topic = new RoutingKey("greeting.Asyncevent"), MakeChannels = OnMissingChannel.Assume }
]
)
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance)
.Create();

builder.Services
Expand All @@ -64,7 +64,7 @@
.AddProducers((configure) =>
{
configure.ProducerRegistry = producerRegistry;
configure.Outbox = new MsSqlOutbox(outboxConfig);
configure.Outbox = new MsSqlOutbox(outboxConfig, logger: global::Microsoft.Extensions.Logging.LoggerFactoryExtensions.CreateLogger<global::Paramore.Brighter.Outbox.MsSql.MsSqlOutbox>(global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance));
configure.TransactionProvider = typeof(MsSqlEntityFrameworkCoreTransactionProvider<GreetingsDataContext>);
});

Expand Down
10 changes: 5 additions & 5 deletions samples/TaskQueue/ASBTaskQueue/GreetingsSender/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ static void Main(string[] args)
{
Topic = new RoutingKey("greeting.Asyncevent"),
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

serviceCollection.AddBrighter()
.AddProducers((config) =>
{
Expand All @@ -61,12 +61,12 @@ static void Main(string[] args)
Console.WriteLine("Sending....");
var distroGreeting = new GreetingEvent("Paul - Distributed");
commandProcessor.DepositPost(distroGreeting);

commandProcessor.Post(new GreetingEvent("Paul"));
commandProcessor.Post(new GreetingAsyncEvent("Paul - Async"));

commandProcessor.ClearOutbox([distroGreeting.Id.Value]);

Console.WriteLine("Press q to Quit or any other key to continue");

var keyPress = Console.ReadKey();
Expand Down
8 changes: 4 additions & 4 deletions samples/TaskQueue/ASBTaskQueue/GreetingsWorker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
};

string dbConnString = "Server=127.0.0.1,11433;Database=BrighterTests;User Id=sa;Password=Password1!;Application Name=BrighterTests;MultipleActiveResultSets=True";

//EF
builder.Services.AddDbContext<GreetingsDataContext>(o =>
{
Expand All @@ -69,17 +69,17 @@

var clientProvider = new ServiceBusConnectionStringClientProvider("Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;");

var asbConsumerFactory = new AzureServiceBusConsumerFactory(clientProvider);
var asbConsumerFactory = new AzureServiceBusConsumerFactory(clientProvider, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
builder.Services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
options.DefaultChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory);

})
.AutoFromAssemblies();

builder.Services.AddHostedService<ServiceActivatorHostedService>();

builder.Logging.SetMinimumLevel(LogLevel.Information).AddConsole();


Expand Down
6 changes: 3 additions & 3 deletions samples/TaskQueue/AWSTaskQueue/GreetingsPumper/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon;
Expand Down Expand Up @@ -39,8 +39,8 @@
typeof(FarewellEvent).FullName.ToValidSNSTopicName(true)),
TopicAttributes = new SnsAttributes { Type = SqsType.Fifo }
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

builder.Services.AddBrighter()
.AddProducers((configure) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Licence
#region Licence

/* The MIT License (MIT)
Copyright © 2014 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>
Expand Down Expand Up @@ -78,7 +78,7 @@ THE SOFTWARE. */
builder.Services.AddConsumers(options =>
{
options.Subscriptions = subscriptions;
options.DefaultChannelFactory = new ChannelFactory(awsConnection);
options.DefaultChannelFactory = new ChannelFactory(awsConnection, loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
})
.AutoFromAssemblies();

Expand Down
4 changes: 2 additions & 2 deletions samples/TaskQueue/AWSTaskQueue/GreetingsSender/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ static async Task Main(string[] args)
Type = SqsType.Fifo
}
}
]
).Create();
],
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance).Create();

serviceCollection
.AddBrighter()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#region Licence
#region Licence

/* The MIT License (MIT)
Copyright © 2014 Ian Cooper <ian_hammond_cooper@yahoo.co.uk>
Expand Down Expand Up @@ -75,9 +75,10 @@ THE SOFTWARE. */
var consumerFactory = new KafkaMessageConsumerFactory(
new KafkaMessagingGatewayConfiguration
{
Name = "paramore.brighter", BootStrapServers = new[] { "localhost:9092" }
}
);
Name = "paramore.brighter",
BootStrapServers = new[] { "localhost:9092" }
},
loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);

services.AddConsumers(options =>
{
Expand All @@ -86,7 +87,7 @@ THE SOFTWARE. */
})
// InMemorySchedulerFactory is the default — shown here explicitly to demonstrate scheduler configuration.
// Replace with HangfireMessageSchedulerFactory or QuartzSchedulerFactory for durable scheduling.
.UseScheduler(new InMemorySchedulerFactory())
.UseScheduler(new InMemorySchedulerFactory(loggerFactory: global::Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance))
.AutoFromAssemblies();

services.AddHostedService<ServiceActivatorHostedService>();
Expand Down
Loading
Loading