Extensibility Guide¶
This document explains how ConnectSoft templates enable extensibility through extension point patterns. It is written for architects and engineers who need to add domain-specific metrics, configuration options, or testing infrastructure to specialized templates without modifying the base template.
ConnectSoft's extensibility model ensures that base infrastructure remains domain-agnostic while allowing specialized templates to add their own metrics, options, and test patterns through well-defined extension points.
Important
Base infrastructure never contains domain-specific logic. All domain-specific functionality (metrics, options, tests) is added through extension points implemented in specialized templates.
Extension Point Philosophy¶
ConnectSoft uses a plugin-style architecture where:
- Base provides infrastructure + extension points (interfaces, abstract classes, registration hooks)
- Specialized templates implement extension points (concrete implementations)
- Base discovers and registers implementations (via scanning or explicit registration)
flowchart TB
subgraph Base["Base Template"]
INFRA[Infrastructure]
EXT[Extension Points<br/>IMetricsFeature<br/>IConfigureOptions]
REG[Registration<br/>Auto-discovery]
end
subgraph Identity["Identity Template"]
IMPL1[IdentityMetricsFeature<br/>implements IMetricsFeature]
IMPL2[ConfigureIdentityOptions<br/>implements IConfigureOptions]
end
INFRA --> EXT
EXT --> REG
IMPL1 -->|Implements| EXT
IMPL2 -->|Implements| EXT
REG -->|Discovers| IMPL1
REG -->|Discovers| IMPL2
style Base fill:#BBDEFB
style Identity fill:#C8E6C9
style EXT fill:#FFE0B2
Metrics Infrastructure and IMetricsFeature¶
Base Metrics Infrastructure¶
The base template provides metrics infrastructure through ConnectSoft.Extensions.Metrics:
Base Setup:
// In ConnectSoft.Extensions.Metrics
public interface IMetricsFeature
{
void Register(IMeterFactory meterFactory);
}
public static class MetricsServiceCollectionExtensions
{
public static IServiceCollection AddConnectSoftMetrics(
this IServiceCollection services)
{
// Setup OpenTelemetry / Meter infrastructure
services.AddOpenTelemetryMetering();
// Auto-discover and register all IMetricsFeature implementations
services.Scan(scan => scan
.FromApplicationDependencies()
.AddClasses(c => c.AssignableTo<IMetricsFeature>())
.AsImplementedInterfaces()
.WithSingletonLifetime());
// Bootstrapper that calls Register() on all features
services.AddHostedService<MetricsFeatureBootstrapper>();
return services;
}
}
Registration in Base Host:
Domain-Specific Metrics Implementation¶
Specialized templates implement IMetricsFeature to add domain-specific metrics:
Identity Metrics Example:
// In Identity template: Identity.Infrastructure/Metrics/IdentityMetricsFeature.cs
using ConnectSoft.Extensions.Metrics;
namespace Identity.Infrastructure.Metrics;
public sealed class IdentityMetricsFeature : IMetricsFeature
{
private readonly Meter _meter;
private readonly Counter<long> _loginSuccessCounter;
private readonly Counter<long> _loginFailedCounter;
public IdentityMetricsFeature(IMeterFactory meterFactory)
{
_meter = meterFactory.Create("ConnectSoft.Identity");
_loginSuccessCounter = _meter.CreateCounter<long>(
"identity.login.success",
"count",
"Number of successful login attempts");
_loginFailedCounter = _meter.CreateCounter<long>(
"identity.login.failed",
"count",
"Number of failed login attempts");
}
public void Register(IMeterFactory meterFactory)
{
// Registration happens automatically via DI
}
// Domain-specific methods
public void OnLoginSuccess()
{
_loginSuccessCounter.Add(1);
}
public void OnLoginFailed(string reason)
{
_loginFailedCounter.Add(1, new KeyValuePair<string, object?>("reason", reason));
}
}
Registration in Identity Infrastructure:
// In Identity.Infrastructure/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddConnectSoftIdentityInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
// Register Identity metrics feature
services.AddSingleton<IMetricsFeature, IdentityMetricsFeature>();
// ... other Identity infrastructure registration
return services;
}
}
Options Infrastructure and IOptions¶
Base Options Infrastructure¶
The base template provides options infrastructure through ConnectSoft.Extensions.Options:
Base Setup:
// In ConnectSoft.Extensions.Options
public static class OptionsServiceCollectionExtensions
{
public static IServiceCollection AddConnectSoftOptions(
this IServiceCollection services,
IConfiguration configuration)
{
// Bind generic options
services.Configure<ObservabilityOptions>(
configuration.GetSection("ConnectSoft:Observability"));
services.Configure<MessagingOptions>(
configuration.GetSection("ConnectSoft:Messaging"));
// Auto-discover and register all IConfigureOptions<T> implementations
services.Scan(scan => scan
.FromApplicationDependencies()
.AddClasses(c => c.AssignableTo(typeof(IConfigureOptions<>)))
.AsImplementedInterfaces()
.WithTransientLifetime());
return services;
}
}
Domain-Specific Options Implementation¶
Specialized templates define their own options and configure them:
Identity Options Example:
// In Identity template: Identity.Domain/Options/IdentitySecurityOptions.cs
namespace Identity.Domain.Options;
public sealed class IdentitySecurityOptions
{
public const string SectionName = "ConnectSoft:Identity:Security";
public bool RequireConfirmedEmail { get; set; } = true;
public int MaxFailedAccessAttempts { get; set; } = 5;
public TimeSpan LockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(15);
public int PasswordMinLength { get; set; } = 8;
}
Options Configuration:
// In Identity template: Identity.Infrastructure/Options/ConfigureIdentitySecurityOptions.cs
using Microsoft.Extensions.Options;
namespace Identity.Infrastructure.Options;
public sealed class ConfigureIdentitySecurityOptions
: IConfigureOptions<IdentitySecurityOptions>
{
private readonly IConfiguration _configuration;
public ConfigureIdentitySecurityOptions(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure(IdentitySecurityOptions options)
{
_configuration.GetSection(IdentitySecurityOptions.SectionName).Bind(options);
}
}
Base Testing Infrastructure¶
The base template provides reusable testing infrastructure that specialized templates can leverage.
ITestAppFactory Interface¶
Base Interface:
// In Base.Testing.Infrastructure/ITestAppFactory.cs
namespace Base.Testing.Infrastructure;
public interface ITestAppFactory
{
HttpClient CreateClient();
IServiceProvider Services { get; }
void ConfigureWebHost(Action<IWebHostBuilder> configure);
}
AcceptanceTestBase¶
Base Test Class:
// In Base.Testing.Infrastructure/AcceptanceTestBase.cs
namespace Base.Testing.Infrastructure;
[TestClass]
public abstract class AcceptanceTestBase
{
protected abstract ITestAppFactory AppFactory { get; }
[TestMethod]
public async Task Health_endpoint_returns_ok()
{
var client = AppFactory.CreateClient();
var response = await client.GetAsync("/health");
response.EnsureSuccessStatusCode();
}
}
Identity Testing Strategy¶
Specialized templates implement the base test infrastructure for their domain.
IdentityTestAppFactory¶
Identity Factory Implementation:
// In Identity.AcceptanceTests/IdentityTestAppFactory.cs
using Base.Testing.Infrastructure;
using Microsoft.AspNetCore.Mvc.Testing;
namespace Identity.AcceptanceTests;
public sealed class IdentityTestAppFactory : ITestAppFactory
{
private WebApplicationFactory<Program>? _appFactory;
public IServiceProvider Services => _appFactory?.Services
?? throw new InvalidOperationException("Factory not initialized");
public IdentityTestAppFactory()
{
_appFactory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
// Override configuration for tests
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["ConnectionStrings:DefaultConnection"] =
"Server=(localdb)\\mssqllocaldb;Database=IdentityTest;Trusted_Connection=true"
});
});
});
}
public HttpClient CreateClient()
{
return _appFactory?.CreateClient()
?? throw new InvalidOperationException("Factory not initialized");
}
public void ConfigureWebHost(Action<IWebHostBuilder> configure)
{
_appFactory = _appFactory?.WithWebHostBuilder(configure)
?? throw new InvalidOperationException("Factory not initialized");
}
}
Identity Acceptance Tests¶
Using Base Test Infrastructure:
// In Identity.AcceptanceTests/IdentityHealthChecksTests.cs
using Base.Testing.Infrastructure;
namespace Identity.AcceptanceTests;
[TestClass]
public class IdentityHealthChecksTests : AcceptanceTestBase
{
private static readonly IdentityTestAppFactory Factory = new();
protected override ITestAppFactory AppFactory => Factory;
[TestMethod]
public async Task Identity_health_endpoint_includes_database_check()
{
var client = AppFactory.CreateClient();
var response = await client.GetAsync("/health");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Assert.IsTrue(content.Contains("database"));
}
}
Rules and Anti-Patterns¶
Do's¶
✅ Implement extension point interfaces (IMetricsFeature, IConfigureOptions<T>)
✅ Use auto-discovery - Let base infrastructure discover your implementations
✅ Extend base test classes - Reuse AcceptanceTestBase, AggregateTestBase
✅ Keep base domain-agnostic - Never add domain logic to base
✅ Use dependency injection - Register implementations via DI
Don'ts¶
❌ Don't modify base code - Never add domain-specific code to base template
❌ Don't duplicate base infrastructure - Use base test infrastructure, don't copy it
❌ Don't bypass extension points - Use interfaces, don't modify base directly
❌ Don't hard-code domain logic - Use options pattern for configuration
❌ Don't create base dependencies on domains - Base should not reference Identity/Audit/etc.
Extension Point Summary¶
| Extension Point | Interface/Pattern | Purpose | Example |
|---|---|---|---|
| Metrics | IMetricsFeature |
Add domain-specific metrics | IdentityMetricsFeature |
| Options | IConfigureOptions<T> |
Configure domain-specific options | ConfigureIdentitySecurityOptions |
| Testing | ITestAppFactory |
Create test HTTP clients | IdentityTestAppFactory |
| Testing | AcceptanceTestBase |
Reuse acceptance test patterns | IdentityHealthChecksTests |
Related Documents¶
- Template Architecture Specification - First-class technical specification for template hierarchy, inheritance, and composition (authoritative contract)
- Template Overlays Specification - Detailed specification for overlay system, versioning, and compatibility
- Template Architecture Overview - Business overview of the template architecture
- Template Layering Guide - Overview of the three-layer architecture
- ADR-0003 - Extensibility Patterns - Architecture decision record
- Microservice Template - Architecture details of the microservice template