davianspace_hosting
Enterprise-grade hosting framework for Dart. Unifies configuration, dependency injection, logging, and lifecycle management into a coherent application model — conceptually equivalent to Microsoft.Extensions.Hosting, expressed idiomatically in Dart.
Table of contents
- Features
- Installation
- Quick start
- Ecosystem integration
- Architecture
- Configuration
- Logging
- Advanced usage
- Error handling
- Testing
- API reference
- Contributing
- Security
- License
Features
| Feature | Description |
|---|---|
| Unified startup model | Single builder wires configuration, DI, and logging together |
| Hosted services | Background workers that participate in the application lifecycle |
| Application lifetime | Started / stopping / stopped event hooks with one-shot semantics |
| Graceful shutdown | Handles SIGINT / SIGTERM with orderly teardown in reverse order |
| Environment-aware | Development / Staging / Production detection and conditional setup |
| Platform-agnostic | Works in CLI apps, servers, background workers, and scheduled jobs |
| Zero reflection | No dart:mirrors, no code generation — full AOT and tree-shaking support |
| Error resilient | Startup rollback, stop-error collection, lifecycle exception aggregation |
Installation
Add to your pubspec.yaml:
dependencies:
davianspace_hosting: ^1.0.3
Then run:
dart pub get
Quick start
import 'package:davianspace_hosting/davianspace_hosting.dart';
void main() async {
final host = await createDefaultBuilder()
.configureServices((context, services) {
services.addHostedService(
(sp) => MyBackgroundWorker(),
);
})
.build();
await host.run(); // Blocks until SIGINT/SIGTERM or programmatic shutdown
}
Ecosystem integration
davianspace_hosting is the orchestration layer that unifies the DavianSpace ecosystem:
| Package | Role |
|---|---|
davianspace_configuration |
Hierarchical configuration (JSON, env vars, in-memory) |
davianspace_dependencyinjection |
Service collection & provider (singleton, scoped, transient) |
davianspace_logging |
Structured logging with providers and filtering |
davianspace_options |
Options pattern for strongly-typed settings |
davianspace_hosting |
Orchestrates all of the above |
davianspace_http_resilience |
(optional) Retry, circuit breaker, timeout policies |
davianspace_http_ratelimit |
(optional) HTTP rate limiting |
Architecture
┌───────────────────────────────────────────────────────────┐
│ Host.run() │
├───────────────────────────┬───────────────────────────────┤
│ ApplicationLifetime │ HostedServiceExecutor │
├───────────────────────────┴───────────────────────────────┤
│ ServiceProvider (DI Container) │
├────────────────┬──────────────────┬───────────────────────┤
│ Configuration │ LoggerFactory │ Options │
└────────────────┴──────────────────┴───────────────────────┘
For a deep dive into internal design decisions, see doc/architecture.md.
Host
The central runtime that manages an application's lifecycle. All interactions flow through the Host interface:
abstract interface class Host {
Configuration get configuration;
ServiceProvider get services;
LoggerFactory get loggerFactory;
Future<void> start();
Future<void> stop();
Future<void> run();
Future<void> dispose();
}
Lifecycle flow:
build() → start() → [running...] → stop() → dispose()
↑ ↑
│ │
onStarted() SIGINT/SIGTERM
requestShutdown()
HostBuilder
Fluent builder for composing the host. All configure* methods are cumulative and chainable:
final host = await createDefaultBuilder()
.configureConfiguration((ctx, config) {
config.addInMemory({'App:Name': 'MyApp'});
})
.configureServices((ctx, services) {
services.addSingleton<IOrderService, OrderService>();
})
.configureLogging((ctx, logging) {
logging.addConsole().setMinimumLevel(LogLevel.debug);
})
.build();
Build pipeline order:
- Configuration callbacks →
Configurationbuilt - Environment resolved →
HostContextfinalised - Logging callbacks →
LoggerFactorybuilt - Framework services registered → User service callbacks
- DI container built →
Hostreturned
Default builder
createDefaultBuilder() provides sensible defaults suitable for most applications:
| Phase | Default behaviour |
|---|---|
| Configuration | appsettings.json → appsettings.{Env}.json → env vars → CLI args |
| Logging | Console logger; debug in Development, info otherwise |
| Services | Configuration, LoggerFactory, Logger, ApplicationLifetime, HostedServiceCollection, HostedServiceExecutor |
Configuration layering (later sources override earlier):
appsettings.json
→ appsettings.Development.json
→ environment variables
→ --key=value arguments
Hosted services
Background tasks that participate in the host lifecycle. Implement HostedService and register with addHostedService:
final class PingService implements HostedService {
Timer? _timer;
@override
Future<void> start() async {
_timer = Timer.periodic(
const Duration(seconds: 30),
(_) => print('ping'),
);
}
@override
Future<void> stop() async {
_timer?.cancel();
}
}
// Registration:
builder.configureServices((ctx, services) {
services.addHostedService((_) => PingService());
});
Service lifecycle guarantees:
- Services start in registration order.
- Services stop in reverse registration order.
- If a service fails during startup, all previously started services are stopped (best-effort).
- During shutdown, errors are collected — all services get a chance to stop.
Application lifetime
React to lifecycle events with one-shot callbacks:
final lifetime = host.services.getRequired<ApplicationLifetime>();
lifetime.onStarted(() => logger.info('Application started'));
lifetime.onStopping(() => logger.info('Graceful shutdown initiated'));
lifetime.onStopped(() => logger.info('Shutdown complete'));
Event semantics:
- Callbacks fire at most once per event.
- Late-registered callbacks on an already-fired event execute immediately.
- Exceptions are collected and thrown as a single
LifetimeEventException.
Environment detection
The environment controls conditional configuration, logging levels, and DI validation:
builder.configureServices((context, services) {
if (context.isDevelopment) {
services.addSingleton<ICache, InMemoryCache>();
} else {
services.addSingleton<ICache, RedisCache>();
}
});
Resolution order:
| Priority | Source | Example |
|---|---|---|
| 1 | DART_ENVIRONMENT env var |
DART_ENVIRONMENT=Development |
| 2 | Hosting:Environment config key |
Set in appsettings.json |
| 3 | Default | Production |
Well-known environments: Development, Staging, Production (via HostEnvironments constants).
Configuration
The default builder loads configuration from multiple sources with a layered override model:
final host = await createDefaultBuilder(args: args)
.configureConfiguration((ctx, config) {
// Add custom sources — these override defaults
config.addInMemory({
'Database:ConnectionString': 'Server=localhost;Database=mydb',
'Cache:Enabled': 'true',
});
})
.build();
// Access configuration anywhere via DI:
final connString = host.configuration['Database:ConnectionString'];
Logging
Logging is configured through the builder and uses the structured logging pipeline from davianspace_logging:
final host = await createDefaultBuilder()
.configureLogging((ctx, logging) {
logging
.addConsole()
.setMinimumLevel(
ctx.isDevelopment ? LogLevel.debug : LogLevel.warning,
);
})
.build();
// Create loggers via DI:
final logger = host.loggerFactory.createLogger('MyComponent');
logger.info('Application configured successfully');
Advanced usage
Multiple hosted services
Register multiple services — they start in order and stop in reverse:
builder.configureServices((ctx, services) {
services
..addHostedService((sp) => DatabaseMigrationService(
config: sp.getRequired<Configuration>(),
))
..addHostedService((sp) => CacheWarmupService(
logger: sp.getRequired<LoggerFactory>().createLogger('CacheWarmup'),
))
..addHostedService((sp) => HealthCheckService());
});
Programmatic shutdown
Trigger shutdown from within your application code:
final class GracefulShutdownService implements HostedService {
GracefulShutdownService(this._lifetime);
final ApplicationLifetime _lifetime;
@override
Future<void> start() async {
// Perform one-time work, then request shutdown
await _performMigration();
_lifetime.requestShutdown();
}
@override
Future<void> stop() async {}
Future<void> _performMigration() async {
// Migration logic...
}
}
Custom host builder
Build a host without the default configuration for full control:
final host = await HostBuilderImpl()
.configureConfiguration((ctx, config) {
config.addInMemory({'Hosting:Environment': 'Production'});
})
.configureLogging((ctx, logging) {
logging.addConsole();
})
.configureServices((ctx, services) {
services.addHostedService((_) => MyService());
})
.build();
await host.run();
Concurrent service execution
The HostedServiceExecutor supports concurrent start/stop for independent services:
// Register a concurrent executor manually:
builder.configureServices((ctx, services) {
// Override the default sequential executor
services.addSingletonFactory<HostedServiceExecutor>(
(sp) => HostedServiceExecutor(
logger: sp.getRequired<LoggerFactory>().createLogger('Executor'),
concurrent: true,
),
);
});
Error handling
The hosting framework provides structured error handling at every lifecycle stage:
| Scenario | Behaviour |
|---|---|
| Service fails to start | Previously started services are rolled back (stopped); HostedServiceException thrown |
| Service fails to stop | Error collected; remaining services still get a chance to stop; HostedServiceStopException thrown |
| Multiple lifetime callbacks fail | Errors collected; single LifetimeEventException thrown |
| Dispose fails | Error logged but not thrown (best-effort cleanup) |
Testing
# Run the full test suite
dart test
# Run with verbose output
dart test --reporter expanded
# Run a specific test group
dart test --name "Host lifecycle"
The package includes 44 tests covering:
- Host builder wiring and build pipeline
- Host context and environment detection
- Host lifecycle (start / stop / run / dispose)
- Hosted service execution order (sequential and concurrent)
- Application lifetime event hooks
- Graceful shutdown (SIGINT/SIGTERM and programmatic)
- Error resilience (startup rollback, stop error collection)
- Async lock concurrency safety
- Default builder configuration
API reference
Core types
| Type | Description |
|---|---|
Host |
Abstract interface for the application runtime |
HostBuilder |
Abstract interface for the fluent builder |
HostContext |
Contextual data during builder callbacks |
HostEnvironments |
Well-known environment name constants |
HostedService |
Abstract interface for background services |
Implementations
| Type | Description |
|---|---|
HostImpl |
Default Host implementation with AsyncLock-protected lifecycle |
HostBuilderImpl |
Default HostBuilder with 3-phase build pipeline |
createDefaultBuilder() |
Factory function with sensible defaults |
Lifecycle
| Type | Description |
|---|---|
ApplicationLifetime |
Started/stopping/stopped event hooks with programmatic shutdown |
LifetimeEvents |
One-shot callback list with error collection |
LifetimeEventException |
Aggregated exception for failed callbacks |
Services
| Type | Description |
|---|---|
HostedServiceCollection |
Registry for hosted-service factories |
HostedServiceExecutor |
Orchestrates start/stop (sequential or concurrent) |
HostedServiceException |
Thrown when a service fails to start |
HostedServiceStopException |
Thrown when services fail to stop |
HostingServiceCollectionExtensions |
addHostedService() / addHostedServiceInstance() |
Utilities
| Type | Description |
|---|---|
AsyncLock |
Async mutex for preventing concurrent lifecycle transitions |
Full API documentation is available via dart doc.
Contributing
See CONTRIBUTING.md for development setup, coding guidelines, and the pull request process.
Security
See SECURITY.md for the vulnerability reporting process and supported versions.
License
MIT — see LICENSE for details.
Libraries
- davianspace_hosting
- Enterprise-grade hosting framework for Dart.