#Bus 접근 패턴 — 의사결정 가이드

버전: 0.33.0 최종 업데이트: 2026-03-15 적용 대상: ranvier-core 카테고리: 가이드


#개요

Bus는 Ranvier의 타입 안전 리소스 컨테이너입니다. Transition은 4가지 접근 메서드를 통해 Bus에서 공유 리소스(데이터베이스 풀, 인증 컨텍스트, 설정)에 접근합니다. 이 가이드는 올바른 메서드를 선택하는 데 도움을 줍니다.


#의사결정 트리

Must the resource exist in the Bus?

├── YES: A prior step structurally guarantees it
│   │   (e.g., AuthContext after with_iam())
│   │
│   └── require::<T>()
│       Returns: &T
│       On missing: panic (invariant violation → programming error)
│       Use for: Resources whose insertion is guaranteed by the framework

├── MAYBE: Can proceed without it
│   │
│   └── read::<T>()
│       Returns: Option<&T>
│       On missing: None (+ tracing warning on BusAccessPolicy violation)
│       Use for: Optional features (e.g., TenantId-based filtering)

└── YES: Must return an error if missing

    └── get::<T>() / try_require::<T>()
        Returns: Result<&T, BusAccessError>  /  Option<&T>
        On missing: Err(BusAccessError::NotFound)  /  None
        Use for: Default choice for production code

#메서드 비교

메서드 반환 타입 부재 시 사용 시기
require::<T>() &T panic 프레임워크가 삽입을 보장 (예: with_iam() 이후의 AuthContext)
read::<T>() Option<&T> None 선택적 리소스 — 없어도 진행 가능
get::<T>() Result<&T, BusAccessError> Err(NotFound) 오류 메시지가 필요한 프로덕션 코드
try_require::<T>() Option<&T> None read()의 의미적 별칭

#가변 변형

메서드 반환 타입 부재 시
read_mut::<T>() Option<&mut T> None
get_mut::<T>() Result<&mut T, BusAccessError> Err(NotFound)

#사용 예제

#`require()` — 프레임워크에 의해 보장됨

#[transition]
async fn check_permission(
    state: Request,
    _resources: &(),
    bus: &mut Bus,
) -> Outcome<Request, AppError> {
    // AuthContext is always present after with_iam() setup
    // If it's missing, that's a programming error → panic is correct
    let auth = bus.require::<AuthContext>();

    if !auth.has_role("admin") {
        return Outcome::Fault(AppError::Forbidden);
    }

    Outcome::Next(state)
}

#`read()` — 선택적 향상

#[transition]
async fn filter_by_tenant(
    state: Vec<Item>,
    _resources: &(),
    bus: &mut Bus,
) -> Outcome<Vec<Item>, AppError> {
    // TenantId may or may not be present
    // If absent, return all items (no filtering)
    let filtered = match bus.read::<TenantId>() {
        Some(tenant) => state.into_iter()
            .filter(|item| item.tenant_id == tenant.0)
            .collect(),
        None => state,
    };

    Outcome::Next(filtered)
}

#`get()` — 프로덕션 기본값

#[transition]
async fn load_config(
    state: Request,
    _resources: &(),
    bus: &mut Bus,
) -> Outcome<Request, AppError> {
    // AppConfig should be in the Bus, but we want a clear error if missing
    let config = bus.get::<AppConfig>()
        .map_err(|e| AppError::Internal(format!("Config missing: {}", e)))?;

    // Use config...
    Outcome::Next(state)
}

#리소스 삽입

리소스는 일반적으로 애플리케이션 설정 중에 삽입됩니다:

let mut bus = Bus::new();

// Database pool
bus.insert(db_pool);

// Application config
bus.insert(app_config);

// Auth context (usually inserted by framework via with_iam())
bus.insert(auth_context);

#헬퍼 메서드

메서드 설명
insert::<T>(value) 타입별로 리소스 삽입 또는 교체
provide::<T>(value) insert의 의미적 별칭
remove::<T>() 리소스 제거 및 반환 (Option<T>)
has::<T>() 리소스 존재 여부 확인 (bool)

#올바른 패턴 선택

#간단한 규칙

  1. 프레임워크가 보장하는가?require()
  2. 선택적 기능인가?read()
  3. 그 외 모든 경우get()

#안티패턴

패턴 문제 해결
사용자 제공 설정에 require() 사용 설정 누락 시 프로덕션에서 panic 적절한 오류 메시지와 함께 get() 사용
read()로 누락된 의존성을 조용히 무시 버그가 눈에 띄지 않음 get()을 사용하고 오류 처리
동일 타입에 대해 require() 여러 번 호출 중복 조회 한 번 읽고 로컬 변수에 저장

#Bus 접근 정책 (고급)

bus_allow / bus_deny 매크로는 Transition이 접근할 수 있는 타입을 제한합니다. 이를 통해 Transition 수준에서 의존성 경계를 강제합니다.

// This transition can only read AuthContext and AppConfig from the Bus
bus_allow!(MyTransition, AuthContext, AppConfig);

정책 위반은 tracing::warn!을 통해 로그되지만 접근을 차단하지는 않습니다 (소프트 강제). 이는 개발 중 Bus 사용 패턴을 감사하는 데 유용합니다.


#관련 문서

  • examples/bus-capability-demo — Bus 접근 정책 데모
  • examples/outcome-variants-demo — Outcome 제어 흐름 패턴
  • ranvier-core/src/bus.rs — Bus 구현