ClearHostedService is built following Onion Architecture principles, ensuring a clean separation of concerns and making the codebase maintainable, testable, and extensible.
The library is structured in concentric layers, with dependencies pointing inward:
???????????????????????????????????????????
? Infrastructure Layer ? ? Logging, External Services
???????????????????????????????????????????
? Application Layer ? ? ClearHostedService, Orchestration
???????????????????????????????????????????
? Core/Domain Layer ? ? Abstractions, Interfaces
???????????????????????????????????????????
Key Rules:
- Inner layers know nothing about outer layers
- Dependencies flow inward only
- Domain layer has no external dependencies
- Infrastructure is replaceable
All dependencies are abstracted through interfaces, allowing:
- Easy testing with mocks
- Swapping implementations without changing core logic
- Loose coupling between components
Responsibility: Define domain abstractions and contracts
Components:
IHostedServiceLifecycle: Lifecycle hooks for hosted servicesIServiceRegistration: Contract for dependency registrationILoggingConfiguration: Logging configuration abstraction
Characteristics:
- No external dependencies
- Pure interfaces and abstractions
- Framework-agnostic where possible
Responsibility: Implement base hosted service orchestration
Components:
ClearHostedService: Main base class for all hosted services- Implements
IHostedService - Manages service collection lifecycle
- Handles startup and shutdown sequences
- Provides extension points for child classes
- Implements
Key Features:
- Service collection isolation per hosted service instance
- Lifecycle management (StartAsync, StopAsync, ExecuteAsync)
- Graceful shutdown handling
- Error handling and recovery
Extension Points:
protected virtual void RegisterDependencyInjection(IServiceCollection services)
protected virtual void ConfigureLogging(ILoggingBuilder builder)
protected virtual Task OnStartingAsync(CancellationToken cancellationToken)
protected virtual Task OnStoppingAsync(CancellationToken cancellationToken)
protected abstract Task ExecuteAsync(CancellationToken stoppingToken)Responsibility: Provide concrete implementations for cross-cutting concerns
Components:
SerilogConfiguration: Serilog setup and configurationApplicationInsightsConfiguration: ApplicationInsights integration- Default sinks: Console, File, ApplicationInsights
- Structured logging support
ServiceCollectionExtensions: Helper methods for DI registration- Common service registrations (HTTP clients, configurations, etc.)
Each ClearHostedService instance maintains its own ServiceCollection:
???????????????????????????????????
? HostedService Instance A ?
? ??? ServiceProvider A ?
? ??? Logger A ?
? ??? Dependencies A ?
???????????????????????????????????
???????????????????????????????????
? HostedService Instance B ?
? ??? ServiceProvider B ?
? ??? Logger B ?
? ??? Dependencies B ?
???????????????????????????????????
Benefits:
- No cross-contamination between services
- Independent lifecycle management
- Easier to reason about scope and lifetime
- Simpler testing
Considerations:
- Slightly higher memory overhead
- Cannot share scoped instances between hosted services
- Each service is truly independent
- Singleton: Use for stateless services, caches, shared resources
- Scoped: Use for request-scoped operations (though less common in background services)
- Transient: Use for lightweight, stateless operations
ClearHostedService uses Serilog as the primary logging framework:
Default Configuration:
- Minimum Level: Information
- Console sink with structured output
- File sink with rolling intervals (daily)
- ApplicationInsights sink (when configured)
Log Enrichment:
- Machine name
- Environment name
- Application name/version
- Thread ID
- Process ID
Structured Logging:
logger.Information("Processing {RecordCount} records from {Source}", count, sourceName);Optional telemetry for production environments:
- Request tracking
- Dependency tracking
- Exception tracking
- Custom metrics and events
- Performance counters
1. Constructor called
?
2. StartAsync() called by host
?
3. Configure Logging
?
4. Build Service Collection
?
5. Call RegisterDependencyInjection() (override point)
?
6. Build ServiceProvider
?
7. Call OnStartingAsync() (override point)
?
8. Start ExecuteAsync() on background thread
?
9. Return control to host
1. StopAsync() called by host
?
2. Set cancellation token
?
3. Wait for ExecuteAsync() to complete (with timeout)
?
4. Call OnStoppingAsync() (override point)
?
5. Dispose ServiceProvider
?
6. Dispose resources
?
7. Return control to host
- Logged with full context
- Propagated to host for handling
- Service fails to start
- Logged with stack trace
- Can be caught and handled by child implementations
- Can implement retry logic if needed
- Logged but don't prevent shutdown
- Resources still cleaned up
- Timeout enforced to prevent hanging
Developers using ClearHostedService can extend:
- Dependency Registration: Override
RegisterDependencyInjection() - Logging Configuration: Override
ConfigureLogging() - Startup Logic: Override
OnStartingAsync() - Shutdown Logic: Override
OnStoppingAsync() - Main Execution: Implement
ExecuteAsync()
Future extensions can add:
- Health Checks: Built-in health check support
- Metrics: Performance and business metrics
- Configuration: File-based configuration support
- Middleware Pipeline: Request processing pipeline
- Event Bus: Internal event publishing/subscribing
public class MyServiceTests
{
[Fact]
public async Task ExecuteAsync_ProcessesRecords_Successfully()
{
// Arrange
var mockService = new Mock<IMyService>();
var sut = new MyHostedService();
// Override DI for testing
sut.RegisterDependencyInjection(services =>
{
services.AddSingleton(mockService.Object);
});
// Act
await sut.StartAsync(CancellationToken.None);
// Assert
mockService.Verify(x => x.DoWorkAsync(It.IsAny<CancellationToken>()), Times.Once);
}
}- Test with real dependencies
- Use test containers for external services
- Verify logging output
- Test graceful shutdown
- Service Provider Creation: Minimal overhead, done once at startup
- Logging: Asynchronous sinks for non-blocking writes
- Memory: Isolated service collections use more memory but improve isolation
- Shutdown: Configurable timeout to balance graceful shutdown vs. responsiveness
- Secrets Management: Use configuration providers, not hardcoded values
- Logging: Avoid logging sensitive data
- Dependencies: Keep packages up to date
- Isolation: Service isolation prevents cross-service contamination
- Plugin System: Load hosted services dynamically
- Configuration Validation: Validate settings at startup
- Circuit Breaker: Built-in resilience patterns
- Distributed Tracing: OpenTelemetry integration
- gRPC Health Checks: Standard health check protocol