# Singleton Macro
[](https://crates.io/crates/singleton_macro)
[](https://docs.rs/singleton_macro)
[](LICENSE)
Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services.
## Features
- 🚀 **Zero-boilerplate** singleton pattern with automatic dependency injection
- 🔄 **Spring-like DI**: `Arc<T>` fields are automatically injected via ServiceLocator
- 📦 **Service & Repository** macros for clean architecture
- 🛡️ **Thread-safe** using `Arc` and `OnceCell`
- 🎯 **MongoDB & Redis** integration helpers
- ⚡ **Compile-time** dependency resolution with `inventory` crate
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
singleton_macro = "0.1.0"
once_cell = "1.0"
inventory = "0.3"
async-trait = "0.1"
# Optional: for MongoDB & Redis integration
mongodb = "3.2"
redis = "0.31"
```
## Usage
### Basic Service
```rust,ignore
use singleton_macro::service;
use std::sync::Arc;
#[service]
struct UserService {
user_repo: Arc<UserRepository>, // Auto-injected
email_service: Arc<EmailService>, // Auto-injected
retry_count: u32, // Default::default()
}
impl UserService {
async fn create_user(&self, email: &str) -> Result<User, Error> {
let user = self.user_repo.find_by_email(email).await?;
self.email_service.send_welcome(&user).await?;
Ok(user)
}
}
// Usage
let service = UserService::instance();
```
### Repository with MongoDB & Redis
```rust,ignore
use singleton_macro::repository;
use std::sync::Arc;
#[repository(collection = "users")]
struct UserRepository {
db: Arc<Database>, // Auto-injected
redis: Arc<RedisClient>, // Auto-injected
}
impl UserRepository {
async fn find_by_email(&self, email: &str) -> Result<Option<User>, Error> {
// Check cache first
let cache_key = self.cache_key(&format!("email:{}", email));
if let Ok(Some(cached)) = self.redis.get(&cache_key).await {
return Ok(Some(cached));
}
// Query database
let user = self.collection::<User>()
.find_one(doc! { "email": email })
.await?;
// Cache result
if let Some(ref user) = user {
self.redis.set_with_expiry(&cache_key, user, 300).await?;
}
Ok(user)
}
}
```
### Service Registry Setup
```rust,ignore
use crate::core::registry::ServiceLocator;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize core services
let db = Arc::new(Database::connect("mongodb://localhost").await?);
let redis = Arc::new(RedisClient::connect("redis://localhost").await?);
// Register with ServiceLocator
ServiceLocator::set(db);
ServiceLocator::set(redis);
// Initialize all services (discovers via inventory)
ServiceLocator::initialize_all().await?;
// Use your services
let user_service = UserService::instance();
let user = user_service.create_user("test@example.com").await?;
Ok(())
}
```
## Generated Code
The `#[service]` macro generates:
```rust
impl UserService {
pub fn instance() -> Arc<Self> { /* singleton logic */ }
fn new() -> Self { /* dependency injection */ }
}
impl Service for UserService {
fn name(&self) -> &str { "userservice" }
async fn init(&self) -> Result<(), Box<dyn Error>> { /* */ }
}
```
The `#[repository]` macro additionally generates:
```rust
impl UserRepository {
pub fn collection<T>(&self) -> mongodb::Collection<T> { /* */ }
pub fn cache_key(&self, id: &str) -> String { /* */ }
pub async fn invalidate_cache(&self, id: &str) -> Result<(), Error> { /* */ }
// ... more caching helpers
}
```
## Dependency Injection Rules
| `Arc<T>` | any | `ServiceLocator::get::<T>()` |
| any | `db`, `database` | `ServiceLocator::get::<Database>()` |
| any | `redis`, `cache` | `ServiceLocator::get::<RedisClient>()` |
| other | any | `Default::default()` |
## Registry System
Services and repositories are automatically registered:
- `#[service]` → `{name}`
- `#[repository]` → `{name}`
```rust,ignore
#[service(name = "auth")] // Registered as "auth"
#[repository(name = "user")] // Registered as "user"
```
## Architecture Example
```rust,ignore
// 1. Repository Layer
#[repository(collection = "users")]
struct UserRepository {
db: Arc<Database>,
redis: Arc<RedisClient>,
}
// 2. Service Layer
#[service]
struct UserService {
user_repo: Arc<UserRepository>, // Injected
}
// 3. Application Layer
#[service]
struct UserController {
user_service: Arc<UserService>, // Injected
auth_service: Arc<AuthService>, // Injected
}
```
## Best Practices
### ✅ Do
- Use clear layered architecture (Repository → Service → Controller)
- Register core dependencies (Database, Redis) before using services
- Keep services focused on single responsibilities
### ❌ Avoid
- Circular dependencies (A → B → A will panic at runtime)
- Direct repository access from controllers (use services)
- Mixing business logic in repositories
## Error Handling
Common issues and solutions:
```rust
// ❌ Circular dependency
#[service] struct A { b: Arc<B> }
#[service] struct B { a: Arc<A> } // Runtime panic!
// ✅ Proper layering
#[service] struct A { repo: Arc<Repository> }
#[service] struct B { repo: Arc<Repository>, a: Arc<A> }
```
## Author
**Janghoon Park** <ceo@dataengine.co.kr>
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option.
---
Copyright (c) 2025 Janghoon Park <ceo@dataengine.co.kr>