PROD주권 유럽 BaaS 플랫폼대시보드 열기 →

공학 · 10분 읽음

다중 서비스 Rust 백엔드를 위한 화물 작업공간

Affane Daylami · Fondateur · 2026년 8월 12일

블로그로 돌아가기

Rust의 다중 서비스 백엔드는 동일한 질문을 빠르게 제기합니다. 서비스당 하나의 저장소입니까, 아니면 단일 Cargo 작업 공간입니까? Aurabase는 두 번째 옵션을 선택했습니다. 11개의 서비스, CLI 및 5개의 공유 라이브러리가 단일 Cargo.toml 루트에 있으며 모두를 위한 단일 Cargo.lock이 있습니다. 다음은 이 작업 공간을 한 줄씩 읽어 구성하는 방법입니다.

이 영어 텍스트는 프랑스어 원본에서 자동으로 생성되었으며 아직 검토되지 않았습니다.
이 페이지는 자동으로 번역되었습니다. 영어 버전은 권위가 있습니다.

이것은 행사를 위해 고안된 교육적 예가 아닙니다. 아래의 각 코드 조각은 Aurabase 루트 Cargo.toml 및 현재 저장소에 존재하는 크레이트 매니페스트에서 가져온 것입니다. 여기에는 이 기사를 위해 다시 읽는 동안 발견한 두 가지 불일치가 포함되어 있으며 게시하기 전에 조용히 수정하기보다는 있는 그대로 문서화하고 있습니다.

필수사항
  • Aurabase 루트 Cargo 작업 공간은 resolver = "2" 및 단일 Cargo.lock아래에 11개의 서비스, aura-cliCLI, 5개의 공유 라이브러리 및 aurabase-rs SDK 등 18개의 명시적 멤버를 선언합니다.
  • [workspace.dependencies]는 공유 버전을 중앙 집중화합니다. 각 크레이트는 자체 번호를 설정하는 대신 { workspace = true }를 사용하여 상속합니다. — 크레이트가 자동으로 재정의되는 경우를 제외합니다.
  • services/ 아래의 폴더는 자동으로 작업 공간의 멤버가 아닙니다. members 목록은 비Rust 코드를 제외할 수 있도록 glob이 아닌 명시적입니다.
  • [profile.release]는 전체 작업 공간에 한 번만 적용됩니다. panic = "unwind"와 같은 선택은 한 번에 11개 서비스 모두에 적용됩니다.
#
작업 공간이 필요한 이유

서비스당 하나의 창고인가요, 아니면 하나의 Cargo.lock인가요?

작업 공간 Cargo는 단일 Cargo.lock 및 단일 대상 디렉터리 아래에 여러 상자를 그룹화합니다. 이는 Rust의 패키지 관리자가 이 경우에 제공하는 바로 그 기능입니다. 각각 자체 저장소에 있는 11개의 개별 Rust 바이너리는 언뜻 보면 더 독립적인 것처럼 보입니다. 실제로 이는 11개의 고유한 Cargo.lock, 시간이 지남에 따라 달라질 수 있는 11개의 버전 확인 및 두 서비스가axum 또는 sqlx의 동일한 버전을 컴파일한다는 보장이 없음을 의미합니다.

A Cargo workspace solves this at the package manager level, not the team discipline level. All members share a single Cargo.lock at the root: a common dependency is resolved once, to an identical version, for the entire graph. This is also what makes a cross-service refactor (changing a signature in aura-core, for example) visible to a single cargo build --workspace, rather than discovered service by service in production. This is one of the choices that distinguishes our 100% unified Rust core from a heterogeneous stack assembled service by service.

#
해부학

Cargo.toml 루트: 해석기 및 멤버

모든 것은 [workspace] 선언과 해당 목록 members으로 시작됩니다. Aurabase에서 이 목록은 services/*와 같은 일반적인 패턴으로 생성되지 않고 서비스, 도구, 라이브러리 등 역할별로 그룹화되어 손으로 작성됩니다.

Cargo.tomltoml
[workspace]
resolver = "2"

members = [
    # 서비스
    "services/aura-gateway",
    "services/aura-auth",
    "services/aura-db",
    # … 기타 8개 서비스(aura-realtime, aura-storage, aura-ai, …)
    # 도구
    "aura-cli",
    # 라이브러리
    "libs/aura-core",
    # ...4개의 다른 라이브러리
    "aurabase-rs",
]

resolver = "2"은 외관상의 세부 사항이 아닙니다. Cargo의 리졸버 v2는 build-dependencies 및 대상별 종속성(예:target.'cfg(windows)')의 기능을 나머지 그래프에서 분리합니다. — 더 이상 최종 바이너리로 누출되지 않습니다. 또한 이를 공유하는 작업 공간의 모든 구성원 간에 동일한 종속성 버전을 통합합니다. 단일 axum, 단 한 번만, 11개의 독립적인 해결 방법은 아닙니다.

#
레거시

작업 공간.패키지: 하나의 버전, 하나의 에디션, 원칙적으로 공유

[workspace.package]는 각 크레이트가 복사하는 대신 version.workspace = true로 상속할 수 있는 공통 필드(버전, 에디션, 작성자, 라이센스)를 한 번 선언합니다.

Cargo.toml(루트)toml
[workspace.package]
version = "0.1.1"
edition = "2021"

# services/aura-gateway/Cargo.toml
[package]
name = "aura-gateway"
version.workspace = true

Aurabase의 11개 서비스는 모두 이 패턴을 따릅니다. 두 개의 상자가 벗어났으며 이는 이 기사를 준비하는 동안 발견된 두 가지 불일치 중 첫 번째입니다. aura-cli는 하드 카피에서 version = "0.2.0"을 선언하고 lib libs/aura-migrations는 version = "0.1.0"을 선언합니다. 둘 다 작업 공간의 0.1.1와 다릅니다.

의미

version.workspace = true는 선택사항이며, 필드별로, 상자별로 상자별로 표시됩니다. 자발적으로(예를 들어 별도로 게시된 도구) 또는 망각으로 인해 상자가 자체 번호를 유지하는 것을 막을 수 있는 방법은 없습니다. 작업공간 감사에서는 상속을 가정하지 않고 이 필드 상자를 상자별로 확인해야 합니다.

#
중복 제거

작업 공간.종속성: 크레이트가 이를 우회하지 않는 한 진실의 소스

[workspace.dependencies]는 여러 크레이트가 공유하는 종속성을 중앙 집중화합니다. 각 서비스는 자체 버전 제약 조건을 설정하는 대신 { workspace = true }을 사용하여 이를 참조합니다.

Cargo.toml(루트)toml
[workspace.dependencies]
axum = { version = "0.8", features = ["ws", "multipart", "macros"] }
sqlx = { version = "0.8", default-features = false, features = […] }
tower_governor = { version = "0.8", features = ["axum"] }
governor = "0.10"

# services/aura-gateway/Cargo.toml — 작업 공간 관리자를 상속하지 않습니다.
governor = "0.8"  # 로컬 버전, 다른

This is the second real discrepancy: the workspace centralizes governor in version 0.10, but aura-gateway redeclares its own line governor = "0.8" instead of inheriting — the gateway applies its rate limiting with the bare governor crate, when aura-auth, aura-ai, aura-db and aura-functions pass through tower_governor in middleware. A workspace does not prevent divergence: it only makes it visible, if we take the trouble to compare.

그러나 모든 종속성을 중앙 집중화할 가치가 있는 것은 아닙니다. wasmtime는 [workspace.dependencies]어디에도 나타나지 않습니다. 단 하나의 크레이트인 aura-functions만이 이를 WASM 런타임에 사용하므로 로컬로 선언된 상태로 유지됩니다. 우리가 적용하는 규칙은 둘 이상의 크레이트가 공유하는 순간부터 작업 공간 수준에 대한 종속성을 높이는 것입니다. 이전이 아닙니다.

#
내부 그래프

libs/에서 services/: 무엇에 따라 다름

5개의 공유 라이브러리(aura-core, aura-crypto, aura-db-adapters, aura-migrations, aura-telemetry)는 [workspace.dependencies] — aura-core = { path = "libs/aura-core" }에서 경로 종속성으로 선언된 다음 각 서비스가 실제로 필요한 라이브러리를 선택합니다.

결과 그래프는 한 파일에서 다른 파일로 계속 읽을 수 있습니다. aura-gateway는 5개의 내부 라이브러리 중 3개( aura-core, aura-crypto 및 aura-telemetry)에만 의존합니다. 라우팅하기에 충분하므로 aura-db-adapters 또는 aura-migrations를 건드리지 않고 PostgREST 사이드카용 JWT에 서명하고 추적을 내보냅니다. aura-provisioner는 aura-migrations을 추가합니다. 프로젝트 생성으로 인한 스키마 마이그레이션을 재생합니다.

가장 좁은 사례는 CI/CD 마이그레이션을 수행하는 전용 바이너리인 aura-migrator입니다. Aurabase 내부 라이브러리 중에서 프로덕션 [dependencies]는 테스트를 위해aura-migrations만 나열합니다. — aura-core는 [dev-dependencies]에만 나타납니다. 따라서 프로덕션에 전달되는 바이너리에는aura-core 코드가 포함되지 않습니다. cargo test동안에만 존재합니다.

#
콘크리트 케이스

하나의 상자, 여러 바이너리: aura-realtime 분할

작업 영역에서는 배포 가능한 새 프로세스를 원할 때마다 새 멤버를 만들 필요가 없습니다. aura-realtime은 작업 공간의 단일 멤버로 남아 있지만 해당 Cargo.toml는 동일한 공유 [lib]주위에 세 개의 서로 다른 [[bin]] 테이블을 선언합니다.

Cargo.tomltoml
[lib]
name = "aura_realtime"

[[bin]]
name = "aura-realtime"

[[bin]]
name = "aura-realtime-cdc-worker"
path = "src/bin/cdc_worker.rs"

[[bin]]
name = "aura-realtime-ws-front"
path = "src/bin/ws_front.rs"

선언문 자체는 둘 사이의 경계를 기록합니다. cdc-worker는 WebSocket 서버를 열지 않고도 폴링을 통해 Postgres 변경 사항을 캡처하고 게시/구독 코어(Client::publish_with_headers)의 NATS에 게시합니다. 동일한 서비스에서 JetStream은 CDC 팬아웃이 아닌 인스턴스 간 존재 KV를 독점적으로 제공합니다. ws-front는 CDC를 건드리지 않고 NATS만 사용하고 WebSocket/SSE 연결을 유지합니다. 자세한 내용은 실시간 엔진 문서에 있습니다. 두 바이너리 모두 [lib]를 통해 동일한 RLS 필터링 코드를 공유하지만 Kubernetes에서 독립적으로 배포 및 확장됩니다. 이는 새로운 작업 공간 구성원이 아닌 크레이트에서 여러 바이너리(동일한 내부 논리, 다른 배포 토폴로지)를 선택하는 올바른 신호입니다.

#
피해야 할 함정

services/ 아래의 파일은 반드시 Cargo 회원일 필요는 없습니다.

aura-edge-runtime 폴더는 Aurabase 저장소에 존재합니다. 그러나 Cargo.toml은 포함되지 않고 TypeScript(index.ts, envelope.ts)만 포함되며 루트 작업 공간의 members 목록 어디에도 나타나지 않습니다.

아스투스

이것이 바로 Aurabase의 members 목록이 services/*와 같은 일반 패턴으로 대체되지 않고 직접 작성된 이유입니다. glob은 작업 공간에 이 비Rust 폴더를 포함하려고 시도했으며 결과적으로 해결이 실패했습니다. 명시적 목록을 사용하면 동일한 언어를 사용하지 않는 폴더가 동일한 상위 services/아래에 공존할 수 있습니다.

이 강의에서는 일반화합니다. services/의 하위 폴더를 나열하여 Rust 백엔드의 서비스를 계산하면 잘못된 숫자가 제공됩니다. 루트 Cargo.toml만이 작업공간에서 실제로 컴파일되는 항목에 대해 권한을 갖습니다.

#
편집

[profile.release]: 전체 작업공간에 대한 단일 설정

작업 공간 루트에 선언된 [profile.release]는 릴리스 모드에서 컴파일된 모든 멤버에 적용됩니다. 즉, 조정할 수 있는 위치는 11개가 아닌 한 곳뿐입니다. Cargo는 컴파일 프로필 참조에 사용 가능한 모든 키를 문서화합니다. Aurabase는 5개만 활성화합니다.

Cargo.toml(루트)toml
[profile.release]
lto = "thin"          # LTO 교차 상자, 성능/빌드 시간 균형
codegen-units = 1     # 전반적으로 최고의 인라인
panic = "unwind"   # tokio/axum에 의해 고립된 패닉 → 500, 글로벌 크래시가 아님
strip = "symbols"
opt-level = 3

"abort" 대신 panic = "unwind"을 선택하는 것은 파일 주석에 직접 문서화되어 있습니다. axum 처리기의 패닉이 tokio에 의해 차단되고 작업이 500을 반환하며 프로세스는 계속해서 다른 동시 요청을 처리합니다.abort의 성능 향상은 쿼리 간 격리의 손실만큼 가치가 없습니다.

#
실제로

툴체인과 중요한 명령을 동결합니다.

통합 작업공간은 종속성 버전을 설정하지만 컴파일러 자체의 버전은 설정하지 않습니다. rust-toolchain.toml는 루트에서 저장소의 cargo 또는 rustc에 대한 직접 호출을 위해 도구 체인(현재channel = "1.93")을 고정합니다.

이 파일은 자동 드리프트가 발생했기 때문에 정확하게 존재합니다. CI 작업은 rust-toolchain.toml을 읽지 않고 오늘의 stable 채널을 설치했지만 릴리스 Docker 이미지는 고정 버전으로 컴파일되었습니다. 커밋은 오늘날의 안정적인 Rust를 사용하여 모든 테스트를 통과한 다음 이미지 빌드에서 중단될 수 있습니다. 이는 병합 이전이 아닌 병합 후에 발견됩니다. rustup는 모든 직접적인 명령에 대해 이 파일을 존중합니다. 이는 CI 작업 흐름에 영향을 주지 않고 필요한 유일한 수정 사항이었습니다.

terminalbash
# 전체 작업 공간을 컴파일
cargo build --workspace

# 단일 크레이트 테스트(전체 작업공간이 아님)
cargo test -p aura-auth

# 전체 작업공간에 대한 엄격한 린트, 경고 = 오류
cargo clippy --workspace --all-targets -- -D warnings

# 서식 지정
cargo fmt --all

매니페스트를 실행하기 전에 읽는 사람들을 위한 마지막 세부 사항: aura-cli 크레이트는 [[bin]] 테이블의 이름을 aura가 아닌 aurabase로 지정하는 바이너리를 컴파일합니다. 게시된 npm 래퍼인 @aurabase/cli는 두 명령(aura 및 aurabase 모두 동일한 스크립트를 가리킴)을 노출합니다. [[bin]] Cargo의 이름, 상자의 이름, npm 래퍼에 의해 노출되는 이름은 세 가지 별개의 것입니다. 나머지 두 개에서는 아무것도 추측할 수 없습니다.

#
요약

체크리스트: 어디에 무엇을 선언할 것인가

화물 작업 공간에 상자를 추가할 때마다 다섯 가지 결정이 내려집니다. 위에서 다룬 Aurabase 예제를 기반으로 각각이 선언되는 위치는 다음과 같습니다.

회원 목록[워크스페이스] 멤버루트 — 명시적 목록, 절대 글로벌이 아님
공유 버전/에디션[작업공간.패키지]루트 — version.workspace = true, 상자별, 선택 사항
2개 이상의 크레이트가 공유하는 종속성[작업공간.종속성]루트 - 각 크레이트의 { 작업 공간 = true }
단일 소비자에 대한 의존성상자의 [종속성]작업공간을 거치지 않고 로컬로
프로필 작성[프로필.릴리스]루트만 — 모든 구성원에게 적용됩니다.

귀하 또는 귀하가 인계받는 프로젝트의 기존 Cargo 작업 공간을 감사하려면 위에 문서화된 종류의 불일치를 찾는 데 4번의 검사만으로 충분합니다.

  1. [workspace.package].version를 각 상자의 version와 비교하세요. 다른 값이 반드시 버그는 아니지만 문서화할 가치가 있습니다.
  2. [workspace.dependencies]를 각 크레이트에 의해 로컬로 선언된 종속성과 비교하십시오. 서로 다른 버전의 양쪽에 존재하는 이름을 찾아보세요.
  3. 루트 Cargo.toml의 members 목록을 저장소의 실제 하위 폴더와 비교합니다. members에서 누락된 폴더가 반드시 실수인 것은 아닙니다.
  4. 크레이트 이름이나 가능한 npm 래퍼에 의해 노출된 이름과 일치한다고 가정하기보다는 각 컴파일된 바이너리([[bin]] name)의 실제 이름을 확인하세요.

배포할 준비가 되셨나요?

5분 만에 백엔드를 완성하세요.

신용카드 불필요 · 500MB 무료 · 50,000 MAU