davianspace_hosting 1.0.3
davianspace_hosting: ^1.0.3 copied to clipboard
Enterprise-grade hosting framework for Dart. Unifies configuration, logging, dependency injection, and lifecycle management into a coherent application model.
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.