Ranvier 시작하기
버전: 0.17 최근 업데이트: 2026-03-04
필수 요건
- Rust 툴체인 (1.75+):
rustup,cargo - Git
1. 첫 번째 Axon 만들기 (5분)
Ranvier는 워크플로우를 타입 안전한 파이프라인으로 모델링합니다. 각 단계는 Transition이고, 이들을 체이닝하면 Axon이 됩니다.
프로젝트 생성
cargo new my-ranvier-app
cd my-ranvier-app
cargo add ranvier-core ranvier-runtime tokio --features tokio/full
cargo add anyhow async-trait serde --features serde/derive
cargo add serde_json첫 번째 트랜지션 작성
use async_trait::async_trait;
use ranvier_core::prelude::*;
use ranvier_runtime::Axon;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Greeting {
name: String,
message: String,
}
#[derive(Clone)]
struct BuildGreeting;
#[async_trait]
impl Transition<String, Greeting> for BuildGreeting {
type Error = String;
type Resources = ();
async fn run(
&self,
name: String,
_resources: &Self::Resources,
_bus: &mut Bus,
) -> Outcome<Greeting, Self::Error> {
Outcome::Next(Greeting {
name: name.clone(),
message: format!("Hello, {}!", name),
})
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let axon = Axon::<String, String, String>::new("greeting")
.then(BuildGreeting);
let mut bus = Bus::new();
let result = axon.execute("World".to_string(), &(), &mut bus).await;
match result {
Outcome::Next(greeting) => println!("{}", greeting.message),
Outcome::Fault(e) => eprintln!("Error: {}", e),
_ => {}
}
Ok(())
}핵심 개념
| 개념 | 설명 |
|---|---|
Transition<From, To> |
단일 처리 단계: From을 받아 To를 생성 |
Axon |
트랜지션을 체이닝한 파이프라인 |
Outcome<T, E> |
결과 타입: Next(T), Fault(E), Branch, Jump, Emit |
Bus |
파이프라인을 관통하는 타입 인덱스 기반 역량 저장소 |
Resources |
트랜지션에 주입되는 공유 의존성 |
실행
cargo run
# 출력: Hello, World!예제 실행:
cargo run -p hello-world(ranvier 워크스페이스 기준)
2. 트랜지션 체이닝 (5분)
트랜지션은 .then()으로 조합합니다:
#[derive(Clone)]
struct ValidateInput;
#[async_trait]
impl Transition<String, String> for ValidateInput {
type Error = String;
type Resources = ();
async fn run(
&self,
input: String,
_resources: &(),
_bus: &mut Bus,
) -> Outcome<String, Self::Error> {
if input.is_empty() {
return Outcome::Fault("입력값이 비어있습니다".to_string());
}
Outcome::Next(input)
}
}
let axon = Axon::<String, String, String>::new("pipeline")
.then(ValidateInput)
.then(BuildGreeting);각 .then()은 단계를 추가합니다. 한 단계의 출력 타입이 다음 단계의 입력 타입과 일치해야 하며, 컴파일러가 빌드 타임에 이를 강제합니다.
예제 실행:
cargo run -p typed-state-tree
3. 트랜지션 테스트 (10분)
트랜지션은 일반 비동기 함수이므로 직접 테스트할 수 있습니다:
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_validate_rejects_empty() {
let validator = ValidateInput;
let mut bus = Bus::new();
let result = validator.run("".to_string(), &(), &mut bus).await;
assert!(result.is_fault());
}
#[tokio::test]
async fn test_full_pipeline() {
let axon = Axon::<String, String, String>::new("test")
.then(ValidateInput)
.then(BuildGreeting);
let mut bus = Bus::new();
let result = axon.execute("Alice".to_string(), &(), &mut bus).await;
match result {
Outcome::Next(g) => assert_eq!(g.message, "Hello, Alice!"),
other => panic!("Expected Next, got: {:?}", other),
}
}
}예제 실행:
cargo run -p testing-patterns(7개 단위 + 통합 테스트 포함)
4. 커스텀 에러 타입 (10분)
구조화된 에러를 위해 thiserror를 사용합니다:
cargo add thiserroruse thiserror::Error;
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
enum AppError {
#[error("찾을 수 없음: {0}")]
NotFound(String),
#[error("검증 실패: {0}")]
Validation(String),
#[error("권한 없음: {0}")]
Unauthorized(String),
}
#[async_trait]
impl Transition<String, User> for FetchUser {
type Error = AppError;
type Resources = ();
async fn run(
&self,
user_id: String,
_resources: &(),
_bus: &mut Bus,
) -> Outcome<User, Self::Error> {
if user_id == "unknown" {
return Outcome::Fault(AppError::NotFound(user_id));
}
Outcome::Next(User { id: user_id, name: "Alice".into() })
}
}Ranvier는 core prelude에 RanvierError도 제공합니다. Message, NotFound, Validation, Internal 변형을 가진 serde 호환 에러 타입입니다.
예제 실행:
cargo run -p custom-error-types
5. Bus 사용하기 (5분)
Bus는 파이프라인을 통해 역량(capability)을 전달하는 타입 인덱스 저장소입니다:
use ranvier_core::prelude::*;
// 역량 정의
#[derive(Clone, Debug)]
struct RequestId(String);
// 실행 전 삽입
let mut bus = Bus::new();
bus.insert(RequestId("req-123".to_string()));
// 트랜지션 내부에서 읽기
async fn run(&self, input: Input, _res: &(), bus: &mut Bus) -> Outcome<Output, Error> {
let req_id = bus.read::<RequestId>();
match req_id {
Some(id) => println!("요청 처리 중: {}", id.0),
None => println!("요청 ID 없음"),
}
Outcome::Next(output)
}Bus는 insert(), read(), read_mut(), has(), remove()를 지원합니다. 타입은 Any + Send + Sync + 'static이어야 합니다.
예제 실행:
cargo run -p bus-capability-demo
6. 공유 리소스 (5분)
모든 트랜지션에 공유되는 의존성(DB 풀, 설정 등)에는 Resources를 사용합니다:
use ranvier_core::transition::ResourceRequirement;
#[derive(Clone)]
struct AppConfig {
api_key: String,
max_retries: u32,
}
impl ResourceRequirement for AppConfig {}
#[async_trait]
impl Transition<Request, Response> for CallExternalApi {
type Error = String;
type Resources = AppConfig;
async fn run(
&self,
req: Request,
config: &Self::Resources,
_bus: &mut Bus,
) -> Outcome<Response, Self::Error> {
println!("API 키 사용: {}", config.api_key);
Outcome::Next(response)
}
}
// 실행 시 리소스 전달
let config = AppConfig { api_key: "key".into(), max_retries: 3 };
let result = axon.execute(input, &config, &mut bus).await;리소스는 ResourceRequirement (마커 트레이트)를 구현해야 합니다. 유닛 타입 ()는 기본 구현을 제공합니다.
7. 장애 복원력: 재시도 & DLQ (10분)
Ranvier는 데드 레터 큐(DLQ) 지원 기본 재시도를 제공합니다:
use ranvier_core::prelude::*;
use ranvier_runtime::Axon;
let axon = Axon::<Input, Input, String>::new("resilient")
.then(UnreliableStep)
.with_dlq_policy(DlqPolicy::RetryThenDlq {
max_attempts: 3,
backoff_ms: 100,
})
.with_dlq_sink(MyDlqSink::new());max_attempts회 지수 백오프 재시도 후, 실패한 이벤트는 DLQ 싱크로 전송됩니다.
예제 실행:
cargo run -p retry-dlq-demo
8. 영속성: 체크포인트 & 재개 (10분)
장기 실행 워크플로우에서는 영속성 레이어로 상태를 체크포인트하고 장애 시 재개합니다:
use ranvier_runtime::{
InMemoryPersistenceStore, PersistenceHandle,
PersistenceTraceId, PersistenceAutoComplete,
};
use std::sync::Arc;
let store = Arc::new(InMemoryPersistenceStore::new());
let handle = PersistenceHandle::from_arc(store.clone() as Arc<dyn PersistenceStore>);
// 첫 실행 — auto_complete=false로 장애 시 트레이스를 열어둠
let mut bus = ranvier_core::ranvier_bus!(
handle.clone(),
PersistenceTraceId::new("order-123"),
PersistenceAutoComplete(false),
);
let result = axon.execute(input, &(), &mut bus).await;예제 실행:
cargo run -p state-persistence-demo
9. 다음 단계
난이도별
| 수준 | 예제 | 시간 |
|---|---|---|
| 초급 | hello-world, typed-state-tree, bus-capability-demo |
각 5분 |
| 중급 | routing-demo, flat-api-demo, testing-patterns, custom-error-types |
각 10-15분 |
| 고급 | order-processing-demo, retry-dlq-demo, state-persistence-demo, multitenancy-demo |
각 15-20분 |
주제별
| 주제 | 예제 |
|---|---|
| HTTP & 라우팅 | routing-demo, routing-params-demo, flat-api-demo, session-demo |
| 인증 & 보안 | auth-jwt-role-demo, guard-demo |
| 데이터 & 영속성 | db-example, persistence-production-demo, ecosystem-redis-demo |
| 관측성 | observe-http-demo, otel-demo, otel-concept-demo |
| 장애 복원력 | retry-dlq-demo, state-persistence-demo |
| 엔터프라이즈 | synapse-demo, job-scheduler-demo, multitenancy-demo |
학습 경로
- 빠른 시작: hello-world → typed-state-tree → testing-patterns → custom-error-types → routing-demo
- HTTP 서비스: routing-params → flat-api → session → multipart-upload → websocket → sse-streaming → openapi → auth-jwt-role
- 고급 패턴: order-processing → retry-dlq → state-persistence → multitenancy
예제 실행
# ranvier 워크스페이스 루트에서
cargo run -p <예제-이름>