singleton_macro 0.1.0

Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services
Documentation
//! # Singleton Macro
//!
//! Spring Framework-inspired dependency injection and singleton pattern macros for Rust backend services.
//!
//! Copyright (c) 2025 Janghoon Park <ceo@dataengine.co.kr>
//!
//! Licensed under either of:
//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
//! - MIT license ([LICENSE-MIT](LICENSE-MIT))
//!
//! at your option.
//!
//! ## 개요
//!
//! 백엔드 서비스를 위한 컴파일 타임 의존성 주입 및 싱글톤 패턴 구현 매크로 크레이트입니다.
//!
//! 이 크레이트는 Spring Framework의 DI 컨테이너와 유사한 방식으로 Rust 서비스의
//! 의존성을 관리하며, 메모리 효율성과 타입 안전성을 보장합니다.
//!
//! ## 주요 기능
//!
//! - **자동 싱글톤 관리**: `OnceCell`과 `Arc`를 사용한 thread-safe 싱글톤 구현
//! - **컴파일 타임 의존성 주입**: `Arc<T>` 타입 필드 자동 감지 및 주입
//! - **전역 레지스트리**: `inventory` 크레이트를 통한 자동 서비스 등록
//! - **Zero-cost abstraction**: 런타임 오버헤드 최소화
//! - **Static context 호환**: 컴파일 타임에 안전한 등록 시스템
//!
//! ## 제공 매크로
//!
//! ### `#[service]`
//!
//! 비즈니스 로직을 담당하는 서비스 컴포넌트를 정의합니다.
//!
//! #### 사용법
//! ```rust,ignore
//! use std::sync::Arc;
//!
//! #[service]
//! struct UserService {
//!     user_repo: Arc<UserRepository>,  // 자동 주입
//!     email_service: Arc<EmailService>, // 자동 주입
//!     config: Config,                   // Default::default() 사용
//! }
//!
//! // 커스텀 이름 지정 (자동으로 _service suffix 추가됨)
//! #[service(name = "auth")]  // "auth_service"로 등록됨
//! struct AuthenticationService {
//!     // ...
//! }
//! ```
//!
//! #### 생성되는 메서드
//! ```rust,ignore
//! // 싱글톤 인스턴스 가져오기
//! let service = UserService::instance();
//!
//! // Service trait 자동 구현
//! assert_eq!(service.name(), "userservice_service");
//! ```
//!
//! ### `#[repository]`
//!
//! 데이터 액세스 계층을 담당하는 리포지토리 컴포넌트를 정의합니다.
//! MongoDB 컬렉션과의 통합 및 Redis 캐싱을 지원합니다.
//!
//! #### 사용법
//! ```rust,ignore
//! #[repository]
//! struct UserRepository {
//!     db: Arc<Database>,        // 자동 주입 (필드명이 db/database인 경우)
//!     redis: Arc<RedisClient>,  // 자동 주입 (필드명이 redis/cache인 경우)
//! }
//!
//! // 커스텀 설정 (자동으로 _repository suffix 추가됨)
//! #[repository(name = "user", collection = "users")]  // "user_repository"로 등록됨
//! struct UserRepo {
//!     db: Arc<Database>,
//! }
//! ```
//!
//! #### 생성되는 메서드
//! ```rust,ignore
//! let repo = UserRepository::instance();
//!
//! // MongoDB 컬렉션 접근 (db 필드가 있을 때)
//! let collection = repo.collection::<User>();
//!
//! // 컬렉션 이름 가져오기
//! let name = repo.collection_name(); // "users"
//!
//! // Redis 캐싱 메서드 (redis/cache 필드가 있을 때)
//! let key = repo.cache_key("user123"); // "user_repository:user123"
//! repo.invalidate_cache("user123").await?;
//! repo.invalidate_collection_cache(Some("active")).await?;
//! repo.invalidate_pattern_cache("user_repository:*").await?;
//! ```
//!
//! ## 의존성 주입 규칙
//!
//! 필드 타입과 이름에 따라 자동으로 의존성이 주입됩니다:
//!
//! | 필드 타입 | 필드 이름 | 주입 방식 |
//! |-----------|-----------|-----------|
//! | `Arc<T>` | 모든 이름 | `ServiceLocator::get::<T>()` |
//! | 모든 타입 | `db`, `database` | `ServiceLocator::get::<Database>()` |
//! | 모든 타입 | `redis`, `cache` | `ServiceLocator::get::<RedisClient>()` |
//! | 기타 | 기타 | `Default::default()` |
//!
//! ## 레지스트리 시스템
//!
//! ### 자동 등록
//! - **서비스**: `{name}_service` 형태로 자동 등록
//! - **리포지토리**: `{name}_repository` 형태로 자동 등록
//! - **Static 호환**: 컴파일 타임에 안전한 함수 포인터 사용
//!
//! ### 등록 구조체
//! ```rust,ignore
//! pub struct ServiceRegistration {
//!     pub name: &'static str,
//!     pub constructor: fn() -> Box<dyn Any + Send + Sync>,
//! }
//!
//! pub struct RepositoryRegistration {
//!     pub name: &'static str,
//!     pub constructor: fn() -> Box<dyn Any + Send + Sync>,
//! }
//! ```
//!
//! ## 동작 원리
//!
//! 1. **컴파일 타임**: 매크로가 구조체를 분석하고 필요한 코드 생성
//! 2. **프로그램 시작**: `inventory`가 모든 서비스/리포지토리 수집
//! 3. **첫 사용**: `instance()` 호출 시 의존성 주입 및 인스턴스 생성
//! 4. **재사용**: 캐시된 싱글톤 인스턴스 반환
//!
//! ## 필수 의존성
//!
//! 이 매크로를 사용하려면 프로젝트에 다음 크레이트가 필요합니다:
//! - `once_cell`: 싱글톤 저장
//! - `inventory`: 전역 레지스트리
//! - `async-trait`: Repository/Service trait 구현
//! - `mongodb` (repository 사용 시)
//! - `redis` (캐싱 사용 시)
//!
//! ## 예제: 완전한 서비스 구성
//!
//! ```rust,ignore
//! // 1. Repository 정의
//! #[repository(collection = "users")]
//! struct UserRepository {
//!     db: Arc<Database>,
//!     redis: Arc<RedisClient>,
//! }
//!
//! impl UserRepository {
//!     async fn find_by_id(&self, id: &str) -> Option<User> {
//!         // 캐시 확인
//!         let cache_key = self.cache_key(id);
//!         if let Ok(Some(cached)) = self.redis.get(&cache_key).await {
//!             return Some(cached);
//!         }
//!
//!         // DB 조회
//!         let user = self.collection()
//!             .find_one(doc! { "_id": id }, None)
//!             .await
//!             .ok()
//!             .flatten();
//!
//!         // 캐시 저장
//!         if let Some(ref user) = user {
//!             self.redis.set_with_expiry(&cache_key, user, 600).await;
//!         }
//!
//!         user
//!     }
//! }
//!
//! // 2. Service 정의
//! #[service]
//! struct UserService {
//!     repo: Arc<UserRepository>,
//! }
//!
//! impl UserService {
//!     async fn get_user(&self, id: &str) -> Result<User> {
//!         self.repo.find_by_id(id).await
//!             .ok_or_else(|| Error::NotFound)
//!     }
//! }
//!
//! // 3. 사용
//! #[actix_web::main]
//! async fn main() {
//!     // ServiceLocator 초기화 (Database, RedisClient 등록)
//!
//!     // 서비스 사용
//!     let user_service = UserService::instance();
//!     let user = user_service.get_user("123").await?;
//! }
//! ```
//!
//! ## 주의사항
//!
//! - **순환 의존성 방지**: A가 B를 의존하고 B가 A를 의존하면 런타임 패닉 발생
//! - **ServiceLocator 초기화**: Database, RedisClient 등 핵심 서비스는 미리 등록 필요
//! - **Thread-safety**: 모든 의존성은 `Arc`로 감싸져 있어야 함
//! - **Static context**: 모든 등록은 컴파일 타임에 결정됨
//!
//! ## 디버깅
//!
//! 의존성 주입 과정은 콘솔에 로그가 출력됩니다:
//! ```text
//! ServiceLocator::get called for type: UserRepository
//!   - Creating new instance for UserRepository
//!   - Found matching repository: user_repository
//! ```
mod service;
mod repository;

use proc_macro::TokenStream;

/// 비즈니스 로직 서비스를 위한 싱글톤 매크로
///
/// 이 매크로는 다음을 자동으로 생성합니다:
/// - 싱글톤 인스턴스 관리 (`instance()` 메서드)
/// - 의존성 자동 주입 (`new()` 메서드)
/// - 전역 레지스트리 등록 (이름에 `_service` suffix 자동 추가)
/// - Service trait 구현
///
/// # 인자
///
/// - `name` (선택): 서비스 이름. 기본값은 구조체 이름의 소문자 + "_service"
///
/// # 의존성 주입 규칙
///
/// - `Arc<T>` 타입: `ServiceLocator::get::<T>()` 자동 주입
/// - 기타 타입: `Default::default()` 사용
///
/// # 예제
///
/// ```rust,ignore
/// #[service]
/// struct PaymentService {
///     payment_repo: Arc<PaymentRepository>,  // 자동 주입
///     notification: Arc<NotificationService>, // 자동 주입
///     retry_count: u32,                      // Default::default()
/// }
///
/// // 커스텀 이름 지정
/// #[service(name = "payment")]  // "payment_service"로 등록됨
/// struct PaymentProcessor {
///     // ...
/// }
///
/// // 사용
/// let service = PaymentService::instance();
/// assert_eq!(service.name(), "paymentservice_service");
/// ```
///
/// # 생성되는 코드
///
/// - `instance()`: 싱글톤 인스턴스 접근
/// - `new()`: 의존성 주입을 통한 인스턴스 생성 (private)
/// - Service trait 구현 (`name()`, `init()`)
/// - 전역 레지스트리 자동 등록
#[proc_macro_attribute]
pub fn service(args: TokenStream, input: TokenStream) -> TokenStream {
    service::implement_service(args, input)
}

/// 데이터 액세스 리포지토리를 위한 싱글톤 매크로
///
/// Service 매크로와 유사하지만 추가로 다음을 제공합니다:
/// - MongoDB 컬렉션 통합 (`collection()` 메서드) - db 필드가 있을 때
/// - 컬렉션 이름 관리 (`collection_name()` 메서드)
/// - Redis 캐싱 헬퍼 메서드들 - redis/cache 필드가 있을 때:
///   - `cache_key()` - 엔티티 캐시 키 생성
///   - `collection_cache_key()` - 컬렉션 캐시 키 생성
///   - `invalidate_cache()` - 캐시 무효화
///   - `invalidate_collection_cache()` - 컬렉션 캐시 무효화
///   - `invalidate_pattern_cache()` - 패턴 기반 캐시 무효화
///
/// # 인자
///
/// - `name` (선택): 리포지토리 이름. 기본값은 구조체 이름의 소문자 + "_repository"
/// - `collection` (선택): MongoDB 컬렉션 이름. 기본값은 구조체 이름에서 "Repository" 제거 후 복수형
///
/// # 의존성 주입 규칙
///
/// - `db`, `database` 필드: `ServiceLocator::get::<Database>()` 자동 주입
/// - `redis`, `cache` 필드: `ServiceLocator::get::<RedisClient>()` 자동 주입
/// - `Arc<T>` 타입: `ServiceLocator::get::<T>()` 자동 주입
/// - 기타 타입: `Default::default()` 사용
///
/// # 예제
///
/// ```rust,ignore
/// #[repository(name = "order", collection = "orders")]
/// struct OrderRepository {
///     db: Arc<Database>,          // 자동 주입 (Database)
///     redis: Arc<RedisClient>,    // 자동 주입 (RedisClient)
///     user_service: Arc<UserService>, // 자동 주입 (ServiceLocator)
/// }
///
/// // 사용
/// let repo = OrderRepository::instance();
///
/// // MongoDB 작업 (db 필드가 있으므로 자동 생성됨)
/// let orders = repo.collection::<Order>()
///     .find(doc! { "status": "pending" }, None)
///     .await?;
///
/// // Redis 캐싱 (redis 필드가 있으므로 자동 생성됨)
/// let key = repo.cache_key("order123");              // "order_repository:order123"
/// repo.invalidate_cache("order123").await?;
/// repo.invalidate_pattern_cache("order_repository:*").await?;
///
/// // 컬렉션 이름
/// assert_eq!(repo.collection_name(), "orders");
/// assert_eq!(repo.name(), "order_repository");
/// ```
///
/// # 생성되는 코드
///
/// - `instance()`: 싱글톤 인스턴스 접근
/// - `new()`: 의존성 주입을 통한 인스턴스 생성 (private)
/// - `collection_name()`: MongoDB 컬렉션 이름 반환
/// - `collection<T>()`: MongoDB 컬렉션 접근 (db 필드 있을 때만)
/// - Redis 캐싱 메서드들 (redis/cache 필드 있을 때만)
/// - Repository trait 구현 (`name()`, `collection_name()`, `init()`)
/// - 전역 레지스트리 자동 등록
///
/// # 특별한 필드 처리
///
/// | 필드 이름 | 자동 주입 타입 | 추가 메서드 |
/// |-----------|----------------|-------------|
/// | `db`, `database` | `Database` | `collection<T>()` |
/// | `redis`, `cache` | `RedisClient` | 캐싱 메서드들 |
#[proc_macro_attribute]
pub fn repository(args: TokenStream, input: TokenStream) -> TokenStream {
    repository::implement_repository(args, input)
}