Resolve all 46 low-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 22:56:36 -04:00
parent fef3e7675f
commit 9ee402bbcd
139 changed files with 2621 additions and 1082 deletions
@@ -675,9 +675,16 @@ public class RealOpcUaClient : IOpcUaClient
List<DataShard> touched,
CancellationToken cancellationToken)
{
foreach (var (index, nodeId, startNode) in resolved)
// Plan once for this chunk while the apply gate keeps shard occupancy stable.
var placement = MonitoredItemShardPlanner.Plan(
_dataShards.Select(s => s.ItemCount).ToArray(), ItemsPerShard, resolved.Count);
for (var itemIndex = 0; itemIndex < resolved.Count; itemIndex++)
{
var shard = await PlaceOnDataShardAsync(cancellationToken);
var (index, nodeId, startNode) = resolved[itemIndex];
var shardIndex = placement[itemIndex];
var shard = shardIndex < _dataShards.Count
? _dataShards[shardIndex]
: await CreateDataShardAsync(cancellationToken);
var handle = Guid.NewGuid().ToString();
var monitoredItem = new MonitoredItem(shard.Subscription.DefaultItem)
{
@@ -760,20 +767,6 @@ public class RealOpcUaClient : IOpcUaClient
created.Count);
}
/// <summary>
/// Returns the shard this item belongs on per
/// <see cref="MonitoredItemShardPlanner"/> — first shard with free capacity, else a
/// freshly created one. Callers must hold <see cref="_applyGate"/>.
/// </summary>
private async Task<DataShard> PlaceOnDataShardAsync(CancellationToken cancellationToken)
{
var index = MonitoredItemShardPlanner.Plan(
_dataShards.Select(s => s.ItemCount).ToList(), ItemsPerShard, 1)[0];
return index < _dataShards.Count
? _dataShards[index]
: await CreateDataShardAsync(cancellationToken);
}
/// <summary>
/// Drops a monitored item that failed to create from the shard it was placed on.
/// Callers must hold <see cref="_applyGate"/>. No ApplyChanges is issued: the server
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Options;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
/// <summary>Validates the MxGateway client identity before connections start.</summary>
public sealed class MxGatewayGlobalOptionsValidator : IValidateOptions<MxGatewayGlobalOptions>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, MxGatewayGlobalOptions options)
{
if (string.IsNullOrWhiteSpace(options.ClientNamePrefix))
return ValidateOptionsResult.Fail("ScadaBridge:MxGateway:ClientNamePrefix must not be blank.");
return ValidateOptionsResult.Success;
}
}
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Options;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
/// <summary>Validates the OPC UA application identity before connections start.</summary>
public sealed class OpcUaGlobalOptionsValidator : IValidateOptions<OpcUaGlobalOptions>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, OpcUaGlobalOptions options)
{
if (string.IsNullOrWhiteSpace(options.ApplicationName))
return ValidateOptionsResult.Fail("ScadaBridge:OpcUa:ApplicationName must not be blank.");
return ValidateOptionsResult.Success;
}
}
@@ -24,16 +24,18 @@ public static class ServiceCollectionExtensions
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IValidateOptions<DataConnectionOptions>, DataConnectionOptionsValidator>());
// OpcUaGlobalOptions / MxGatewayGlobalOptions carry only string identity/path
// fields (no numeric or duration knobs), so there is nothing to range-validate
// — no dedicated validator is registered for them. Canonical sections are
// ScadaBridge:OpcUa / ScadaBridge:MxGateway (NF8): the bare "OpcUa"/"MxGateway"
// sections exist in no appsettings, so binding them read nothing.
// Validate identities before the first connection attempts to register with a server.
services.AddOptions<OpcUaGlobalOptions>()
.BindConfiguration("ScadaBridge:OpcUa");
.BindConfiguration("ScadaBridge:OpcUa")
.ValidateOnStart();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IValidateOptions<OpcUaGlobalOptions>, OpcUaGlobalOptionsValidator>());
services.AddOptions<MxGatewayGlobalOptions>()
.BindConfiguration("ScadaBridge:MxGateway");
.BindConfiguration("ScadaBridge:MxGateway")
.ValidateOnStart();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IValidateOptions<MxGatewayGlobalOptions>, MxGatewayGlobalOptionsValidator>());
// Register the factory for protocol extensibility
services.AddSingleton<IDataConnectionFactory, DataConnectionFactory>();