The bootstrap module handles application initialization, dependency injection, and service registration. It wires together all layers of the application.
AppState
Central dependency injection container managing all services and shared resources.
Global application state containing configuration, database pool, and all services.
Structure
#[derive(Clone)]
pub struct AppState {
pub config: Arc<AppConfig>,
pub pool: PgPool,
pub auth_service: Arc<AuthService>,
pub user_service: Arc<UserService>,
pub test_item_service: Arc<TestItemService>,
}
Application configuration (JWT secret, database URL, etc.)
SQLx PostgreSQL connection pool
Authentication service instance
User management service instance
Test item service instance
Constructor
pub fn new(config: AppConfig, pg_pool: PgPool) -> Self
Initializes all dependencies and wires them together.
Example from source (app_state.rs:26-65):
pub fn new(config: AppConfig, pg_pool: PgPool) -> Self {
let config = Arc::new(config);
// ============================================
// Repositories
// ============================================
let user_repository: Arc<dyn UserRepository> =
Arc::new(PostgresUserRepository::new(pg_pool.clone()));
let test_item_repository: Arc<dyn TestItemRepository> =
Arc::new(PostgresTestItemRepository::new(pg_pool.clone()));
// ============================================
// Services
// ============================================
let auth_service = Arc::new(AuthService::new(
user_repository.clone(),
config.clone(),
));
let user_service = Arc::new(UserService::new(
user_repository.clone(),
config.clone(),
));
let test_item_service = Arc::new(TestItemService::new(
test_item_repository.clone(),
));
// ============================================
// Return AppState
// ============================================
Self {
config,
pool: pg_pool,
auth_service,
user_service,
test_item_service,
}
}
Dependency Graph
The constructor establishes the following dependency graph:
AppState
├── config: Arc<AppConfig>
├── pool: PgPool
├── auth_service: Arc<AuthService>
│ ├── user_repository: Arc<dyn UserRepository>
│ │ └── PostgresUserRepository(pool)
│ └── config: Arc<AppConfig>
├── user_service: Arc<UserService>
│ ├── user_repository: Arc<dyn UserRepository> (shared)
│ └── config: Arc<AppConfig> (shared)
└── test_item_service: Arc<TestItemService>
└── test_item_repository: Arc<dyn TestItemRepository>
└── PostgresTestItemRepository(pool)
Usage in Main
AppState is typically created in main.rs and passed to Actix-web:
let app_state = AppState::new(config, pg_pool);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_state.clone()))
.app_data(web::Data::new(app_state.config.clone()))
.app_data(web::Data::new(app_state.auth_service.clone()))
.app_data(web::Data::new(app_state.user_service.clone()))
.app_data(web::Data::new(app_state.test_item_service.clone()))
// ... routes
})
Adding New Services
To add a new service to AppState:
- Add field to struct:
pub struct AppState {
// ... existing fields
pub new_service: Arc<NewService>,
}
- Create repository and service in constructor:
let new_repository: Arc<dyn NewRepository> =
Arc::new(PostgresNewRepository::new(pg_pool.clone()));
let new_service = Arc::new(NewService::new(new_repository));
- Add to Self initialization:
Self {
// ... existing fields
new_service,
}
- Register in Actix app:
.app_data(web::Data::new(app_state.new_service.clone()))
Service Providers
Service provider pattern for modular service registration (currently under development).
Trait for registering and booting services.
ServiceProvider Trait
pub trait ServiceProvider: Send + Sync {
/// Unique name for this provider
fn name(&self) -> &str;
/// Register services in the container
fn register(&self, container: &mut ServiceContainer);
/// Boot services (optional)
fn boot(&self, _container: &ServiceContainer) {
// Default: do nothing
}
}
Returns unique identifier for the provider
register
fn(&mut ServiceContainer)
Registers services in dependency injection container
Optional initialization logic after all providers are registered
ProviderRegistry
Manages collection of service providers with two-phase initialization.
Registry for managing service providers.
pub struct ProviderRegistry {
providers: Vec<Box<dyn ServiceProvider>>,
}
Methods
new
Creates empty provider registry.
register
pub fn register<P: ServiceProvider + 'static>(&mut self, provider: P)
Adds provider to registry.
Service provider to register
boot_all
pub fn boot_all(&self, container: &mut ServiceContainer)
Executes two-phase initialization:
- Phase 1: Calls
register() on all providers
- Phase 2: Calls
boot() on all providers
Example from source (providers.rs:36-48):
pub fn boot_all(&self, container: &mut ServiceContainer) {
// Phase 1: Register
for provider in &self.providers {
tracing::debug!("Registering provider: {}", provider.name());
provider.register(container);
}
// Phase 2: Boot
for provider in &self.providers {
tracing::debug!("Booting provider: {}", provider.name());
provider.boot(container);
}
}
Example Provider Implementation
struct AuthServiceProvider;
impl ServiceProvider for AuthServiceProvider {
fn name(&self) -> &str {
"AuthServiceProvider"
}
fn register(&self, container: &mut ServiceContainer) {
// Register user repository
container.singleton::<Arc<dyn UserRepository>>(|| {
Arc::new(PostgresUserRepository::new(container.get::<PgPool>()))
});
// Register auth service
container.singleton::<Arc<AuthService>>(|| {
Arc::new(AuthService::new(
container.get::<Arc<dyn UserRepository>>(),
container.get::<Arc<AppConfig>>(),
))
});
}
fn boot(&self, container: &ServiceContainer) {
// Optional: Run any initialization code
tracing::info!("Auth service initialized");
}
}
Using Providers
let mut registry = ProviderRegistry::new();
registry.register(AuthServiceProvider);
registry.register(UserServiceProvider);
registry.register(TestItemServiceProvider);
let mut container = ServiceContainer::new();
registry.boot_all(&mut container);
Macros
Bootstrap module provides macros for reducing boilerplate (implementation details in macros.rs).
register_services! (Future)
Macro for registering multiple services at once:
let app = register_services!(
app,
app_state,
config,
pool,
auth_service,
user_service,
test_item_service
);
Design Patterns
Dependency Injection
AppState implements constructor injection, creating all dependencies in one place and passing them to consumers.
Service Locator
AppState acts as a service locator, providing access to all application services.
Shared Ownership
All services use Arc for thread-safe shared ownership, allowing cloning without data duplication.
Two-Phase Initialization
Provider registry separates registration (phase 1) from bootstrapping (phase 2), allowing providers to depend on services registered by other providers.
Best Practices
Repository Trait Objects
Always use Arc<dyn Trait> for repositories to enable dependency inversion:
let user_repository: Arc<dyn UserRepository> =
Arc::new(PostgresUserRepository::new(pg_pool.clone()));
Shared Dependencies
Reuse Arc clones for dependencies used by multiple services:
let user_repository: Arc<dyn UserRepository> = /* ... */;
let auth_service = Arc::new(AuthService::new(
user_repository.clone(), // Clone Arc, not data
config.clone(),
));
let user_service = Arc::new(UserService::new(
user_repository.clone(), // Same repository instance
config.clone(),
));
Configuration
Wrap configuration in Arc once and share:
let config = Arc::new(config); // Wrap once
// Pass config.clone() to all services