Wzorce programowania w Rust
Wzorce Rust dla agentów AI to reguły, prompty i bramki, dzięki którym Claude Code, Codex i Cursor tworzą usługi na Axum, SQLx i Tokio akceptowane przez kompilator bez zalewu .clone(): jawny ownership, jeden typ błędu thiserror mapowany na HTTP, query_as! sprawdzane na prawdziwym schemacie i czyste zamykanie. Kod agenta przyjmuje się na podstawie wyników clippy, testów, SQLx i testów mutacyjnych.
Prosisz agenta o endpoint użytkowników. Dostajesz kod, który klonuje każdy String, żeby uciszyć borrow checker, rejestruje trasę /users/:id (na axum 0.8 panikuje przy starcie) i pisze query_as!, którego lista SELECT nie zgadza się ze strukturą. Potem pada CI, bo nie ma tam DATABASE_URL. Każda poprawka to kolejna runda, a i tak kończysz, czytając każdą linijkę.
Ta receptura jest dla programistów, którzy budują backendy w Rust z agentem. Ograniczenia, na których zależy kompilatorowi, trafiają od razu do promptów, a wynik udowadnia toolchain, a nie Twoje oczy.
Co daje Ci projekt Rust przygotowany pod agenta
Dział zatytułowany „Co daje Ci projekt Rust przygotowany pod agenta”- Jeden blok reguł Rust w
AGENTS.md, którego przestrzegają Claude Code, Codex i Cursor, oraz bramkęmake check, którą agent musi przejść, zanim zgłosi „gotowe” - Sześć promptów do wklejenia: refaktoryzacja ownershipu, kanoniczny model domeny, repozytorium SQLx świadome schematu, typ błędu mapowany na HTTP, graceful shutdown oraz testy bazodanowe i property-based
- Konfigurację serwera Postgres MCP tylko do odczytu, żeby agent czytał Twój schemat, zanim napisze zapytanie
- Tabelę weryfikacji, która mówi, która bramka łapie który błąd, więc czytasz wynik bramki zamiast diffu
- Sposoby wyjścia z siedmiu typowych awarii Rust pisanego przez agenta
Przygotuj konwencje Rust i bramkę, którą agent musi przejść
Dział zatytułowany „Przygotuj konwencje Rust i bramkę, którą agent musi przejść”Zanim poprosisz o jakikolwiek kod, napisz raz na repozytorium trzy pliki: przypięcie toolchaina, manifest workspace’u z aktualnymi wersjami crate’ów i plik reguł z jedną definicją „gotowe”.
-
Przypnij toolchain w
rust-toolchain.toml. 26 września 2026 aktualne wydanie stabilne to Rust 1.98.1 (według manifestu kanału stable nastatic.rust-lang.org). Przypięcie powstrzymuje agenta przed zakładaniem starszego kompilatora, a przy SQLx ma to znaczenie:sqlx0.9.0 deklaruje na crates.iorust-version = 1.94.[toolchain]channel = "1.98.1"components = ["rustfmt", "clippy", "rust-analyzer"] -
Zadeklaruj stos raz, w
Cargo.tomlworkspace’u. Wersje to najnowsze na crates.io z 26 września 2026. Wymagania z daszkiem pozwalającargo updatepodnosić wydania poprawkowe; dla crate’a0.x, takiego jak"0.9", daszek nie wychodzi poza0.9.*.[workspace]members = ["api", "db"]resolver = "3"[workspace.package]edition = "2024"[workspace.dependencies]axum = "0.8"tokio = { version = "1", features = ["full"] }tokio-util = { version = "0.7", features = ["rt"] } # "rt" enables TaskTrackerserde = { version = "1", features = ["derive"] }serde_json = "1"sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls", "postgres", "macros", "migrate", "chrono",] }chrono = { version = "0.4", features = ["serde"] }thiserror = "2"anyhow = "1"tracing = "0.1"argon2 = "0.6"proptest = "1"Łatwo przeoczyć dwie flagi. Bez
chronowsqlxpoleDateTime<Utc>nie skompiluje się wquery_as!. Bezrtwtokio-utilnie istniejeTaskTracker. -
Dodaj bramkę jako cel
make check. PotrzebujeDATABASE_URLwskazującego na lokalny, deweloperski Postgres, bo zarównocargo sqlx prepare --check, jak i#[sqlx::test]łączą się z serwerem..PHONY: checkcheck:cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo sqlx prepare --workspace --checkcargo nextest run --workspacecargo test --workspace --doccargo deny checkZainstaluj narzędzia raz, w terminalu, a potem wygeneruj startowy
deny.toml:Okno terminala cargo install --locked cargo-nextest cargo-deny sqlx-cli cargo-mutantscargo deny initcargo nextestnie uruchamia doctestów, dlatego bramka wywołuje osobnocargo test --doc.cargo deny checkjednym poleceniem sprawdza zgłoszenia RustSec, licencje oraz zakazane i zdublowane crate’y. -
Umieść reguły Rust w
AGENTS.mdw katalogu głównym repozytorium. Codex czyta ten plik natywnie; zakładki poniżej pokazują, jak ten sam tekst trafia do Claude Code i Cursora.## Rust conventions- Edition 2024, toolchain from rust-toolchain.toml. Stack: axum 0.8 + sqlx 0.9(Postgres) + tokio. Do not add actix-web, diesel, rocket, or sea-orm.- axum 0.8 routes use `/{id}`, never `/:id`. Extractors implementFromRequestParts without #[async_trait].- Borrow by default (&str, &[T]). Take ownership only when the callee stores thevalue. Never add .clone(), Arc, or Rc just to satisfy the borrow checker;change the signature and explain why instead.- Every fallible function returns Result<T, AppError>. No .unwrap() or .expect()outside #[cfg(test)] and main().- SQL uses sqlx::query_as! / query! macros only. After any query or migrationchange, run `cargo sqlx prepare --workspace` and commit .sqlx/.- CPU-heavy or blocking work (password hashing, std::fs) runs intokio::task::spawn_blocking.- Never delete or loosen an existing test, and never add #[ignore], to reachgreen. If a test looks wrong, stop and say why.- Done means `make check` passes. Paste its last 20 lines in your final message.
Najwięcej ważą dwie ostatnie reguły. Pierwsza nie pozwala agentowi osłabiać testów, żeby dojść do zielonego wyniku; w artykule chroń wyrocznię wyjaśniamy, dlaczego to najczęstszy sposób, w jaki praca agenta udaje sukces. Druga zamienia „kompiluje się” w dowód, który przeczytasz w dziesięć sekund.
Podłącz reguły i bramkę w każdym narzędziu
Dział zatytułowany „Podłącz reguły i bramkę w każdym narzędziu”Plik reguł i prompty są takie same we wszystkich narzędziach. Różni się to, jak każde z nich ładuje reguły, formatuje kod i recenzuje wynik.
Utwórz CLAUDE.md, który importuje wspólny plik. Import @path wstawia jego treść do kontekstu przy starcie:
@AGENTS.mdOd wersji 2.1.277 (kanał latest 26 września 2026) Claude Code sam czyta AGENTS.md, gdy projekt nie ma CLAUDE.md. Import działa na każdym kanale.
Formatuj każdy plik Rust edytowany przez agenta hookiem PostToolUse w .claude/settings.json. Przekaż --edition 2024, bo rustfmt uruchomiony na pojedynczym pliku nie odczytuje edycji z Cargo.toml:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | grep '\\.rs$' | xargs -r rustfmt --edition 2024" } ] } ] }}Daj agentowi nawigację świadomą typów i diagnostykę kompilatora przez plugin rust-analyzer-lsp z oficjalnego marketplace’u Anthropic. Plugin tylko rejestruje serwer; binarkę dostarcza komponent rust-analyzer z kroku 1:
claude plugin install rust-analyzer-lsp@claude-plugins-officialZanim otworzysz pull request, uruchom w sesji /code-review, żeby dostać drugą opinię o diffie.
Codex czyta AGENTS.md bezpośrednio, więc nie potrzebujesz dodatkowego pliku. Uruchom zadanie z zapisem w workspace i prośbą o zgodę na wszystko szersze:
codex -s workspace-write -a on-request "Implement UserRepository as described in docs/tasks/users.md. Done means make check passes."Jeśli cargo nie może pobrać crate’ów z wnętrza sandboksa, uruchom najpierw cargo fetch we własnym terminalu, żeby agent budował z lokalnego cache’u rejestru.
Zrecenzuj gałąź, zanim ją wypchniesz:
codex review --base mainUtwórz regułę projektu w ustawieniach Rules Cursora z tą samą listą co w AGENTS.md i dołącz ją do plików Rust. Zainstaluj rozszerzenie rust-analyzer, żeby edytor zgłaszał diagnostykę kompilatora.
Przy szkieletach z wieloma crate’ami z tej strony używaj Plan Mode, żeby zatwierdzić listę plików, zanim powstanie jakikolwiek kod. Niezależne zadania uruchamiaj w Worktrees, a pull request oddaj do recenzji Bugbotowi.
Zacznij w każdym narzędziu od jego domyślnego modelu i podnieś poziom effort, zanim zmienisz model; aktualne domyślne modele znajdziesz w przeglądzie modeli.
Zbuduj usługę na Axum i SQLx z agentem krok po kroku
Dział zatytułowany „Zbuduj usługę na Axum i SQLx z agentem krok po kroku”Każdy krok to jeden prompt, fragment dobrej odpowiedzi i bramka, która ją udowadnia. Wykonuj je po kolei: późniejsze prompty zakładają, że model i repozytorium już istnieją.
Jak powstrzymać agenta przed obchodzeniem borrow checkera klonowaniem?
Dział zatytułowany „Jak powstrzymać agenta przed obchodzeniem borrow checkera klonowaniem?”Gdy agent trafia na błąd ownershipu, domyślnie naprawia go przez .clone() albo Arc::new. Oba sposoby się kompilują i oba ukrywają pytanie projektowe, które zadawał błąd. Nazwij historię ownershipu, jakiej chcesz, i zabroń drogi na skróty.
Dobra odpowiedź zmienia sygnaturę, a nie dane w miejscach wywołania:
// Before: consumes the vector, so callers clone it first.pub fn active_emails(users: Vec<User>) -> Vec<String> { users.into_iter().filter(|u| u.active).map(|u| u.email).collect()}
// After: borrows; the returned &str values live as long as `users`.pub fn active_emails(users: &[User]) -> Vec<&str> { users.iter().filter(|u| u.active).map(|u| u.email.as_str()).collect()}Nie musisz sprawdzać lifetime’ów wzrokiem: jeśli pożyczka jest błędna, build się nie uda. Kompilator nie powie Ci natomiast, czy agent przemycił klonowanie gdzie indziej, więc policz klony w diffie (zobacz sekcję o weryfikacji).
Jak przypiąć model domeny, żeby query_as! się kompilowało?
Dział zatytułowany „Jak przypiąć model domeny, żeby query_as! się kompilowało?”Rust pisany przez agenta często się rozjeżdża: CreateUser ma trzy pola w jednym pliku i dwa w innym, a query_as! nie przechodzi, bo lista SELECT pomija kolumnę zadeklarowaną w strukturze. Zdefiniuj typy raz i powiedz agentowi, że to jedyne dozwolone definicje.
use chrono::{DateTime, Utc};
/// One row of the `users` table, field for field.#[derive(Debug, Clone, serde::Serialize)]pub struct User { pub id: i64, pub email: String, pub name: String, pub active: bool, #[serde(skip_serializing)] pub password_hash: String, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,}
/// Request body for creation. Carries the plaintext password; the repository hashes it.#[derive(Debug, serde::Deserialize)]pub struct CreateUser { pub email: String, pub name: String, pub password: String,}#[serde(skip_serializing)] przy password_hash to linijka, która trzyma hash z dala od każdej odpowiedzi JSON. Umieść ją w typie kanonicznym, żeby żaden handler nie mógł o niej zapomnieć.
Jak dać agentowi prawdziwy schemat, zanim napisze zapytania SQLx?
Dział zatytułowany „Jak dać agentowi prawdziwy schemat, zanim napisze zapytania SQLx?”query_as! sprawdza każde zapytanie na żywej bazie w czasie kompilacji, więc zgadnięta nazwa kolumny przerywa kompilację. Agent zgaduje, bo nie widzi Twojego schematu. Serwer Postgres MCP tylko do odczytu pozwala mu najpierw obejrzeć prawdziwe tabele, więc zapytanie pasuje za pierwszym podejściem, a nie za czwartym.
Ta receptura używa Postgres MCP Pro, pakietu Pythona (postgres-mcp 0.3.0 na PyPI, Python 3.12 lub nowszy) uruchamianego przez uvx. Flaga --access-mode=restricted ogranicza go do transakcji tylko do odczytu. Narzędzia różnią się wyłącznie miejscem, w którym trzymają konfigurację.
claude mcp add postgres -e DATABASE_URI=postgresql://localhost/myapp_dev \ -- uvx postgres-mcp --access-mode=restrictedDomyślny zakres to local: serwer jest zapisany dla tego projektu w Twojej konfiguracji użytkownika, a nie w repozytorium. Dodaj -s project tylko wtedy, gdy chcesz go mieć we wspólnym .mcp.json, i nigdy nie commituj connection stringa z hasłem. Połączenie sprawdzisz przez claude mcp list.
codex mcp add postgres --env DATABASE_URI=postgresql://localhost/myapp_dev \ -- uvx postgres-mcp --access-mode=restrictedTo polecenie zapisuje wpis [mcp_servers.postgres] w ~/.codex/config.toml. Sprawdzisz go przez codex mcp list.
.cursor/mcp.json:
{ "mcpServers": { "postgres": { "command": "uvx", "args": ["postgres-mcp", "--access-mode=restricted"], "env": { "DATABASE_URI": "postgresql://localhost/myapp_dev" } } }}Rdzeń dobrej odpowiedzi:
pub async fn find_by_email(&self, email: &str) -> Result<Option<User>> { let user = sqlx::query_as!( User, "SELECT id, email, name, active, password_hash, created_at, updated_at \ FROM users WHERE email = $1", email ) .fetch_optional(&self.pool) .await?; Ok(user)}fetch_optional ma znaczenie: fetch_one dla brakującego użytkownika zwraca sqlx::Error::RowNotFound, który następna sekcja musi zamienić na 404 zamiast 500. Krok cargo sqlx prepare --workspace --check w bramce kończy się błędem, gdy zacommitowane metadane w .sqlx/ przestają pasować do zapytań. To właśnie utrzymuje CI zdolne do budowania z SQLX_OFFLINE=true.
Jak mapować błędy na kody HTTP, nie ujawniając szczegółów wewnętrznych?
Dział zatytułowany „Jak mapować błędy na kody HTTP, nie ujawniając szczegółów wewnętrznych?”Zdefiniuj enum błędów raz, przez thiserror, a potem zaimplementuj IntoResponse, żeby axum zamieniał każdy wariant na kod statusu. W kodzie agenta powtarzają się dwa błędy: RowNotFound zwracany jako 500 i surowy tekst błędu bazy wysyłany klientowi.
use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
#[derive(Debug, thiserror::Error)]pub enum AppError { #[error("{0} not found")] NotFound(&'static str), #[error("validation failed: {0}")] Validation(String), #[error("{0} already exists")] Conflict(&'static str), #[error("unauthorized")] Unauthorized, #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] Internal(#[from] anyhow::Error),}
pub type Result<T> = std::result::Result<T, AppError>;
impl IntoResponse for AppError { fn into_response(self) -> Response { let status = match &self { AppError::NotFound(_) | AppError::Database(sqlx::Error::RowNotFound) => StatusCode::NOT_FOUND, AppError::Validation(_) => StatusCode::BAD_REQUEST, AppError::Conflict(_) => StatusCode::CONFLICT, AppError::Unauthorized => StatusCode::UNAUTHORIZED, AppError::Database(_) | AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, }; // Log the detail; send the client a generic message for every 5xx. let message = if status.is_server_error() { tracing::error!(error = ?self, "request failed"); "internal error".to_string() } else { self.to_string() }; (status, Json(serde_json::json!({ "error": message }))).into_response() }}Dowodem są testy handlerów. Nie przejdą, jeśli wariant trafi na zły status albo jeśli tekst z bazy dotrze do klienta, a Ty nie musisz czytać into_response, żeby się o tym dowiedzieć.
Jak czysto zamknąć usługę na axum?
Dział zatytułowany „Jak czysto zamknąć usługę na axum?”Poproś o „graceful shutdown”, a agent często zbuduje własny kanał oneshot: serwer przestaje przyjmować połączenia, ale uruchomione zadania w tle działają dalej i nic na nie nie czeka. Do żądań użyj with_graceful_shutdown z axum, a do całej reszty CancellationToken i TaskTracker z tokio-util.
axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?;
token.cancel(); // tell background workers to stop taking new worktracker.close(); // no new tasks may be spawnedif tokio::time::timeout(Duration::from_secs(10), tracker.wait()).await.is_err() { tracing::warn!("background tasks did not finish within 10s");}Dowodem jest test integracyjny: nie przejdzie, jeśli nikt nie czeka na tracker, bo zamykanie kończy się przed zakończeniem zadania, a asercja ukończenia widzi niedokończone zadanie.
Jak generować testy Rust, które łapią prawdziwe błędy?
Dział zatytułowany „Jak generować testy Rust, które łapią prawdziwe błędy?”Nazwij przypadki wprost, inaczej dostaniesz same testy happy path. Potem dodaj jeden test na prawdziwym Postgresie, bo zamockowane repozytorium nie wyłapie złej kolumny ani brakującego indeksu unikalnego. #[sqlx::test] tworzy dla każdego testu izolowaną bazę i automatycznie stosuje ./migrations, gdy włączona jest flaga migrate.
Prośba o listę przypadków przed kodem daje Ci jedyną rzecz wartą przeczytania: listę testów. Więcej o projektowaniu testów z agentem znajdziesz w artykułach o testach integracyjnych i testach property-based.
Jak zweryfikować Rust napisany przez agenta bez czytania każdej linijki?
Dział zatytułowany „Jak zweryfikować Rust napisany przez agenta bez czytania każdej linijki?”Czytaj wynik bramki i listę testów, nie diff. Kompilator już wyklucza błędy bezpieczeństwa pamięci i wyścigi danych w bezpiecznym kodzie, więc uwaga człowieka idzie tam, czego on nie widzi: do logiki, mapowania błędów i tego, czy testy zauważą regresję.
| Bramka | Polecenie | Co łapie |
|---|---|---|
| Formatowanie | cargo fmt --all -- --check | Niesformatowane pliki, które ominął hook |
| Lint | cargo clippy --workspace --all-targets -- -D warnings | Zbędne pożyczki i konwersje, typowe linty poprawności i wydajności; .unwrap() w kodzie produkcyjnym po włączeniu clippy::unwrap_used |
| Metadane zapytań | cargo sqlx prepare --workspace --check | Nieaktualne .sqlx/, które psuje buildy z SQLX_OFFLINE w CI |
| Testy | cargo nextest run --workspace i cargo test --doc | Złe zachowanie, złe kody statusu, brakujące ograniczenia |
| Łańcuch dostaw | cargo deny check | Ostrzeżenia bezpieczeństwa RustSec, niedozwolone licencje, zdublowane lub zakazane crate’y |
| Siła testów | cargo mutants --in-diff pr.diff | Zmieniony kod, którego testy przechodzą, nawet gdy kod jest zepsuty |
| Liczba klonów | git diff main -- '*.rs' | grep -c '^+.*\.clone()' | Klony dodane przez agenta, żeby obejść borrow checker |
| Agent recenzujący | /code-review (Claude Code), codex review --base main (Codex), Bugbot (Cursor) | Błędy logiki, których bramki nie potrafią wyrazić |
Plik pr.diff wygenerujesz przez git diff main > pr.diff. Testy mutacyjne ważą tu najwięcej: zielony zestaw dowodzi tylko tego, że testy przechodzą, a cargo mutants pokazuje, czy nie przejdą, gdy zmieniony kod zostanie zepsuty. Pierwsze pięć bramek uruchamiaj w CI przy każdym pull requeście (plik workflow znajdziesz we wzorcach pipeline’ów CI/CD), a testy mutacyjne na diffie.
Inżynier odpowiedzialny za usługę akceptuje cztery rzeczy: listę przypadków testowych, zieloną bramkę, raport mutacyjny bez przeoczonych mutantów w zmienionym kodzie i liczbę klonów, którą potrafi uzasadnić. Ludzkie czytanie trafia do fragmentów oznaczonych przez agenta recenzującego oraz do każdego kodu z unsafe, spawn_blocking albo transakcjami. Jak podzielić tę pracę w zespole, opisuje recenzja PR z agentem.
Gdy Rust napisany przez agenta się psuje
Dział zatytułowany „Gdy Rust napisany przez agenta się psuje”Każda z poniższych awarii przetrwa pobieżny rzut oka na diff. Każda ma objaw, który wykryjesz, i sposób wyjścia, który wkleisz.
- Zalew klonów i
Arc. Objaw: liczba klonów w diffie skacze alboArc<Mutex<_>>pojawia się wokół danych, których dotyka tylko jedno zadanie. Wyjście: prompt „Remove every.clone()andArcyou added in this branch. For each one, either change the signature to borrow or explain in one line why ownership must move. Done meansmake checkpasses.” - Przestarzałe idiomy z axum 0.7. Objaw: serwer panikuje przy starcie na trasie typu
/users/:idalbo ekstraktor ma#[async_trait]. Przyczyna: axum 0.8.0 zmienił składnię ścieżek na/{id}(stara celowo wywołuje panikę), a axum-core zastąpił#[async_trait]przezimpl Traitw pozycji zwracanej w traitach. Wyjście: trzymaj regułę o axum wAGENTS.mdi użyj promptu „Migrate this crate to axum 0.8:/{param}route syntax, no#[async_trait]on extractors. Change no behaviour.” query_as!pada w CI, ale nie lokalnie. Objaw:set DATABASE_URL to use query macros online, or run cargo sqlx prepare. Przyczyna: agent zmienił zapytanie bez odświeżenia.sqlx/. Wyjście: uruchomcargo sqlx prepare --workspacena bazie deweloperskiej, zacommituj.sqlx/i ustawSQLX_OFFLINE=truew CI. Krok--checkw bramce nie pozwoli, żeby to się powtórzyło.- Runtime async staje pod obciążeniem. Objaw: skoki opóźnień na wszystkich endpointach, gdy jeden z nich jest zajęty. Przyczyna: hashowanie argon2,
std::fsalbo blokujący klient HTTP wywołany wprost wasync fn. Wyjście: prompt „Find every CPU-heavy or blocking call inside anasync fnin this workspace and move it intotokio::task::spawn_blocking. List each call you moved.” - Błędy wewnętrzne wyciekają do klientów. Objaw: treść odpowiedzi 500 zawiera SQL, nazwę tabeli albo ścieżkę pliku. Przyczyna:
self.to_string()używane dla każdego statusu. Wyjście: ogólny komunikat dla 5xx winto_responseplus test handlera, który sprawdza, że w treści nie ma tekstu SQL. - „Zero-copy”, które kopiuje. Objaw: agent nazywa parser zero-copy, ale ten wywołuje
.to_vec()albo zbiera każdą ramkę doVec<u8>. Wyjście: poproś obytes::BytesMut::split_to(n).freeze(), które zwraca zliczany referencjami widokBytesna tę samą alokację, albo otokio_util::codec::LengthDelimitedCodeczamiast ręcznego ramkowania. Zysk udowodnij benchmarkiem, nie etykietą. - Agent osłabia test, żeby dojść do zielonego wyniku. Objaw: poluzowana asercja, usunięty przypadek albo
#[ignore]dodane w tym samym diffie co poprawka. Wyjście: przywróć plik testu, powtórz regułę zAGENTS.mdi skonfiguruj CI tak, żeby odrzucało pull request, który zmienia, przenosi albo usuwa istniejący test bez etykietytest-change:git diff --name-status --diff-filter=MDR origin/main... -- '*tests/*' | awk -F'\t' '{print $2}'wypisuje te pliki pod ścieżką sprzed zmiany, a pełną kontrolę opisuje artykuł chroń wyrocznię.
Dokąd dalej z usługami w Rust
Dział zatytułowany „Dokąd dalej z usługami w Rust”- Najpierw przygotuj repozytorium pod agenta, jeśli jeszcze nie jest gotowe: baza kodu gotowa na agenta.
- Wybierz i zabezpiecz serwer MCP dla bazy danych: serwery MCP dla baz danych.
- Wzmocnij warstwę danych: wzorce optymalizacji SQL i wzorce migracji baz danych.
- Zaprojektuj warstwę HTTP: wzorce tworzenia API.
- Zdecyduj, co agent może uruchamiać bez nadzoru: uprawnienia i sandboxing.
- Porównaj z innym kompilowanym backendem: wzorce języka Go albo wróć do przeglądu receptur backendowych.