#Ranvier 설계 원칙

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


#소개

이 문서는 Ranvier의 설계를 형성한 아키텍처 결정을 기록합니다. PHILOSOPHY.md가 Ranvier가 존재하고 어떤 원칙이 사용을 안내하는지 설명한다면, 이 문서는 특정 기술적 선택을 어떻게 결정했는지 설명합니다.

각 결정은 맥락, 대안, 결과를 포함한 Architecture Decision Record (ADR) 형식으로 기록됩니다.


#목차

ID 제목 상태 날짜
DP-1 패러다임 테스트: 크레이트 통합 승인됨 2025-10
DP-2 Tower 분리: Hyper 1.0 네이티브 승인됨 2025-09
DP-3 Opinionated Core: 비협상 패러다임 승인됨 2025-08

#DP-1: Paradigm Test: Crate Consolidation

상태: 승인됨 날짜: 2025-10 버전: v0.21.0

#맥락

Ranvier v0.20에는 23개의 크레이트가 있었으며, 각각 에코시스템 라이브러리의 얇은 추상화를 나타냈습니다:

  • ranvier-graphql (async-graphql 래핑)
  • ranvier-grpc (tonic 래핑)
  • ranvier-db (sqlx 래핑)
  • ranvier-redis, ranvier-session, ranvier-cluster, ranvier-job
  • 4개의 미들웨어 크레이트: ranvier-auth, ranvier-guard, ranvier-observe, ranvier-multitenancy

문제점:

  1. 유지보수 부담: 23개의 크레이트를 버전 관리, 배포, 문서화, 유지보수해야 함
  2. 정체성 희석: 사용자가 어떤 크레이트가 "핵심 Ranvier"이고 어떤 것이 편의 래퍼인지 혼란스러워 함
  3. 거짓 추상화: 대부분의 래퍼 크레이트가 1-2개 파일의 얇은 래퍼로, 실질적인 가치를 제공하지 못함
  4. 에코시스템 중복: sqlx/tonic/redis가 이미 잘 제공하는 것을 재구현하고 있었음

질문: 어떤 크레이트가 Ranvier 패러다임에 필수적(Transition/Outcome/Bus/Schematic)이고, 어떤 것이 단순한 얇은 래퍼인가?

#결정

패러다임 테스트를 적용했습니다:

"이 크레이트가 존재하기 위해 근본적으로 Transition, Outcome, Bus, 또는 Schematic이 필요한가?"

테스트 결과:

  • ranvier-core — Transition/Outcome/Bus/Schematic 정의 → 유지

  • ranvier-runtime — Axon (Transition 파이프라인) 실행 → 유지

  • ranvier-http — Transition을 위한 Ingress/Egress 경계 → 유지

  • ranvier-macros#[transition] 매크로 → 유지

  • ranvier-std — 표준 Transition (Guard 노드) → 유지

  • ranvier-inspector, ranvier-audit, ranvier-compliance, ranvier-openapi — 패러다임을 사용하는 확장 → 유지

  • kit/ (facade 크레이트 ranvier) — 편의 재내보내기 → 유지

  • ranvier-graphql — async-graphql을 재내보내기만 함 → 삭제

  • ranvier-grpc — tonic을 재내보내기만 함 → 삭제

  • ranvier-db, ranvier-redis, ranvier-session, ranvier-cluster, ranvier-job, ranvier-status삭제

  • ❌ 4개의 미들웨어 크레이트 (auth/guard/observe/multitenancy) → core/std로 통합

결과: 23개 크레이트 → 10개 크레이트

#결과

긍정적:

  • 명확성: 사용자가 핵심 Ranvier(10개 크레이트)와 에코시스템 도구를 즉시 구분 가능
  • 유지보수: 배포, 버전 관리, 문서화할 크레이트가 13개 감소
  • 정체성: Ranvier는 이제 "모든 것의 래퍼"가 아닌 명확한 "패러다임"
  • 에코시스템 수용: 사용자가 sqlx, tonic, redis를 직접 사용 — 중간에 Ranvier 레이어 없음
  • 유연성: 강제된 추상화 없음 — DB/캐시/큐 라이브러리를 자유롭게 선택

부정적:

  • ⚠️ 마이그레이션 부담: v0.20 사용자는 import를 업데이트해야 함 (단, 명확한 마이그레이션 가이드 제공)
  • ⚠️ 인식된 기능 손실: 일부 사용자가 기능을 "제거"했다고 생각할 수 있음 (실제로는 얇은 래퍼만 제거)

완화 조치:

  • 마이그레이션 지침이 포함된 종합 CHANGELOG
  • Transition에서 직접 sqlx/tonic/redis 사용을 보여주는 예제
  • "Flexible Edges" 원칙을 설명하는 PHILOSOPHY.md

#검토된 대안

대안 A: 23개 크레이트 모두 유지

  • ❌ 거부: 유지보수 부담이 너무 높고, 정체성 불명확

대안 B: 래퍼를 별도 조직으로 이동 (ranvier-contrib)

  • ⚠️ 검토: 여전히 유지보수 필요, 사용자가 공식 지원을 기대할 수 있음
  • ❌ 거부: 사용자를 에코시스템 직접 사용으로 안내하는 것이 나음

대안 C: 기존 크레이트를 폐기하되 계속 배포

  • ⚠️ 검토: 마이그레이션이 더 쉬움
  • ❌ 거부: 신규 사용자를 혼란스럽게 하고, 지속적 지원을 암시함

#참고 자료

  • 통합 PR: ranvier#210
  • 마이그레이션 가이드: CHANGELOG.md v0.21.0 섹션
  • 예제 업데이트: examples/db-sqlx-demo, examples/graphql-async-graphql-demo, examples/grpc-tonic-demo

#DP-2: Tower Separation: Hyper 1.0 Native

상태: 승인됨 날짜: 2025-09 버전: v0.21.0

#맥락

Ranvier v0.20은 HTTP 처리에 Tower (tower, tower-http)를 사용했습니다:

  • ranvier-http가 Tower의 Service 트레이트를 래핑
  • Tower의 Layer 트레이트를 통한 미들웨어 합성
  • tower v0.4 + tower-http v0.4 의존성

문제점:

  1. Tower는 미들웨어이지 Ranvier의 패러다임이 아님: Tower의 Service<Request> 트레이트는 Ranvier의 Transition 트레이트와 근본적으로 다릅니다. 둘을 혼합하면 사용자가 혼란스러워합니다.
  2. 숨겨진 복잡성: Tower의 Layer 순서와 Service::poll_ready 시맨틱은 복잡합니다. Ranvier의 가치 제안은 명시적 실행이지만, Tower는 제어 흐름을 숨깁니다.
  3. Hyper 1.0 호환성: Hyper 1.0이 tower::Service 지원을 제거 — Tower는 이제 선택 사항이지 필수가 아닙니다.
  4. 의존성 비대화: 기본 HTTP에 Tower를 끌어오는 것은 Hyper 1.0이 네이티브 async fn 지원을 제공할 때 과도합니다.

질문: Ranvier가 Tower를 계속 필수로 할 것인가, 아니면 Hyper 1.0을 네이티브로 수용할 것인가?

#결정

Tower 의존성 제거. Hyper 1.0 직접 사용.

구현:

  • ranvier-http가 이제 hyper::service::service_fn을 직접 사용
  • Ingress 트레이트가 HTTP 요청을 Axon 입력으로 매핑
  • Egress 트레이트가 Axon 출력을 HTTP 응답으로 매핑
  • tower::Service 구현 없음 — Ranvier는 Tower 미들웨어가 아님

사용자 마이그레이션 경로:

// v0.20 (Tower-based)
let app = ServiceBuilder::new()
    .layer(CorsLayer::permissive())
    .service(ranvier_handler);

// v0.21+ (Hyper native, optional Tower)
// Option A: Pure Ranvier
let app = Ranvier::http()
    .route("/", axon)
    .run(resources).await?;

// Option B: Hybrid (user wraps Ranvier with Tower)
let ranvier_service = /* Ranvier handler wrapped as Tower Service */;
let app = ServiceBuilder::new()
    .layer(CorsLayer::permissive())
    .service(ranvier_service);

핵심 통찰: Tower는 이제 선택적 통합이지 필수 의존성이 아닙니다. Tower를 원하는 사용자는 Ranvier 핸들러를 래핑할 수 있지만, Ranvier 자체는 Tower에 의존하지 않습니다.

#결과

긍정적:

  • 더 단순한 멘탈 모델: Ranvier는 Service 기반이 아닌 Transition 기반
  • 가벼운 의존성: 사용자가 선택하지 않는 한 tower/tower-http 불필요
  • Hyper 1.0 네이티브: Hyper의 async fn 개선 사항 활용
  • 명확한 경계: HTTP는 Ingress/Egress에 있으며 핵심 패러다임이 아님
  • 유연한 통합: 사용자가 필요 시 Tower/Axum/actix로 Ranvier를 래핑 가능

부정적:

  • ⚠️ 호환성 깨짐: v0.20 사용자는 코드를 업데이트해야 함 (단, 마이그레이션은 간단)
  • ⚠️ Tower 에코시스템 손실: tower-http 레이어를 직접 사용 불가 (단, 경계에서 통합 가능)

마이그레이션 지원:

  • Tower 통합을 보여주는 예제 (examples/auth-tower-integration)
  • 에코시스템 통합을 설명하는 PHILOSOPHY.md 섹션 3.1
  • 비교 가이드 (docs/guides/auth-comparison.md)

#검토된 대안

대안 A: Tower를 핵심 의존성으로 유지

  • ❌ 거부: "Opinionated Core, Flexible Edges"에 모순 — Tower는 엣지 관심사
  • ❌ 거부: 미들웨어가 필요하지 않은 사용자도 Tower를 학습해야 함

대안 B: Tower를 선택적 기능으로 만들기

  • ⚠️ 검토: ranvier-http/tower 기능
  • ❌ 거부: 여전히 Tower가 "공인된" 통합임을 암시, 실제로는 어떤 프레임워크든 작동해야 함

대안 C: Tower 및 비-Tower API 모두 제공

  • ❌ 거부: 유지보수가 두 배, "올바른 방법"에 대해 사용자를 혼란스럽게 함

#참고 자료


#DP-3: Opinionated Core: Non-Negotiable Paradigm

상태: 승인됨 날짜: 2025-08 버전: v0.18.0+

#맥락

초기 Ranvier 버전(v0.1-v0.17)은 다양한 수준의 유연성을 실험했습니다:

  • 선택적 #[transition] 매크로 (사용자가 Transition 트레이트를 구현하거나 자유 함수를 사용할 수 있음)
  • 선택적 Bus (일부 파이프라인은 Bus를 사용하고, 다른 일부는 함수 인자를 사용)
  • 선택적 Schematic 내보내기 (사용자가 JSON 생성을 선택 해제할 수 있음)

문제점:

  1. 일관성 없는 코드베이스: 일부 프로젝트는 Transition을 사용하고, 일부는 자유 함수를 사용 → 파편화된 에코시스템
  2. 도구 지원 불가: VSCode 확장이 Schematic의 존재를 가정할 수 없음 → Circuit 뷰 작동 불가
  3. 정체성 상실: "Ranvier가 Axum/Actix와 다른 점은?" → 불명확한 답변
  4. 문서화 부담: 모든 가이드가 "X 또는 Y 또는 Z를 할 수 있습니다"를 설명해야 함

질문: Ranvier가 유연해야(여러 패턴 지원) 하는가, 아니면 고집스러워야(하나의 패러다임 강제) 하는가?

#결정

Ranvier의 핵심 패러다임(Transition/Outcome/Bus/Schematic)은 비협상 사항입니다.

강제 사항:

  1. Transition 필수: 모든 비즈니스 로직은 #[transition] 매크로 또는 Transition 트레이트를 사용해야 함
  2. Outcome 필수: Transition은 Result<T, E>가 아닌 Outcome<T, E>를 반환해야 함
  3. Bus 필수: 모든 Axon 실행에 Bus가 있음 (비어 있더라도)
  4. Schematic 생성: 모든 Axon이 Schematic을 생성 (JSON 내보내기는 선택적이지만 구조는 존재)

왜 고집스러운가:

  • 정체성: Ranvier = "Schematic 우선, 시각화 가능한 프레임워크" (고유한 가치 제안)
  • 학습 곡선: 하나의 공인된 경로 → 더 빠른 온보딩 (PHILOSOPHY.md 섹션 2.2 참조)
  • 일관성: 모든 Ranvier 코드베이스가 유사하게 보임 → 더 쉬운 코드 리뷰, 온보딩
  • 도구 지원: VSCode 확장이 Schematic의 존재를 가정할 수 있음 → 안정적인 Circuit 뷰

유연하게 유지되는 부분:

  • HTTP 서버 선택 (Hyper, actix, Axum) — Ingress/Egress
  • 데이터베이스 선택 (sqlx, diesel, sea-orm) — Transition으로 래핑
  • 비동기 런타임 (tokio, async-std) — Ranvier는 런타임에 무관
  • 배포 (Docker, K8s, Lambda) — Ranvier는 단순한 Rust 코드

전체 "Opinionated Core, Flexible Edges" 설명은 PHILOSOPHY.md를 참조하세요.

#결과

긍정적:

  • 명확한 정체성: Ranvier는 "Transition/Schematic 프레임워크" ("47번째 Rust 웹 프레임워크"가 아님)
  • 일관된 에코시스템: 모든 예제, 튜토리얼, 프로젝트가 동일한 패턴을 따름
  • 안정적인 도구: VSCode 확장, CLI 도구, 웹 대시보드 모두 Schematic의 존재를 가정
  • 빠른 학습: 신규 사용자가 여러 경쟁 패턴이 아닌 하나의 경로를 따름
  • 더 나은 디버깅: Schematic 시각화가 100% 작동 ("선택적 기능"이 아님)

부정적:

  • ⚠️ 덜 유연함: Result 또는 자유 함수를 원하는 사용자는 Transition으로 래핑해야 함
  • ⚠️ 높은 진입 장벽: 사소한 앱에는 "Transition을 배우는" 것보다 "Axum을 사용"하는 것이 더 단순
  • ⚠️ Actix/Axum에서의 마이그레이션: Transition 패러다임을 채택해야 하며, 드롭인 대체가 아님

완화 조치:

  • 하이브리드 접근: 사용자가 기존 앱에 Ranvier를 삽입 가능 (PHILOSOPHY.md 섹션 6.2 경로 3 참조)
  • 명확한 문서: PHILOSOPHY.md 의사결정 트리가 "언제 Ranvier를 사용할지" 안내
  • 예제: "순수 Ranvier"와 "하이브리드" 패턴 모두 표시

#검토된 대안

대안 A: 모든 것을 선택적으로 (최대 유연성)

  • ❌ 거부: 정체성 상실, 도구 작동 불가, 코드베이스 불일관

대안 B: Transition과 자유 함수 모두 지원

  • ⚠️ 검토: 마이그레이션이 더 쉬움
  • ❌ 거부: 에코시스템 분열, 도구가 구조를 가정할 수 없음

대안 C: Opinionated core + 탈출구

  • ⚠️ 검토: 예를 들어 비-Transition 코드용 Axon::raw_fn()
  • ❌ 거부: 모든 탈출구는 일관성의 구멍

대안 D (현재 접근 방식): Opinionated core + flexible edges

  • 승인: 코어는 엄격 (Transition 필수), 엣지는 유연 (모든 HTTP/DB 라이브러리)

#참고 자료

  • 패러다임 강제: ranvier-core v0.18.0+
  • 철학 문서: PHILOSOPHY.md
  • 의사결정 프레임워크: PHILOSOPHY.md 섹션 5
  • 예제: 모든 61개 예제가 Transition 패턴을 사용

#이 문서에 기여하기

새로운 설계 결정을 추가할 때:

  1. ADR 형식 사용: 맥락, 결정, 결과, 대안
  2. 목차 추가: 상단의 표 업데이트
  3. 코드에서 참조: 특정 DP-X에 링크하는 rustdoc 주석 추가
  4. 상호 참조: 관련된 곳에서 PHILOSOPHY.md에 링크 (PHILOSOPHY = 왜, DP = 어떻게)

템플릿:

## DP-X: [Title]

**Status:** Proposed | Accepted | Deprecated
**Date:** YYYY-MM
**Version:** vX.Y.Z

### Context
[Why did this decision come up? What problem were we solving?]

### Decision
[What did we decide? Be specific.]

### Consequences
**Positive**: [Benefits]
**Negative**: [Costs/trade-offs]

### Alternatives Considered
[What other options did we evaluate? Why rejected?]

### References
[Links to PRs, issues, discussions, code]

#관련 문서

  • PHILOSOPHY.md — 설계 철학 (Ranvier가 왜 존재하는지, 언제 사용하는지)
  • README.md — 프로젝트 개요 및 빠른 시작
  • CHANGELOG.md — 버전 이력 및 마이그레이션 가이드
  • examples/README.md — 66개 참조 구현

이 문서는 Ranvier v0.33.0의 일부입니다. 최종 업데이트: 2026-03-15.