Expand description
§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)
- MIT license (LICENSE-MIT)
at your option.
§개요
백엔드 서비스를 위한 컴파일 타임 의존성 주입 및 싱글톤 패턴 구현 매크로 크레이트입니다.
이 크레이트는 Spring Framework의 DI 컨테이너와 유사한 방식으로 Rust 서비스의 의존성을 관리하며, 메모리 효율성과 타입 안전성을 보장합니다.
§주요 기능
- 자동 싱글톤 관리:
OnceCell과Arc를 사용한 thread-safe 싱글톤 구현 - 컴파일 타임 의존성 주입:
Arc<T>타입 필드 자동 감지 및 주입 - 전역 레지스트리:
inventory크레이트를 통한 자동 서비스 등록 - Zero-cost abstraction: 런타임 오버헤드 최소화
- Static context 호환: 컴파일 타임에 안전한 등록 시스템
§제공 매크로
§#[service]
비즈니스 로직을 담당하는 서비스 컴포넌트를 정의합니다.
§사용법
ⓘ
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 {
// ...
}§생성되는 메서드
ⓘ
// 싱글톤 인스턴스 가져오기
let service = UserService::instance();
// Service trait 자동 구현
assert_eq!(service.name(), "userservice_service");§#[repository]
데이터 액세스 계층을 담당하는 리포지토리 컴포넌트를 정의합니다. MongoDB 컬렉션과의 통합 및 Redis 캐싱을 지원합니다.
§사용법
ⓘ
#[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>,
}§생성되는 메서드
ⓘ
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 호환: 컴파일 타임에 안전한 함수 포인터 사용
§등록 구조체
ⓘ
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>,
}§동작 원리
- 컴파일 타임: 매크로가 구조체를 분석하고 필요한 코드 생성
- 프로그램 시작:
inventory가 모든 서비스/리포지토리 수집 - 첫 사용:
instance()호출 시 의존성 주입 및 인스턴스 생성 - 재사용: 캐시된 싱글톤 인스턴스 반환
§필수 의존성
이 매크로를 사용하려면 프로젝트에 다음 크레이트가 필요합니다:
once_cell: 싱글톤 저장inventory: 전역 레지스트리async-trait: Repository/Service trait 구현mongodb(repository 사용 시)redis(캐싱 사용 시)
§예제: 완전한 서비스 구성
ⓘ
// 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: 모든 등록은 컴파일 타임에 결정됨
§디버깅
의존성 주입 과정은 콘솔에 로그가 출력됩니다:
ServiceLocator::get called for type: UserRepository
- Creating new instance for UserRepository
- Found matching repository: user_repositoryAttribute Macros§
- repository
- 데이터 액세스 리포지토리를 위한 싱글톤 매크로
- service
- 비즈니스 로직 서비스를 위한 싱글톤 매크로