gRPC-Rust'ı kullanmaya başlama

1. Giriş

Bu codelab'de, Rust ile yazılmış bir rota eşleme uygulamasının temelini oluşturan bir istemci ve sunucu oluşturmak için gRPC-Rust'ı kullanacaksınız.

Eğitimin sonunda, haritada belirli koordinatlarda bulunan yerin adını veya posta adresini almak için gRPC kullanarak uzak bir sunucuya bağlanan bir istemciniz olacak. Tam teşekküllü bir uygulama, bir rota üzerindeki önemli yerleri listelemek veya özetlemek için bu istemci-sunucu tasarımını kullanabilir.

Hizmet, istemci ve sunucu için standart kod oluşturmak üzere kullanılacak bir Protocol Buffers dosyasında tanımlanır. Böylece, bu işlevselliği uygularken zamandan ve emekten tasarruf edersiniz.

Oluşturulan bu kod, yalnızca sunucu ile istemci arasındaki iletişimin karmaşıklıklarını değil, aynı zamanda veri serileştirme ve seri durumdan çıkarma işlemlerini de ele alır.

Neler öğreneceksiniz?

  • Hizmet API'sini tanımlamak için Protocol Buffers'ı kullanma.
  • Otomatik kod oluşturma kullanarak bir Protokol Arabellek tanımından gRPC tabanlı istemci ve sunucu oluşturma.
  • gRPC ile istemci-sunucu iletişimi hakkında bilgi sahibi olmanız gerekir.

Bu codelab, gRPC'ye yeni başlayan veya gRPC bilgilerini tazelemek isteyen Rust geliştiricilerin yanı sıra dağıtılmış sistemler oluşturmakla ilgilenen herkes için hazırlanmıştır. Daha önce gRPC deneyimi gerekmez.

2. Başlamadan önce

Ön koşullar

Aşağıdakileri yüklediğinizden emin olun:

  • GCC. Buradaki talimatları uygulayın.
  • Rust, 1.89.0 sürümü. Yükleme talimatlarını buradan inceleyin.

Kodu alın

Bu codelab, tamamen sıfırdan başlamanız gerekmemesi için uygulamanın kaynak kodunun tamamlayabileceğiniz bir iskeletini sağlar. Aşağıdaki adımlarda, protokol arabellek derleyici eklentilerini kullanarak standart gRPC kodu oluşturma da dahil olmak üzere uygulamanın nasıl tamamlanacağı gösterilmektedir.

Öncelikle codelab çalışma dizinini oluşturun ve bu dizine gidin:

mkdir grpc-rust-getting-started && cd grpc-rust-getting-started

Codelab'i indirip ayıklayın:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-rust-getting-started/start_here

Alternatif olarak, yalnızca codelab dizinini içeren .zip dosyasını indirebilir ve manuel olarak sıkıştırmasını açabilirsiniz.

Bir uygulamayı yazmayı atlamak isterseniz tamamlanmış kaynak kodunu GitHub'da bulabilirsiniz.

3. Hizmeti tanımlama

İlk adımınız, Protocol Buffers'ı kullanarak uygulamanın gRPC hizmetini, RPC yöntemini, istek ve yanıt mesajı türlerini tanımlamaktır. Hizmetinizde sunulacaklar:

  • Sunucunun uyguladığı ve istemcinin çağırdığı GetFeature adlı bir RPC yöntemi.
  • GetFeature yöntemi kullanılırken istemci ile sunucu arasında değiştirilen veri yapıları olan Point ve Feature mesaj türleri. İstemci, sunucuya gönderdiği GetFeature isteğinde harita koordinatlarını Point olarak sağlar ve sunucu, bu koordinatlarda bulunan her şeyi açıklayan ilgili bir Feature ile yanıt verir.

Bu RPC yöntemi ve mesaj türleri, sağlanan kaynak kodun proto/route_guide.proto dosyasında tanımlanır.

Protocol Buffers genellikle protobuf olarak bilinir. gRPC terminolojisi hakkında daha fazla bilgi için gRPC'nin Temel kavramlar, mimari ve yaşam döngüsü başlıklı makalesine bakın.

Hizmet yöntemi

Öncelikle hizmet yöntemlerimizi, ardından da mesaj türlerimizi Point ve Feature tanımlayalım. proto/routeguide.proto dosyasında, uygulamanın hizmeti tarafından sağlanan bir veya daha fazla yöntemi tanımlayan RouteGuide adlı bir service yapısı bulunur.

rpc yöntemini RouteGuide tanımının içine ekleyin.GetFeature Daha önce açıklandığı gibi, bu yöntem belirli bir koordinat kümesinden bir konumun adını veya adresini arar. Bu nedenle, belirli bir Point için GetFeature işlevinin Feature döndürmesini sağlayın:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

Bu, tekli bir UPÇ yöntemidir: İstemcinin sunucuya istek gönderdiği ve yanıtın geri gelmesini beklediği basit bir UPÇ'dir (yerel bir işlev çağrısı gibi).

Mesaj türleri

Kaynak kodun proto/route_guide.proto dosyasında önce Point mesaj türünü tanımlayın. Point, haritadaki bir enlem-boylam koordinat çiftini temsil eder. Bu codelab'de koordinatlar için tam sayıları kullanın:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

1 ve 2 sayıları, message yapısındaki her alan için benzersiz kimlik numaralarıdır.

Ardından, Feature mesaj türünü tanımlayın. Bir Feature, Point ile belirtilen bir konumdaki bir şeyin adı veya posta adresi için string alanını kullanır:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

4. İstemci ve sunucu kodunu oluşturma

Oluşturulan dizindeki .proto dosyasından oluşturulan kodu size daha önce verdik.

Her projede olduğu gibi, kodumuz için gerekli olan bağımlılıkları düşünmemiz gerekir. Rust projelerinde bağımlılıklar Cargo.toml içinde yer alır. Gerekli bağımlılıkları Cargo.toml dosyasında listeledik.

.proto dosyasından kodu kendiniz nasıl oluşturacağınızı öğrenmek istiyorsanız bu talimatları inceleyin.

Oluşturulan kod şunları içerir:

  • Point ve Feature mesaj türleri için yapı tanımları.
  • Uygulamamız gereken bir hizmet özelliği: route_guide_server::RouteGuide.
  • Sunucuyu çağırmak için kullanacağımız istemci türü: route_guide_client::RouteGuideClient<T>.

Ardından, istemci bir istek gönderdiğinde sunucunun yanıt verebilmesi için sunucu tarafında GetFeature yöntemini uygulayacağız.

5. Hizmeti uygulama

src/server/server.rs içinde, oluşturulan kodu gRPC'nin include_generated_proto! makrosu aracılığıyla kapsam içine alabilir, RouteGuide özelliğini ve Point öğesini içe aktarabiliriz.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

pub use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature,
};

Hizmetimizi temsil edecek bir yapı tanımlayarak başlayabiliriz. Bunu şimdilik src/server/server.rs üzerinde yapabiliriz:

#[derive(Debug)]
pub struct RouteGuideService {
    features: Vec<Feature>,
}

Şimdi, oluşturduğumuz koddan route_guide_server::RouteGuide özelliğini uygulamamız gerekiyor.

Tekli RPC (Unary RPC)

RouteGuideService, tüm hizmet yöntemlerimizi uygular. Sunucu tarafındaki get_feature işlevi, asıl işin yapıldığı yerdir: Bu işlev, istemciden bir Point mesajı alır ve bilinen yerlerin listesinden ilgili konum bilgilerini Feature mesajıyla döndürür. İşlevin src/server/server.rs'daki uygulanışı:

#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
    async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
        println!("GetFeature = {:?}", request);
        let requested_point = request.get_ref();
        for feature in self.features.iter() {
            if feature.location().latitude() == requested_point.latitude() {
                if feature.location().longitude() == requested_point.longitude(){
                    return Ok(Response::new(feature.clone()))
                };
            };    
        }
        Ok(Response::new(Feature::default()))
    }
}

Yöntemde, belirli bir Point için uygun bilgileri içeren bir Feature nesnesi oluşturun ve döndürün.

Bu yöntemi uyguladıktan sonra, istemcilerin hizmetimizi kullanabilmesi için bir gRPC sunucusu da başlatmamız gerekir. main() yerine bunu kullanın.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:10000".parse().unwrap();
    println!("RouteGuideServer listening on: {addr}");
    let route_guide = RouteGuideService {
        features: load(),
    };
    let svc = RouteGuideServer::new(route_guide);
    Server::builder().add_service(svc).serve(addr).await?;
    Ok(())
}

main() bölgesinde adım adım neler oluyor?

  1. İstemci isteklerini dinlemek için kullanmak istediğimiz bağlantı noktasını belirtin.
  2. Yardımcı işlev load() çağrılarak yüklenen özelliklerle RouteGuideService oluşturma
  3. Oluşturduğumuz hizmeti kullanarak RouteGuideServer::new() ile gRPC sunucusunun bir örneğini oluşturun.
  4. Hizmet uygulamamızı gRPC sunucusuna kaydedin.
  5. İşlem sonlandırılana kadar engelleme beklemesi yapmak için sunucuda bağlantı noktası ayrıntılarımızla birlikte serve() komutunu çağırın.

6. İstemciyi oluşturma

Bu bölümde, src/client/client.rs dilinde RouteGuide hizmetimiz için bir Rust istemcisi oluşturmayı ele alacağız.

src/server/server.rs bölümünde yaptığımız gibi, oluşturulan kodu gRPC'nin include_generated_code! makrosu aracılığıyla kapsam içine alabilir ve RouteGuideClient türünü içe aktarabiliriz.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_client::RouteGuideClient,
    Point,
};

Hizmet yöntemlerini arama

gRPC-Rust'ta RPC'ler, engelleme/eşzamanlı modda çalışır. Bu, RPC çağrısının sunucunun yanıt vermesini beklediği ve yanıt ya da hata döndüreceği anlamına gelir.

Hizmet yöntemlerini çağırmak için öncelikle sunucuyla iletişim kuracak bir kanal oluşturmamız gerekir. Bunu oluşturmak için önce bir uç nokta oluşturur, bu uç noktaya bağlanır ve RouteGuideClient::new()'ya bağlanıldığında oluşturulan kanalı aşağıdaki gibi iletiriz:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel);
    Ok(())
}

Bu işlevde, istemciyi oluştururken yukarıda oluşturulan genel kanalı, .proto hizmetinde tanımlanan belirli yöntemleri uygulayan oluşturulmuş kod saplamasıyla sarmalarız.

Simple RPC

Basit RPC GetFeature'yi çağırmak, yerel bir yöntemi çağırmak kadar kolaydır. Bunu main() içinde ekleyin.

println!("*** SIMPLE RPC ***");
let point = proto!(Point{
    latitude: 409_146_138,
    longitude: -746_188_906
});
let response = client
    .get_feature(Request::new(point))
    .await?.into_inner();
Ok(())

Gördüğünüz gibi, daha önce aldığımız saplamadaki yöntemi çağırıyoruz. Yöntem parametrelerimizde bir istek protokol arabellek nesnesi (bizim durumumuzda Point) oluşturup dolduruyoruz. Çağrı hata döndürmezse yanıt bilgilerini sunucudan ilk dönüş değerinden okuyabiliriz.

println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
    response.name(),
    response.location().latitude(),
    response.location().longitude());

Sonuç olarak, istemcinin main() işlevi şu şekilde görünmelidir:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    //Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel); 

    println!("*** SIMPLE RPC ***");
    let point = proto!(Point{
        latitude: 409_146_138,
        longitude: -746_188_906
    });
    let response = client
        .get_feature(Request::new(point))
        .await?.into_inner();

    println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
        response.name(),
        response.location().latitude(),
        response.location().longitude());
    Ok(())
}

7. Deneyin

Öncelikle istemcimizi ve sunucumuzu çalıştırmak için bunları sandığımıza ikili hedef olarak ekleyelim. Cargo.toml bölümümüzü buna göre düzenlememiz ve aşağıdakileri eklememiz gerekiyor:

[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"

[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"

Ardından, çalışma dizinimizden aşağıdaki komutları yürütün:

  1. Sunucuyu bir terminalde çalıştırın:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server 
  1. İstemciyi başka bir terminalden çalıştırma:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client

Aşağıdakine benzer bir çıkış görürsünüz. Zaman damgaları, netlik için çıkarılmıştır:

*** SIMPLE RPC ***

FEATURE: Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Lat = 409146138, Lon = -746188906

8. Sırada ne var?