| Crates.io | singleton_macro |
| lib.rs | singleton_macro |
| version | 0.1.0 |
| created_at | 2025-06-06 19:47:35.70855+00 |
| updated_at | 2025-06-06 19:47:35.70855+00 |
| description | Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services |
| homepage | https://github.com/imaustinpark/single_macro.git |
| repository | https://github.com/imaustinpark/single_macro.git |
| max_upload_size | |
| id | 1703385 |
| size | 90,333 |
Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services.
Arc<T> fields are automatically injected via ServiceLocatorArc and OnceCellinventory crateAdd to your Cargo.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"
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();
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)
}
}
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(())
}
The #[service] macro generates:
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:
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
}
| Field Type | Field Name | Injection |
|---|---|---|
Arc<T> |
any | ServiceLocator::get::<T>() |
| any | db, database |
ServiceLocator::get::<Database>() |
| any | redis, cache |
ServiceLocator::get::<RedisClient>() |
| other | any | Default::default() |
Services and repositories are automatically registered:
#[service] → {name}#[repository] → {name}#[service(name = "auth")] // Registered as "auth"
#[repository(name = "user")] // Registered as "user"
// 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
}
Common issues and solutions:
// ❌ 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> }
Janghoon Park ceo@dataengine.co.kr
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under either of
at your option.
Copyright (c) 2025 Janghoon Park ceo@dataengine.co.kr