singleton_macro 0.1.0

Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services
Documentation
# Singleton Macro

[![Crates.io](https://img.shields.io/crates/v/singleton_macro.svg)](https://crates.io/crates/singleton_macro)
[![Documentation](https://docs.rs/singleton_macro/badge.svg)](https://docs.rs/singleton_macro)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](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

| 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()` |

## 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>