이것은 행사를 위해 고안된 교육적 예가 아닙니다. 아래의 각 코드 조각은 Aurabase 루트 Cargo.toml 및 현재 저장소에 존재하는 크레이트 매니페스트에서 가져온 것입니다. 여기에는 이 기사를 위해 다시 읽는 동안 발견한 두 가지 불일치가 포함되어 있으며 게시하기 전에 조용히 수정하기보다는 있는 그대로 문서화하고 있습니다.
- Aurabase 루트 Cargo 작업 공간은
resolver = "2"및 단일Cargo.lock아래에 11개의 서비스,aura-cliCLI, 5개의 공유 라이브러리 및aurabase-rsSDK 등 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/*와 같은 일반적인 패턴으로 생성되지 않고 서비스, 도구, 라이브러리 등 역할별로 그룹화되어 손으로 작성됩니다.
resolver = "2"은 외관상의 세부 사항이 아닙니다. Cargo의 리졸버 v2는 build-dependencies 및 대상별 종속성(예:target.'cfg(windows)')의 기능을 나머지 그래프에서 분리합니다. — 더 이상 최종 바이너리로 누출되지 않습니다. 또한 이를 공유하는 작업 공간의 모든 구성원 간에 동일한 종속성 버전을 통합합니다. 단일 axum, 단 한 번만, 11개의 독립적인 해결 방법은 아닙니다.
작업 공간.패키지: 하나의 버전, 하나의 에디션, 원칙적으로 공유
[workspace.package]는 각 크레이트가 복사하는 대신 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 }을 사용하여 이를 참조합니다.
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]] 테이블을 선언합니다.
선언문 자체는 둘 사이의 경계를 기록합니다. 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개만 활성화합니다.
"abort" 대신 panic = "unwind"을 선택하는 것은 파일 주석에 직접 문서화되어 있습니다. axum 처리기의 패닉이 tokio에 의해 차단되고 작업이 500을 반환하며 프로세스는 계속해서 다른 동시 요청을 처리합니다.abort의 성능 향상은 쿼리 간 격리의 손실만큼 가치가 없습니다.
툴체인과 중요한 명령을 동결합니다.
통합 작업공간은 종속성 버전을 설정하지만 컴파일러 자체의 버전은 설정하지 않습니다. rust-toolchain.toml는 루트에서 저장소의 cargo 또는 rustc에 대한 직접 호출을 위해 도구 체인(현재channel = "1.93")을 고정합니다.
이 파일은 자동 드리프트가 발생했기 때문에 정확하게 존재합니다. CI 작업은 rust-toolchain.toml을 읽지 않고 오늘의 stable 채널을 설치했지만 릴리스 Docker 이미지는 고정 버전으로 컴파일되었습니다. 커밋은 오늘날의 안정적인 Rust를 사용하여 모든 테스트를 통과한 다음 이미지 빌드에서 중단될 수 있습니다. 이는 병합 이전이 아닌 병합 후에 발견됩니다. rustup는 모든 직접적인 명령에 대해 이 파일을 존중합니다. 이는 CI 작업 흐름에 영향을 주지 않고 필요한 유일한 수정 사항이었습니다.
매니페스트를 실행하기 전에 읽는 사람들을 위한 마지막 세부 사항: aura-cli 크레이트는 [[bin]] 테이블의 이름을 aura가 아닌 aurabase로 지정하는 바이너리를 컴파일합니다. 게시된 npm 래퍼인 @aurabase/cli는 두 명령(aura 및 aurabase 모두 동일한 스크립트를 가리킴)을 노출합니다. [[bin]] Cargo의 이름, 상자의 이름, npm 래퍼에 의해 노출되는 이름은 세 가지 별개의 것입니다. 나머지 두 개에서는 아무것도 추측할 수 없습니다.
체크리스트: 어디에 무엇을 선언할 것인가
화물 작업 공간에 상자를 추가할 때마다 다섯 가지 결정이 내려집니다. 위에서 다룬 Aurabase 예제를 기반으로 각각이 선언되는 위치는 다음과 같습니다.
| 회원 목록 | [워크스페이스] 멤버 | 루트 — 명시적 목록, 절대 글로벌이 아님 |
|---|---|---|
| 공유 버전/에디션 | [작업공간.패키지] | 루트 — version.workspace = true, 상자별, 선택 사항 |
| 2개 이상의 크레이트가 공유하는 종속성 | [작업공간.종속성] | 루트 - 각 크레이트의 { 작업 공간 = true } |
| 단일 소비자에 대한 의존성 | 상자의 [종속성] | 작업공간을 거치지 않고 로컬로 |
| 프로필 작성 | [프로필.릴리스] | 루트만 — 모든 구성원에게 적용됩니다. |
귀하 또는 귀하가 인계받는 프로젝트의 기존 Cargo 작업 공간을 감사하려면 위에 문서화된 종류의 불일치를 찾는 데 4번의 검사만으로 충분합니다.
[workspace.package].version를 각 상자의version와 비교하세요. 다른 값이 반드시 버그는 아니지만 문서화할 가치가 있습니다.[workspace.dependencies]를 각 크레이트에 의해 로컬로 선언된 종속성과 비교하십시오. 서로 다른 버전의 양쪽에 존재하는 이름을 찾아보세요.- 루트
Cargo.toml의members목록을 저장소의 실제 하위 폴더와 비교합니다.members에서 누락된 폴더가 반드시 실수인 것은 아닙니다. - 크레이트 이름이나 가능한 npm 래퍼에 의해 노출된 이름과 일치한다고 가정하기보다는 각 컴파일된 바이너리(
[[bin]] name)의 실제 이름을 확인하세요.