#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) |
#올바른 패턴 선택
#간단한 규칙
- 프레임워크가 보장하는가? →
require() - 선택적 기능인가? →
read() - 그 외 모든 경우 →
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 구현