#핵심 패러다임 — 네 가지 기둥

버전: 0.33.0 최종 업데이트: 2026-03-15 적용 대상: ranvier-core, ranvier-macros, ranvier-runtime 카테고리: Philosophy & Architecture


Ranvier의 정체성은 함께 작동하여 Schematic 우선의 시각화 가능한 프레임워크를 만드는 네 가지 기본 개념 위에 구축됩니다.

#Transition

Transition은 하나의 상태를 다른 상태로 변환하는 순수하고 합성 가능한 함수이며, 타입이 지정된 에러로 실패할 수 있습니다. Ranvier에서 연산의 기본 단위입니다.

#주요 특성

  • 순수: 동일한 입력이 주어지면 항상 동일한 출력을 생성 (비동기 I/O 제외)
  • 타입 지정: 입력, 출력, 에러 타입이 명시적
  • 합성 가능: .pipe(), .fanout(), .parallel()로 Transition을 체이닝 가능
  • 테스트 가능: 격리된 환경에서 쉽게 단위 테스트 가능

#왜 중요한가

  • 타입 안전성: 컴파일러가 빌드 시 잘못된 상태 전환을 포착
  • 합성: 단순한 Transition을 체이닝하여 복잡한 워크플로우 구축
  • 가시성: 각 Transition이 Schematic 그래프에 노드로 표시
  • 테스트: 인프라에 접근하지 않고 입출력 모킹 가능

#예제

use ranvier::prelude::*;

#[transition]
async fn validate_input(req: Request) -> Outcome<ValidRequest, ValidationError> {
    if req.body.is_empty() {
        return Outcome::err(ValidationError::EmptyBody);
    }
    Outcome::ok(ValidRequest::from(req))
}

#[transition]
async fn process(input: ValidRequest) -> Outcome<Response, ProcessError> {
    // Business logic here
    let result = compute(&input).await?;
    Outcome::ok(Response::new(result))
}

// Compose:
let pipeline = Axon::simple::<AppError>()
    .pipe(validate_input, process)
    .build();

#Outcome

Outcome은 성공 또는 실패를 나타내는 Ranvier의 결과 타입이며, 명시적 에러 타입을 가집니다. Transition 시스템과 통합됩니다.

#주요 특성

  • 명시적 에러: 각 Transition이 에러 타입을 선언
  • Bus 통합: 성공적인 Outcome이 Bus에 값을 저장 가능
  • Schematic 메타데이터: Outcome이 시각화를 위한 메타데이터를 전달
  • 편리한 사용: ? 연산자 사용 가능, .map(), .and_then() 등 헬퍼 메서드 제공

#왜 중요한가

  • 에러 투명성: 타입 시그니처에서 모든 가능한 실패를 확인
  • 우아한 성능 저하: 올바른 수준(Transition, 파이프라인, 전역)에서 에러 처리
  • 디버깅: Outcome 메타데이터가 Schematic을 통한 실패 추적을 지원

#예제

// Transition returns Outcome
#[transition]
async fn fetch_user(id: UserId) -> Outcome<User, DatabaseError> {
    match db.get_user(id).await {
        Ok(user) => Outcome::ok(user),
        Err(e) => Outcome::err(DatabaseError::from(e)),
    }
}

// Outcome values automatically stored in Bus if marked
#[transition]
async fn enrich_user(user: User) -> Outcome<EnrichedUser, EnrichmentError> {
    // 'user' came from Bus (injected automatically)
    let profile = fetch_profile(&user).await?;
    Outcome::ok(EnrichedUser { user, profile })
}

#Bus

Bus는 단일 실행 컨텍스트 내에서 Transition 간에 상태를 공유하기 위한 타입 안전 인메모리 저장소입니다. "데이터를 위한 의존성 주입"이라고 생각하세요.

#주요 특성

  • 타입으로 인덱싱: 타입별로 값을 저장하고 검색 (TypeMap과 유사)
  • 자동 주입: Transition이 매개변수를 통해 Bus에서 값을 요청 가능
  • 범위 지정: 각 실행이 자체 Bus 인스턴스를 가짐 (전역 상태 없음)
  • 불변 참조: Transition이 Bus에서 &T를 수신 (소유권 이전 없음)

#왜 중요한가

  • 명시적 의존성: Transition 시그니처가 필요한 데이터를 표시
  • 마법 같은 전역 없음: 모든 상태가 명시적이고 실행에 범위 지정
  • 테스트 가능성: 테스트를 위해 Bus에 모의 값 주입
  • 컨텍스트 전파: 인증, 테넌트 ID 등을 파이프라인을 통해 전달

#예제

#[transition]
async fn authenticate(req: Request) -> Outcome<AuthContext, AuthError> {
    let token = extract_token(&req)?;
    let auth = validate(token).await?;
    // AuthContext automatically stored in Bus for downstream transitions
    Outcome::ok(auth)
}

#[transition]
async fn authorize(auth: &AuthContext) -> Outcome<(), AuthError> {
    // 'auth' automatically injected from Bus (by type)
    if !auth.has_role("admin") {
        return Outcome::err(AuthError::Unauthorized);
    }
    Outcome::ok(())
}

#[transition]
async fn handle_request(auth: &AuthContext, req: &Request) -> Outcome<Response, AppError> {
    // Both 'auth' and 'req' injected from Bus
    Ok(Response::new(format!("Hello, {}", auth.user_id)))
}

#Schematic

Schematic은 Transition 파이프라인의 방향성 비순환 그래프(DAG) 표현입니다. 런타임 실행 모델이자 VSCode와 같은 도구가 렌더링할 수 있는 시각적 산출물(JSON)입니다.

#주요 특성

  • 노드: 각 Transition이 하나의 노드
  • 엣지: Transition 간의 데이터 흐름
  • 메타데이터: 타입, 에러 경로, 실행 통계
  • 직렬화 가능: 시각화를 위해 schematic.json으로 내보내기

#왜 중요한가

  • 가시성: 전체 데이터 흐름을 한눈에 파악 (VSCode Circuit 뷰에서)
  • 문서화: Schematic이 곧 문서 (항상 최신 상태)
  • 디버깅: 그래프를 통해 에러 추적, 어떤 노드가 실패했는지 확인
  • 최적화: 병목 지점 식별, 가능한 곳에서 병렬화

#예제

// Build a schematic
let schematic = Axon::simple::<AppError>()
    .pipe(authenticate, authorize, handle_request)
    .build();

// Execute
let outcome = schematic.execute(request).await;

// Export to JSON (for VSCode visualization)
let json = schematic.to_json();
std::fs::write("schematic.json", json)?;

#다음 단계

  • 왜 Opinionated Core인가? — Ranvier가 이러한 개념을 강제하는 이유
  • 경계 맵 — 코어가 끝나고 엣지가 시작되는 곳