Skip to content

Repository files navigation

BeyondNet.Bootstrapper

🇬🇧 English | 🇪🇸 Español

A lightweight, extensible library for orchestrating the startup sequence of any .NET application or library. Based on the Composite pattern, it lets you encapsulate each initialization step as an independent, testable unit.

Built on .NET 10 with full support for async/await, Nullable Reference Types, and a Cloud Native observability stack.


Table of Contents

  1. Why BeyondNet.Bootstrapper?
  2. Installation
  3. Core Concepts
  4. Quick Start — Synchronous
  5. Quick Start — Asynchronous
  6. Adapters
  7. Real-World Example — Combining All Adapters
  8. Glossary

Why BeyondNet.Bootstrapper?

Without a standard, startup code tends to become a monolithic block in Program.cs that is hard to test and maintain. This library solves that by enforcing a single rule:

Each initialization concern lives in its own class. The Composite runs them in order.

Benefits:

  • Each bootstrapper is independently unit-testable.
  • The startup sequence is explicit and readable.
  • Adding or removing a step never changes surrounding code.
  • Async I/O at startup cannot deadlock the application.

Installation

Install only the packages you need:

# Core (always required)
dotnet add package BeyondNet.Bootstrapper
# Official adapters (add as needed)
dotnet add package BeyondNet.Bootstrapper.DependencyInjection
dotnet add package BeyondNet.Bootstrapper.AutoMapper
dotnet add package BeyondNet.Bootstrapper.Observability

Core Concepts

IBootstrapper ← synchronous contract
IBootstrapper<T> ← synchronous contract with typed result
IBootstrapperAsync ← async contract with CancellationToken
IBootstrapperAsync<T> ← async contract with typed result
CompositeBootstrapper ← runs IBootstrapper list in order
CompositeBootstrapperAsync ← runs IBootstrapperAsync list in order, respects cancellation

Flow:

App startup
└─ CompositeBootstrapper / CompositeBootstrapperAsync
├─ Step 1: DatabaseBootstrapper.Run()
├─ Step 2: AutoMapperBootstrapper.Run()
└─ Step 3: ObservabilityBootstrapper.Run()

Quick Start — Synchronous

Step 1 — Implement IBootstrapper<T>

usingBeyondNet.Bootstrapper.Interface;// Encapsulates any initialization logic and exposes the result via ResultpublicclassFeatureFlagBootstrapper:IBootstrapper<bool>{publicbool?Result{get;privateset;}publicvoidRun(){// Any synchronous setup: read config, validate env vars, etc.Result=true;}}

Step 2 — Orchestrate with CompositeBootstrapper

usingBeyondNet.Bootstrapper.Impl;varfeatureFlags=newFeatureFlagBootstrapper();newCompositeBootstrapper().Add(featureFlags).Run();if(featureFlags.Result==true)Console.WriteLine("Feature flags ready.");

Multiple steps in sequence

varstep1=newDatabaseBootstrapper(connectionString);varstep2=newCacheBootstrapper(redisUrl);varstep3=newFeatureFlagBootstrapper();newCompositeBootstrapper().Add(step1).Add(step2).Add(step3).Run();

Quick Start — Asynchronous

Use the async engine whenever a step requires I/O (database ping, HTTP call, file read).

Step 1 — Implement IBootstrapperAsync<T>

usingBeyondNet.Bootstrapper.Interface;publicclassDatabaseConnectionBootstrapper:IBootstrapperAsync<bool>{privatereadonlystring_connectionString;publicDatabaseConnectionBootstrapper(stringconnectionString)=>_connectionString=connectionString;publicbool?Result{get;privateset;}publicasyncTaskRunAsync(CancellationTokencancellationToken=default){// Simulate or replace with real DB pingawaitTask.Delay(50,cancellationToken);Result=true;}}

Step 2 — Orchestrate with CompositeBootstrapperAsync

usingBeyondNet.Bootstrapper.Impl;usingvarcts=newCancellationTokenSource(TimeSpan.FromSeconds(10));vardbStep=newDatabaseConnectionBootstrapper("Server=localhost;...");awaitnewCompositeBootstrapperAsync().Add(dbStep).RunAsync(cts.Token);if(dbStep.Result==true)Console.WriteLine("Database connection established.");

Rule: If any step requires async, use CompositeBootstrapperAsync for all steps. Do not mix sync and async bootstrappers.


Adapters

Dependency Injection Adapter

Package:BeyondNet.Bootstrapper.DependencyInjection

Registers services into IServiceCollection and exposes the populated collection as the result.

Minimal example:

usingBeyondNet.Bootstrapper.DependencyInjection;usingMicrosoft.Extensions.DependencyInjection;vardiBootstrapper=newDependencyInjectionBootstrapper(services =>{services.AddSingleton<IGreeter,ConsoleGreeter>();});diBootstrapper.Run();

Full pipeline — register, build provider, resolve:

usingBeyondNet.Bootstrapper.DependencyInjection;usingBeyondNet.Bootstrapper.Impl;usingMicrosoft.Extensions.DependencyInjection;// 1. Create bootstrapper and register dependenciesvardiBootstrapper=newDependencyInjectionBootstrapper(services =>{services.AddSingleton<IOrderRepository,SqlOrderRepository>();services.AddScoped<IOrderService,OrderService>();services.AddLogging();});// 2. Run registrationnewCompositeBootstrapper().Add(diBootstrapper).Run();// 3. Build IServiceProvider from the populated collectionvarprovider=diBootstrapper.Result!.BuildServiceProvider();// 4. Resolve and use your servicesvarorderService=provider.GetRequiredService<IOrderService>();

Pass an existing IServiceCollection (e.g., ASP.NET Core):

// In Program.cs — inject into the existing ASP.NET Core collectionvardiBootstrapper=newDependencyInjectionBootstrapper(builder.Services, services =>{services.AddSingleton<IPaymentGateway,StripeGateway>();});diBootstrapper.Run();

AutoMapper Adapter

Package:BeyondNet.Bootstrapper.AutoMapper

Builds a MapperConfiguration and exposes it as the result so you can create IMapper instances anywhere in your application.

Minimal example:

usingBeyondNet.Bootstrapper.AutoMapper;varmapperBootstrapper=newAutoMapperBootstrapper(cfg =>{cfg.CreateMap<UserEntity,UserDto>();});mapperBootstrapper.Run();

Full pipeline — configure, build, map:

usingBeyondNet.Bootstrapper.AutoMapper;usingBeyondNet.Bootstrapper.Impl;usingAutoMapper;varmapperBootstrapper=newAutoMapperBootstrapper(cfg =>{cfg.CreateMap<ProductEntity,ProductDto>().ForMember(dest =>dest.FullName,
opt =>opt.MapFrom(src =>$"{src.Brand}{src.Model}"));cfg.CreateMap<OrderEntity,OrderSummaryDto>();});newCompositeBootstrapper().Add(mapperBootstrapper).Run();// Build the mapper — do this once and store it (singleton)IMappermapper=mapperBootstrapper.Result!.CreateMapper();// Use it anywherevardto=mapper.Map<ProductDto>(productEntity);

Observability Adapter

Package:BeyondNet.Bootstrapper.Observability

Configures the V1 Observability Stack: structured logs via Serilog (OTLP sink) and distributed tracing via OpenTelemetry, both pointing to a single OTLP collector.

App → ObservabilityBootstrapper → OTLP Collector → Tempo (traces)
→ Loki (logs)
→ Grafana (dashboards)

Minimal example:

usingBeyondNet.Bootstrapper.Observability;usingMicrosoft.Extensions.DependencyInjection;varservices=newServiceCollection();newObservabilityBootstrapper(services,newObservabilityConfiguration{ServiceName="OrderService",ServiceVersion="2.0.0",OTLPEndpoint="http://localhost:4317"}).Run();

Full configuration with all options:

usingBeyondNet.Bootstrapper.Observability;usingMicrosoft.Extensions.DependencyInjection;varconfig=newObservabilityConfiguration{ServiceName="PaymentService",ServiceVersion="1.4.2",OTLPEndpoint="http://otel-collector:4317",// Additional resource attributes forwarded to every trace and logResourceAttributes=newDictionary<string,object>{{"deployment.environment","production"},{"cloud.region","us-east-1"}}};varobsBootstrapper=newObservabilityBootstrapper(services,config);obsBootstrapper.Run();// Result exposes the IServiceCollection with OTel tracing registeredvarprovider=obsBootstrapper.Result!.BuildServiceProvider();

Local environment: the examples/observability/ folder contains a docker-compose.yml that spins up an OTel Collector, Tempo, Loki, and Grafana in one command:

cd examples/observability
docker-compose up -d

Real-World Example — Combining All Adapters

The following shows a complete Program.cs that wires everything together using a single CompositeBootstrapper.

usingBeyondNet.Bootstrapper.Impl;usingBeyondNet.Bootstrapper.DependencyInjection;usingBeyondNet.Bootstrapper.AutoMapper;usingBeyondNet.Bootstrapper.Observability;usingMicrosoft.Extensions.DependencyInjection;// ── 1. Declare each bootstrapper ──────────────────────────────────────varservices=newServiceCollection();vardi=newDependencyInjectionBootstrapper(services, svc =>{svc.AddSingleton<IOrderRepository,SqlOrderRepository>();svc.AddScoped<IOrderService,OrderService>();});varmapper=newAutoMapperBootstrapper(cfg =>{cfg.CreateMap<OrderEntity,OrderDto>();});varobservability=newObservabilityBootstrapper(services,newObservabilityConfiguration{ServiceName="OrderService",ServiceVersion="2.0.0",OTLPEndpoint="http://localhost:4317"});// ── 2. Run all steps in sequence ─────────────────────────────────────newCompositeBootstrapper().Add(di).Add(mapper).Add(observability).Run();// ── 3. Use the results ────────────────────────────────────────────────varprovider=di.Result!.BuildServiceProvider();variMapper=mapper.Result!.CreateMapper();varorderSvc=provider.GetRequiredService<IOrderService>();

Async variant (e.g., startup with database health-check):

usingBeyondNet.Bootstrapper.Impl;usingBeyondNet.Bootstrapper.Interface;publicclassDatabasePingBootstrapper:IBootstrapperAsync<bool>{publicbool?Result{get;privateset;}publicasyncTaskRunAsync(CancellationTokenct=default){// Replace with actual connection checkawaitTask.Delay(20,ct);Result=true;}}// In Program.csusingvarcts=newCancellationTokenSource(TimeSpan.FromSeconds(15));vardbPing=newDatabasePingBootstrapper();awaitnewCompositeBootstrapperAsync().Add(dbPing).RunAsync(cts.Token);if(dbPing.Result!=true)thrownewInvalidOperationException("Database unreachable at startup.");

Glossary

TermDefinition
BootstrapperA class that encapsulates one startup concern (DB connection, DI registration, mapping profiles, etc.) and exposes its result via Result.
CompositeStructural design pattern that groups multiple bootstrappers and runs them sequentially as a single unit.
IBootstrapperAsyncThe async contract. Use it whenever a step performs I/O-bound work (network, disk, database) to avoid blocking the thread pool.
CancellationTokenPassed through RunAsync to allow the host to cancel the entire startup sequence if a timeout is exceeded.
OTLPOpenTelemetry Protocol — the vendor-neutral format for shipping metrics, traces, and logs to a unified collector.
OTel CollectorA proxy that receives OTLP signals and fans them out to storage backends (Tempo for traces, Loki for logs).

About

A lightweight .NET 10 library for orchestrating application startup using the Composite pattern — testable, async-ready, and adapter-extensible.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages