Skip to content

Architecture

Design principles, project structure, namespace conventions, and architectural patterns used in the ConnectSoft Library Template.

Design Principles

The template follows several key design principles to ensure maintainability, testability, and extensibility.

Clean Architecture

The template promotes separation of concerns with clear boundaries:

  • Domain Logic: Core business logic in the main library project
  • Infrastructure: Cross-cutting concerns (logging, metrics, configuration) as optional features
  • Testing: Separate test project with clear test organization
  • Dependencies: Dependency direction flows inward (tests → library → frameworks)

Domain-Driven Design (DDD)

While primarily a library template, DDD principles guide organization:

  • Feature-Based Organization: Code organized by feature/domain (Options, Metrics, Diagnostics)
  • Clear Boundaries: Each feature folder represents a bounded context
  • Value Objects: Options classes represent configuration value objects
  • Services: Metrics and Diagnostics represent domain services

Dependency Injection

DI is a first-class citizen (when enabled):

  • Service Registration: Extension methods for IServiceCollection
  • Constructor Injection: Preferred method for dependencies
  • Interface-Based Design: Abstractions over concrete implementations
  • Testability: Easy mocking and testing with DI

Configuration Pattern

Strongly-typed configuration with validation:

  • Options Pattern: IOptions<T> for configuration
  • Validation: DataAnnotations and source-generated validators
  • Startup Validation: Configuration validated on application startup
  • Type Safety: Compile-time checking of configuration

Observability First

Built-in support for observability:

  • Structured Logging: ILogger<T> for consistent logging
  • Metrics: OpenTelemetry-compatible metrics
  • Tracing: ActivitySource for distributed tracing
  • Correlation: Logs correlated with traces via trace IDs

Project Structure

Solution Organization

Solution Root
├── src/                          # Source code
│   └── YourLibraryName/         # Main library project
├── tests/                        # Test projects
│   └── YourLibraryName.UnitTests/  # Unit test project
└── docs/                         # Documentation

Library Project Structure

src/YourLibraryName/
├── YourLibraryName.csproj       # Project file
├── GlobalSuppressions.cs        # Code analyzer suppressions
├── Options/                      # Configuration options (if UseOptions=true)
│   ├── LibraryTemplateOptions.cs
│   └── ValidateLibraryTemplateOptions.cs
├── Metrics/                      # Metrics implementation (if UseMetrics=true)
│   └── LibraryTemplateMetrics.cs
├── Diagnostics/                  # Tracing implementation (if UseActivitySource=true)
│   └── LibraryTemplateDiagnostics.cs
└── Samples/                      # Usage samples (if UseActivitySource=true)
    └── TracingDemo.cs

Test Project Structure

tests/YourLibraryName.UnitTests/
├── YourLibraryName.UnitTests.csproj
├── GlobalSuppressions.cs
├── Properties/
│   └── AssemblyInfo.cs
├── Metrics/                      # Metrics tests (if UseMetrics=true)
│   └── LibraryTemplateMetricsUnitTests.cs
└── Diagnostics/                  # Diagnostics tests (if UseActivitySource=true)
    └── LibraryTemplateDiagnosticsTests.cs

Namespace Conventions

Main Namespace

The main namespace matches the library name:

namespace YourLibraryName
{
    // Main library code
}

Feature Namespaces

Feature folders map to namespace segments:

// Options/ folder
namespace YourLibraryName.Options
{
    public class LibraryTemplateOptions { }
}

// Metrics/ folder
namespace YourLibraryName.Metrics
{
    public class LibraryTemplateMetrics { }
}

// Diagnostics/ folder
namespace YourLibraryName.Diagnostics
{
    public static class LibraryTemplateDiagnostics { }
}

Test Namespaces

Test namespaces mirror source namespaces with .UnitTests suffix:

// Source: YourLibraryName.Metrics
// Test: YourLibraryName.Metrics.UnitTests
namespace YourLibraryName.Metrics.UnitTests
{
    [TestClass]
    public class LibraryTemplateMetricsUnitTests { }
}

Code Organization Patterns

Feature-Based Organization

Code is organized by feature/domain rather than by technical layer:

Options/              # Configuration feature
Metrics/              # Metrics feature
Diagnostics/          # Tracing feature

Benefits:

  • Related code stays together
  • Easy to find feature-specific code
  • Clear feature boundaries
  • Simplified navigation

Extension Methods Pattern

Service registration uses extension methods:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibrary(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Service registration
        return services;
    }
}

Benefits:

  • Discoverable via IntelliSense
  • Fluent API style
  • Consistent with .NET patterns
  • Easy to extend

Options Pattern

Configuration uses the Options pattern:

public class MyLibraryOptions
{
    public const string SectionName = "MyLibrary";

    [Required]
    public required string ApiKey { get; set; }
}

Benefits:

  • Strongly-typed configuration
  • Validation support
  • Environment-specific overrides
  • Testable configuration

Metrics Pattern

Metrics use OpenTelemetry patterns:

public class MyLibraryMetrics
{
    private readonly Counter<long> requestCounter;

    public MyLibraryMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("MyLibrary");
        requestCounter = meter.CreateCounter<long>("mylibrary.requests.count");
    }
}

Benefits:

  • OpenTelemetry compatibility
  • Standard metric types
  • Integration with observability platforms
  • Performance monitoring

Diagnostics Pattern

Tracing uses ActivitySource:

public static class MyLibraryDiagnostics
{
    public const string ActivitySourceName = "MyLibrary";
    public static readonly ActivitySource ActivitySource = new(ActivitySourceName);

    public static Activity? StartActivity(string name, ...)
    {
        return ActivitySource.StartActivity(name, ActivityKind.Internal);
    }
}

Benefits:

  • OpenTelemetry compatibility
  • Distributed tracing support
  • Log correlation
  • Performance profiling

Dependency Management Strategy

Central Package Management (CPM)

All package versions managed in Directory.Packages.props:

<ItemGroup>
  <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
</ItemGroup>

Benefits:

  • Single source of truth
  • Easier upgrades
  • Consistent versions
  • Cleaner project files

Conditional Package References

Packages included conditionally based on template parameters:

<ItemGroup Label="Logging Libraries" Condition="'$(UseLogging)' == 'true'">
  <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>

Benefits:

  • Only include what's needed
  • Smaller package size
  • Clear dependencies
  • Flexible feature selection

Package Categories

Packages organized by category:

  • Testing Libraries: MSTest, coverlet
  • Static Code Analyzers: StyleCop, SonarAnalyzer
  • Logging Libraries: Microsoft.Extensions.Logging.*
  • Options Libraries: Microsoft.Extensions.Options.*
  • DI Libraries: Microsoft.Extensions.DependencyInjection.*
  • Metrics Libraries: Microsoft.Extensions.Diagnostics

Testing Strategy

Test Project Organization

Tests mirror source project structure:

Source: src/YourLibraryName/Metrics/LibraryTemplateMetrics.cs
Test:   tests/YourLibraryName.UnitTests/Metrics/LibraryTemplateMetricsUnitTests.cs

Benefits:

  • Easy to find corresponding tests
  • Clear test organization
  • Maintainable test structure

Test Framework

Framework: MSTest
Coverage: coverlet.collector
Assertions: MSTest assertions

Example:

[TestClass]
public class LibraryTemplateMetricsUnitTests
{
    [TestMethod]
    public void IncrementRequestCounter_ShouldIncrementCounter()
    {
        // Arrange
        var meterFactory = new TestMeterFactory();
        var metrics = new LibraryTemplateMetrics(meterFactory);

        // Act
        metrics.IncrementRequestCounter();

        // Assert
        // Verify counter increment
    }
}

Code Coverage

  • Tool: coverlet.collector
  • Format: Cobertura XML
  • Threshold: Configurable (default: 0, recommended: 70+)
  • Collection: Automatic during test runs

Documentation Structure

MkDocs Organization

Documentation organized by topic:

docs/
├── index.md              # Home page
├── Overview.md          # Template overview
├── Features.md          # Feature documentation
├── GettingStarted.md    # Getting started guide
├── UseCases.md          # Use case examples
└── Runbook.md           # Operations guide

Code Documentation

XML documentation comments for all public APIs:

/// <summary>
/// Provides custom application metrics for request counting.
/// </summary>
/// <see href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-instrumentation"/>
public class LibraryTemplateMetrics
{
    /// <summary>
    /// Increments the custom request counter by one.
    /// </summary>
    public void IncrementRequestCounter()
    {
        // Implementation
    }
}

Design Patterns Used

Factory Pattern

Metrics use IMeterFactory for meter creation:

var meter = meterFactory.Create("MyLibrary");

Singleton Pattern

Metrics classes typically registered as singletons:

services.AddSingleton<MyLibraryMetrics>();

Extension Method Pattern

Service registration uses extension methods:

services.AddMyLibrary(configuration);

Options Pattern

Configuration uses Options pattern:

services.Configure<MyLibraryOptions>(config.GetSection("MyLibrary"));

Source Generator Pattern

Options validation uses source generators:

[OptionsValidator]
public partial class ValidateMyLibraryOptions : IValidateOptions<MyLibraryOptions>
{
}

Best Practices

Code Organization

  1. Feature-Based Folders: Organize by feature, not by technical layer
  2. One Class Per File: Each class in its own file
  3. Namespace Matching Folders: Namespace structure matches folder structure
  4. Clear Naming: Use descriptive, consistent names

Dependency Management

  1. Central Package Management: Use CPM for version management
  2. Conditional References: Only include packages that are needed
  3. Version Consistency: Keep versions consistent across projects
  4. Regular Updates: Regularly update package versions

Testing

  1. Test Organization: Mirror source structure in tests
  2. Test Naming: Use descriptive test method names
  3. Test Coverage: Aim for 70%+ code coverage
  4. Test Isolation: Each test should be independent

Documentation

  1. XML Comments: Document all public APIs
  2. README: Keep README.md up to date
  3. Examples: Include usage examples
  4. Architecture: Document architectural decisions