To nie jest przykład edukacyjny wymyślony na tę okazję. Każdy poniższy fragment kodu pochodzi z katalogu głównego Aurabase Cargo.toml i jego manifestów skrzynek, jakie istnieją obecnie w repozytorium — w tym dwie rozbieżności, które znaleźliśmy podczas ponownego czytania ich na potrzeby tego artykułu i które dokumentujemy w obecnym stanie, zamiast po cichu je poprawiać przed publikacją.
- Główny obszar roboczy Cargo Aurabase deklaruje 18 jawnych elementów członkowskich: 11 usług,
aura-cliCLI, 5 współdzielonych bibliotek iaurabase-rsSDK — podresolver = "2"i pojedynczymCargo.lock. [workspace.dependencies]centralizuje udostępnione wersje; każda skrzynka dziedziczy z{ workspace = true }zamiast ustawiać własny numer — z wyjątkiem sytuacji, gdy skrzynka nadpisuje się po cichu.- Folder pod
services/nie jest automatycznie członkiem obszaru roboczego: listamembersjest jawna, a nie glob, właśnie po to, aby móc wykluczyć kod inny niż Rust. [profile.release]ma zastosowanie tylko raz do całego obszaru roboczego — wybór taki jakpanic = "unwind"ma wówczas zastosowanie do wszystkich jedenastu usług jednocześnie.
Jeden magazyn na usługę czy tylko jeden Cargo.lock?
Obszar roboczy Cargo grupuje wiele skrzyń w jednym Cargo.lock i jednym katalogu docelowym — jest to właśnie funkcjonalność, którą menedżer pakietów Rusta zapewnia w tym przypadku. Jedenaście oddzielnych plików binarnych Rusta, każdy w swoim własnym repozytorium, na pierwszy rzut oka wydaje się bardziej niezależnych. W praktyce oznacza to jedenaście różnych Cargo.lock, jedenaście rozdzielczości wersji, które mogą się różnić w czasie i brak gwarancji, że dwie usługi skompilują tę samą wersjęaxum lub sqlx.
Obszar roboczy Cargo rozwiązuje ten problem na poziomie menedżera pakietów, a nie na poziomie dyscypliny zespołu. Wszyscy członkowie współdzielą pojedynczy Cargo.lock w katalogu głównym: wspólna zależność jest rozwiązywana jednorazowo do identycznej wersji dla całego wykresu. To także sprawia, że refaktor międzyusługowy (na przykład zmiana podpisu w aura-core) jest widoczny dla pojedynczego cargo build --workspace, a nie odkrywany usługa po usłudze w środowisku produkcyjnym. Jest to jedna z opcji, która odróżnia nasz w 100% zunifikowany rdzeń Rust od heterogenicznej usługi złożonej w stosie według usługi.
Korzeń Cargo.toml: narzędzie rozpoznawania nazw i elementy członkowskie
Wszystko zaczyna się od deklaracji [workspace] i jej listy members. W Aurabase ta lista jest pisana odręcznie i pogrupowana według ról — usług, narzędzi, bibliotek — a nie generowana według ogólnego wzorca, takiego jak services/*.
resolver = "2" to nie kosmetyczny szczegół. Narzędzie rozpoznawania nazw v2 firmy Cargo izoluje funkcjonalność build-dependencies i zależności specyficznych dla celu (na przykładtarget.'cfg(windows)') od reszty wykresu — nie wyciekają one już do końcowego pliku binarnego. Ujednolica również wersje tej samej zależności wśród wszystkich członków obszaru roboczego, którzy ją współdzielą: pojedyncze axumtylko raz, a nie jedenaście niezależnych rozwiązań.
workspace.package: jedna wersja, jedno wydanie, w zasadzie wspólne
[workspace.package] raz zadeklaruje wspólne pola — wersję, wydanie, autorów, licencję — które każda skrzynka może odziedziczyć za pomocą version.workspace = true zamiast ich kopiowania.
Wszystkie jedenaście usług Aurabase opiera się na tym schemacie. Dwie skrzynki różnią się od siebie i jest to pierwsza z dwóch rozbieżności znalezionych podczas przygotowywania tego artykułu: aura-cli deklaruje version = "0.2.0" w wersji papierowej, a biblioteka libs/aura-migrations deklaruje version = "0.1.0" — obie różnią się od 0.1.1 obszaru roboczego.
version.workspace = true jest opcjonalne, pole po polu, skrzynka po skrzyni. Nic nie stoi na przeszkodzie, aby skrzynka zachowała własną numerację – dobrowolnie (np. narzędzie opublikowane osobno) lub przez zapomnienie. Audyt obszaru roboczego powinien sprawdzać tę skrzynkę po skrzynce, a nie zakładać dziedziczenie.
workspace.dependents: źródło prawdy, chyba że skrzynia je omija
[workspace.dependencies] centralizuje zależności współdzielone przez kilka skrzynek. Każda usługa odwołuje się do niej za pomocą { workspace = true } zamiast ustawiać własne ograniczenie wersji.
To jest druga prawdziwa rozbieżność: obszar roboczy centralizuje governor w wersji 0.10, ale aura-gateway ponownie deklaruje własną linię governor = "0.8" zamiast dziedziczyć — bramka stosuje swoje ograniczenie szybkości z gołą skrzynią governor, gdy aura-auth, aura-ai, aura-db i aura-functions przechodzi przez tower_governor w oprogramowaniu pośrednim. Przestrzeń robocza nie zapobiega rozbieżnościom: uwidacznia je jedynie, jeśli zadamy sobie trud porównania.
Jednak nie wszystkie zależności zasługują na centralizację. wasmtime nie pojawia się nigdzie w [workspace.dependencies]: tylko jedna skrzynka, aura-functions, używa go w środowisku wykonawczym WASM, więc pozostaje zadeklarowany lokalnie. Zasada, którą stosujemy: zwiększaj zależność do poziomu obszaru roboczego od momentu współdzielenia go przez dwie lub więcej skrzynek, a nie wcześniej.
libs/to Services/: co zależy od czego
Pięć współdzielonych bibliotek (aura-core, aura-crypto, aura-db-adapters, aura-migrations, aura-telemetry) zadeklarowano jako zależności ścieżek w [workspace.dependencies] — aura-core = { path = "libs/aura-core" } — następnie każda usługa wybiera, których faktycznie potrzebuje.
Powstały wykres pozostaje czytelny z jednego pliku do drugiego. aura-gateway zależy tylko od trzech z pięciu wewnętrznych bibliotek — aura-core, aura-crypto i aura-telemetry. Wystarczająco, aby wyznaczyć trasę, podpisać JWT dla wózka bocznego PostgREST i wyeksportować jego ślady, bez dotykania aura-db-adapters ani aura-migrations. aura-provisionerdodaje aura-migrations: odtwarza migrację schematu wynikającą z utworzenia projektu.
Najwęższym przypadkiem jest aura-migrator, dedykowany plik binarny, który wykonuje migracje CI/CD. Wśród wewnętrznych bibliotek Aurabase, jej produkcja [dependencies] zawiera tylko listyaura-migrations — aura-core pojawia się tylko w jej [dev-dependencies]w celach testowych. Dlatego plik binarny dostarczony do produkcji nie zawiera żadnego koduaura-core; istnieje tylko podczas cargo test.
Jedna skrzynia, kilka plików binarnych: podział aury w czasie rzeczywistym
Obszar roboczy nie wymaga tworzenia nowego elementu członkowskiego za każdym razem, gdy potrzebny jest nowy proces do wdrożenia. aura-realtime pozostaje pojedynczym elementem obszaru roboczego, ale jego Cargo.toml deklaruje trzy różne tabele [[bin]] wokół tego samego współdzielonego [lib].
Sam manifest dokumentuje granicę między nimi. cdc-worker przechwytuje zmiany Postgres poprzez odpytywanie i publikuje w NATS w rdzeniu pub/sub (Client::publish_with_headers), bez konieczności otwierania serwera WebSocket — JetStream w tej samej usłudze obsługuje wyłącznie KV obecności między instancjami, a nie rozgałęzianie CDC. ws-front zużywa tylko NATS i utrzymuje połączenia WebSocket/SSE, bez dotykania CDC — szczegółowe informacje znajdują się w dokumentacji silnika czasu rzeczywistego. Obydwa pliki binarne korzystają z tego samego kodu filtrującego RLS za pośrednictwem [lib], ale wdrażają i skalują niezależnie w Kubernetes. To właściwy sygnał, aby wybrać kilka plików binarnych w skrzynce zamiast nowego elementu obszaru roboczego: ta sama wewnętrzna logika, różne topologie wdrażania.
Plik w Services/ niekoniecznie jest członkiem Cargo
Folder aura-edge-runtime rzeczywiście istnieje w repozytorium Aurabase. Jednakże nie zawiera żadnego Cargo.toml — tylko TypeScript (index.ts, envelope.ts) i nie pojawia się nigdzie na liście members głównego obszaru roboczego.
Właśnie dlatego lista members Aurabase jest pisana odręcznie, a nie zastępowana ogólnym wzorcem, takim jak services/*. Glob próbowałby włączyć ten folder inny niż Rust do obszaru roboczego, co skutkowałoby niepowodzeniem w rozpoznawaniu. Jawna lista umożliwia współistnienie folderów, które nie mówią tym samym językiem, pod tym samym elementem nadrzędnym services/.
Lekcja uogólnia: zliczenie usług backendu Rusta poprzez wypisanie podfolderów services/ daje fałszywą liczbę. Tylko katalog główny Cargo.toml jest autorytatywny w odniesieniu do tego, co faktycznie kompiluje się w obszarze roboczym.
[profile.release]: jedno ustawienie dla całego obszaru roboczego
[profile.release] zadeklarowany w katalogu głównym obszaru roboczego dotyczy wszystkich elementów skompilowanych w trybie wydania — tylko jedno miejsce do dostosowania, a nie jedenaście. Cargo dokumentuje wszystkie dostępne klucze w swoim profilu kompilacji , numer referencyjny; Aurabase aktywuje tylko pięć.
Wybór panic = "unwind" zamiast "abort" jest udokumentowany bezpośrednio w komentarzu do pliku: tokio przechwytuje panikę w procedurze obsługi axum, zadanie zwraca 500, a proces w dalszym ciągu obsługuje inne współbieżne żądania. Wzrost wydajnościabort nie jest wart utraty izolacji między zapytaniami.
Zablokuj łańcuch narzędzi i ważne polecenia
Ujednolicony obszar roboczy ustawia wersje zależności, ale nie wersję samego kompilatora. rust-toolchain.tomlw katalogu głównym blokuje zestaw narzędzi (dzisiajchannel = "1.93") dla dowolnego bezpośredniego wywołania cargo lub rustc w repozytorium.
Ten plik istnieje właśnie dlatego, że nastąpił cichy dryf: akcja CI zainstalowała kanał stable dnia bez odczytania rust-toolchain.toml, podczas gdy obraz Dockera w wydaniu został skompilowany z wersją zamrożoną. Zatwierdzenie mogłoby przejść wszystkie testy z dzisiejszym stabilnym Rustem, a następnie przerwać kompilację obrazu — odkryte po połączeniu, a nie wcześniej. rustupszanuje ten plik w przypadku wszelkich bezpośrednich poleceń: była to jedyna konieczna poprawka, która nie wpływała na przepływy pracy CI.
Ostatni szczegół dla tych, którzy czytają manifest przed jego wykonaniem: skrzynia aura-cli kompiluje plik binarny, którego tabela [[bin]] nazywa go aurabase, a nie aura. Opublikowane opakowanie npm, @aurabase/cli, udostępnia dwa polecenia — aura i aurabase wskazują na ten sam skrypt. Nazwa ładunku [[bin]], nazwa skrzyni i nazwa widoczna w opakowaniu npm to trzy różne rzeczy; żadnego nie można odgadnąć na podstawie pozostałych dwóch.
Lista kontrolna: gdzie co zadeklarować
Za każdym razem, gdy dodajesz skrzynię do obszaru roboczego Cargo, pojawia się pięć decyzji. Tutaj jest zadeklarowany każdy z nich, w oparciu o przykład Aurabase omówiony powyżej.
| Lista członków | [obszar roboczy] członków | Root — jawna lista, nigdy glob |
|---|---|---|
| Udostępniona wersja/wydanie | [przestrzeń robocza.pakiet] | Root — wersja.workspace = true, według skrzynki, opcjonalnie |
| Zależność współdzielona przez 2+ skrzynie | [obszar roboczy.zależności] | Root — następnie {workspace = true } w każdej skrzynce |
| Uzależnienie od jednego konsumenta | [zależności] skrzynki | Lokalnie, bez przechodzenia przez obszar roboczy |
| Zbuduj profil | [wydanie.profilu] | Tylko root — dotyczy wszystkich członków |
Aby przeprowadzić audyt istniejącego obszaru roboczego Cargo – Twojego lub projektu, który przejmujesz – wystarczą cztery kontrole, aby znaleźć rodzaj rozbieżności udokumentowanych powyżej:
- Porównaj
[workspace.package].versionzversionkażdej skrzynki — inna wartość niekoniecznie oznacza błąd, ale warto ją udokumentować. - Porównaj
[workspace.dependencies]z zależnościami zadeklarowanymi lokalnie w każdej skrzynce — znajdź nazwy obecne po obu stronach w różnych wersjach. - Porównaj listę
membersw katalogu głównymCargo.tomlz rzeczywistymi podfolderami w repozytorium — brak folderu wmembersniekoniecznie oznacza przeoczenie. - Sprawdź rzeczywistą nazwę każdego skompilowanego pliku binarnego (
[[bin]] name), zamiast zakładać, że pasuje do nazwy skrzynki lub nazwy ujawnionej przez możliwe opakowanie npm.