gRPC-Rust দিয়ে শুরু করা

১. ভূমিকা

এই কোডল্যাবে, আপনি gRPC-Rust ব্যবহার করে একটি ক্লায়েন্ট ও সার্ভার তৈরি করবেন, যা Rust-এ লেখা একটি রাউট-ম্যাপিং অ্যাপ্লিকেশনের ভিত্তি স্থাপন করবে।

এই টিউটোরিয়ালটি শেষ করার পর, আপনি এমন একটি ক্লায়েন্ট তৈরি করতে পারবেন যা gRPC প্রোটোকলের অফিসিয়াল রাস্ট ইমপ্লিমেন্টেশন ব্যবহার করে একটি রিমোট সার্ভারের সাথে সংযোগ স্থাপন করবে এবং ম্যাপের নির্দিষ্ট স্থানাঙ্কে অবস্থিত কোনো স্থানের নাম বা ডাক ঠিকানা সংগ্রহ করবে। একটি পূর্ণাঙ্গ অ্যাপ্লিকেশন কোনো রুটের গুরুত্বপূর্ণ স্থানগুলোর তালিকা তৈরি বা সারসংক্ষেপ করার জন্য এই ক্লায়েন্ট-সার্ভার ডিজাইনটি ব্যবহার করতে পারে।

সার্ভিসটি একটি প্রোটোকল বাফারস ফাইলে সংজ্ঞায়িত করা আছে, যা ক্লায়েন্ট এবং সার্ভারের জন্য বয়লারপ্লেট কোড তৈরি করতে ব্যবহৃত হবে, যাতে তারা একে অপরের সাথে যোগাযোগ করতে পারে। এর ফলে ঐ কার্যকারিতাটি বাস্তবায়নে আপনার সময় ও শ্রম বাঁচবে।

এই জেনারেট করা কোডটি শুধু সার্ভার ও ক্লায়েন্টের মধ্যকার যোগাযোগের জটিলতাই নয়, ডেটার সিরিয়ালাইজেশন এবং ডিসিরিয়ালাইজেশনও সামলে নেয়।

আপনি যা শিখবেন

  • সার্ভিস এপিআই সংজ্ঞায়িত করতে প্রোটোকল বাফার কীভাবে ব্যবহার করবেন
  • স্বয়ংক্রিয় কোড জেনারেশন ব্যবহার করে প্রোটোকল বাফার ডেফিনিশন থেকে কীভাবে একটি gRPC-ভিত্তিক ক্লায়েন্ট তৈরি করা যায়।
  • gRPC ব্যবহার করে ক্লায়েন্ট-সার্ভার যোগাযোগ সম্পর্কে ধারণা।

এই কোডল্যাবটি সেইসব রাস্ট ডেভেলপারদের জন্য তৈরি করা হয়েছে যারা gRPC-তে নতুন অথবা এর বিষয়ে নিজেদের জ্ঞান ঝালিয়ে নিতে চান, কিংবা যারা ডিস্ট্রিবিউটেড সিস্টেম তৈরিতে আগ্রহী। gRPC-তে পূর্ব অভিজ্ঞতার কোনো প্রয়োজন নেই।

২. শুরু করার আগে

পূর্বশর্ত

নিশ্চিত করুন যে আপনি নিম্নলিখিতগুলি ইনস্টল করেছেন:

কোডটি নিন

যাতে আপনাকে একেবারে গোড়া থেকে শুরু করতে না হয়, সেজন্য এই কোডল্যাবটি অ্যাপ্লিকেশনটির সোর্স কোডের একটি কাঠামো প্রদান করে, যা আপনাকে সম্পূর্ণ করতে হবে। নিম্নলিখিত ধাপগুলো আপনাকে দেখাবে কীভাবে অ্যাপ্লিকেশনটি শেষ করতে হয়, যার মধ্যে প্রোটোকল বাফার কম্পাইলার প্লাগইন ব্যবহার করে বয়লারপ্লেট gRPC কোড তৈরি করার পদ্ধতিও অন্তর্ভুক্ত রয়েছে।

প্রথমে, কোডল্যাব ওয়ার্কিং ডিরেক্টরি তৈরি করুন এবং তার ভেতরে যান:

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

কোডল্যাবটি ডাউনলোড এবং এক্সট্র্যাক্ট করুন:

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

বিকল্পভাবে, আপনি শুধু কোডল্যাব ডিরেক্টরি সম্বলিত .zip ফাইলটি ডাউনলোড করে ম্যানুয়ালি আনজিপ করতে পারেন।

আপনি যদি ইমপ্লিমেন্টেশন টাইপ করা এড়াতে চান, তাহলে সম্পূর্ণ সোর্স কোডটি গিটহাবে পাওয়া যাবে

৩. পরিষেবাটি সংজ্ঞায়িত করুন

আপনার প্রথম পদক্ষেপ হলো প্রোটোকল বাফার ব্যবহার করে অ্যাপ্লিকেশনটির gRPC সার্ভিস, এর RPC মেথড এবং এর রিকোয়েস্ট ও রেসপন্স মেসেজ টাইপগুলো সংজ্ঞায়িত করা। আপনার সার্ভিসটি প্রদান করবে:

  • GetFeature নামক একটি RPC মেথড, যা সার্ভার ইমপ্লিমেন্ট করে এবং ক্লায়েন্ট কল করে।
  • GetFeature মেথড ব্যবহার করার সময় ক্লায়েন্ট এবং সার্ভারের মধ্যে Point এবং Feature নামক ডেটা স্ট্রাকচার আদান-প্রদান করা হয়। ক্লায়েন্ট তার GetFeature অনুরোধে সার্ভারে একটি Point হিসেবে মানচিত্রের স্থানাঙ্ক প্রদান করে এবং সার্ভার সেই স্থানাঙ্কে অবস্থিত যেকোনো কিছুর বর্ণনা দিয়ে একটি সংশ্লিষ্ট Feature পাঠিয়ে উত্তর দেয়।

এই RPC মেথড এবং এর মেসেজ টাইপগুলো প্রদত্ত সোর্স কোডের proto/routeguide.proto ফাইলে সংজ্ঞায়িত করা হবে।

প্রোটোকল বাফারগুলো সাধারণত প্রোটোবাফ নামে পরিচিত। gRPC পরিভাষা সম্পর্কে আরও তথ্যের জন্য, gRPC-এর মূল ধারণা, স্থাপত্য এবং জীবনচক্র দেখুন।

পরিষেবা পদ্ধতি

চলুন প্রথমে আমাদের সার্ভিস মেথডগুলো সংজ্ঞায়িত করি এবং তারপর আমাদের মেসেজ টাইপ PointFeature সংজ্ঞায়িত করি। proto/routeguide.proto ফাইলটিতে RouteGuide নামের একটি service স্ট্রাকচার রয়েছে, যা অ্যাপ্লিকেশনটির সার্ভিস দ্বারা প্রদত্ত এক বা একাধিক মেথড সংজ্ঞায়িত করে।

RouteGuide ডেফিনিশনের ভিতরে GetFeature rpc মেথডটি যোগ করুন। আগেই যেমন ব্যাখ্যা করা হয়েছে, এই মেথডটি প্রদত্ত স্থানাঙ্কের সেট থেকে কোনো অবস্থানের নাম বা ঠিকানা খুঁজে বের করবে, তাই GetFeature এমনভাবে তৈরি করুন যেন এটি প্রদত্ত Point জন্য একটি Feature রিটার্ন করে।

service RouteGuide {
  // Definition of the service goes here

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

এটি একটি ইউনারি আরপিসি মেথড: এটি একটি সাধারণ আরপিসি যেখানে ক্লায়েন্ট সার্ভারে একটি অনুরোধ পাঠায় এবং একটি লোকাল ফাংশন কলের মতোই প্রতিক্রিয়া ফিরে আসার জন্য অপেক্ষা করে।

বার্তার প্রকারভেদ

সোর্স কোডের proto/routeguide.proto ফাইলে, প্রথমে Point মেসেজ টাইপটি সংজ্ঞায়িত করুন। একটি Point মানচিত্রে একটি অক্ষাংশ-দ্রাঘিমাংশ স্থানাঙ্ক জোড়াকে বোঝায়। এই কোডল্যাবের জন্য, স্থানাঙ্ক হিসেবে পূর্ণসংখ্যা ব্যবহার করুন:

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

1 এবং 2 সংখ্যা দুটি হলো message কাঠামোর প্রতিটি ফিল্ডের অনন্য শনাক্তকরণ নম্বর।

এরপর, Feature মেসেজ টাইপটি সংজ্ঞায়িত করুন। একটি Feature , Point দ্বারা নির্দিষ্ট কোনো অবস্থানে থাকা কোনো কিছুর নাম বা ডাক ঠিকানার জন্য একটি string ফিল্ড ব্যবহার করে।

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

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

৪. ক্লায়েন্ট এবং সার্ভার কোড তৈরি করুন

আমরা ইতিমধ্যেই আপনাকে generated/ ডিরেক্টরিতে থাকা .proto ফাইল থেকে তৈরি করা কোডটি দিয়ে দিয়েছি, যার মধ্যে উপরে আপনার করা সমস্ত সংযোজনও অন্তর্ভুক্ত রয়েছে। তবে, কোড জেনারেশন প্রক্রিয়াটি কীভাবে কাজ করে, তা ব্যাখ্যা করার জন্য আমরা একটু সময় নিতে চাই।

আমাদের .proto ফাইলটি ক্লায়েন্ট বা সার্ভার দ্বারা ব্যবহৃত সমস্ত স্ট্রাকচার এবং ফাংশন বর্ণনা করে। এই কোডটি স্বয়ংক্রিয়ভাবে তৈরি করার জন্য আমরা grpc-protobuf-build ক্রেটের সাথে একটি কার্গো বিল্ড স্ক্রিপ্ট ( build.rs ) ব্যবহার করি।

Cargo.toml ফাইলে [build-dependencies] এর অধীনে আমরা grpc-protobuf-build যোগ করি।

build.rs ফাইলে, আমরা grpc_protobuf_build::CodeGen কনফিগার করি যাতে এটি proto/routeguide.proto ফাইলটিকে generated/ ডিরেক্টরিতে কম্পাইল করে। মূল লাইনগুলো এখানে দেওয়া হলো:

grpc_protobuf_build::CodeGen::new()
    .include("proto")
    .input("routeguide.proto")
    .output_dir("generated")
    .compile()
    .unwrap();

এটি ` routeguide.proto ফাইলটি পাস করে grpc_protobuf_build ক্রেটের কোড জেনারেশনকে কল করে। আমরা এটিকে এমন একটি কোডের মধ্যে রেখেছি যা শুধুমাত্র একটি `feature flag` পাস করা হলেই রান করবে, ফলে এটি কেবল তখনই রি-জেনারেট হবে যখন আপনি চাইবেন। আপনাকে এটি এখন রান করতে হবে না, কারণ আমরা আপনার জন্য কোডটি ইতোমধ্যেই জেনারেট করে দিয়েছি।

cargo build --bin routeguide-server --features regenerate_proto

যখন আপনি `cargo build` চালান, build.rs প্রোটোকল বাফার ডেফিনিশনগুলোকে generate/ ডিরেক্টরিতে কম্পাইল করে, যার মধ্যে অন্তর্ভুক্ত থাকে:

  • Point এবং Feature মেসেজ টাইপের জন্য স্ট্রাক্ট সংজ্ঞা।
  • সার্ভারের জন্য আমাদের একটি টনিক সার্ভিস ট্রেইট ইমপ্লিমেন্ট করতে হবে: route_guide_server::RouteGuide
  • সার্ভারকে কল করার জন্য আমরা যে gRPC-Rust ক্লায়েন্ট টাইপটি ব্যবহার করব তা হলো: route_guide_client::RouteGuideClient<T>

আরও তথ্যের জন্য আপনি protoc-gen-rust-grpc গাইডটি দেখতে পারেন।

এরপরে, আমরা সার্ভারে সার্ভিস মেথডগুলো ইমপ্লিমেন্ট করব।

৫. পরিষেবাটি বাস্তবায়ন করুন

src/server/server.rs ফাইলে, আমরা gRPC-এর include_generated_proto! ম্যাক্রো ব্যবহার করে জেনারেট করা কোডকে স্কোপের মধ্যে আনতে পারি এবং RouteGuide ট্রেইট ও Point ইম্পোর্ট করতে পারি।

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

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

আমরা আমাদের সার্ভিসকে উপস্থাপন করার জন্য একটি স্ট্রাক্ট সংজ্ঞায়িত করার মাধ্যমে শুরু করতে পারি, আপাতত আমরা এটি src/server/server.rs এ করতে পারি:

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

এখন, আমাদের জেনারেটেড কোড থেকে route_guide_server::RouteGuide ট্রেইটটি ইমপ্লিমেন্ট করতে হবে।

সরল একক আরপিসি

RouteGuideService আমাদের সমস্ত সার্ভিস মেথড ইমপ্লিমেন্ট করে। সার্ভার সাইডের get_feature ফাংশনটিতেই মূল কাজটি করা হয়: এটি ক্লায়েন্টের কাছ থেকে একটি Point মেসেজ গ্রহণ করে এবং পরিচিত স্থানগুলির একটি তালিকা থেকে সংশ্লিষ্ট অবস্থানের তথ্য একটি Feature মেসেজে ফেরত দেয়। src/server/server.rs ফাইলে ফাংশনটির ইমপ্লিমেন্টেশন নিচে দেওয়া হলো:

#[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()))
    }
}

এই মেথডটি ইমপ্লিমেন্ট করার পর, আমাদের একটি gRPC সার্ভারও চালু করতে হবে যাতে ক্লায়েন্টরা আমাদের সার্ভিসটি ব্যবহার করতে পারে। main() ফাংশনটিকে এটি দিয়ে প্রতিস্থাপন করুন।

#[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() ফাংশনে যা ঘটছে, তা ধাপে ধাপে নিচে দেওয়া হলো:

  1. ক্লায়েন্টের অনুরোধ শোনার জন্য আমরা যে পোর্টটি ব্যবহার করতে চাই তা নির্দিষ্ট করুন।
  2. load() হেল্পার ফাংশনটি কল করে ফিচারগুলো লোড করে একটি RouteGuideService তৈরি করুন।
  3. আমাদের তৈরি করা সার্ভিসটি ব্যবহার করে RouteGuideServer::new() এর মাধ্যমে gRPC সার্ভারের একটি ইনস্ট্যান্স তৈরি করুন।
  4. আমাদের পরিষেবা বাস্তবায়নটি gRPC সার্ভারে নিবন্ধন করুন।
  5. প্রসেসটি কিল না হওয়া পর্যন্ত ব্লকিং ওয়েট করার জন্য, আমাদের পোর্ট ডিটেইলস সহ সার্ভারে serve() কল করুন।

৬. ক্লায়েন্ট তৈরি করুন

এই অংশে, আমরা src/client/client.rs এ থাকা আমাদের RouteGuide সার্ভিসের জন্য একটি রাস্ট ক্লায়েন্ট তৈরি করা দেখব।

যেমনটি আমরা src/server/server.rs এ করেছিলাম, তেমনি gRPC-এর include_generated_proto! ম্যাক্রো ব্যবহার করে জেনারেট করা কোডকে স্কোপের মধ্যে আনতে পারি এবং RouteGuideClient টাইপটি ইম্পোর্ট করতে পারি।

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

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

কল পরিষেবা পদ্ধতি

gRPC-Rust-এ, RPC-গুলো অ্যাসিঙ্ক্রোনাস এবং নন-ব্লকিং হয় এবং সার্ভার থেকে প্রতিক্রিয়ার জন্য অপেক্ষা করতে Rust-এর async/await সিনট্যাক্স ব্যবহার করে।

সার্ভিস মেথড কল করার জন্য, আমরা প্রথমে Channel::builder() ব্যবহার করে একটি Channel তৈরি করি, যেখানে সার্ভার অ্যাড্রেস ( dns:///[::1]:10000 ) এবং কানেকশন ক্রেডেনশিয়াল ( LocalChannelCredentials ) উল্লেখ করতে হয়। এরপর আমরা আমাদের ক্লায়েন্ট ইনস্ট্যানশিয়েট করার জন্য চ্যানেলটিকে RouteGuideClient::new() তে পাস করি।

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new gRPC channel
    let channel = Channel::builder(
        "dns:///[::1]:10000",
        Arc::new(LocalChannelCredentials::new()),
    )
    .build();

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



}

এই ফাংশনে, RouteGuideClient::new() জেনেরিক চ্যানেলটিকে জেনারেটেড ক্লায়েন্ট স্টাবের সাথে বাইন্ড করে, যা আমাদের .proto সার্ভিস ডেফিনিশনে সংজ্ঞায়িত মেথডগুলো ইমপ্লিমেন্ট করে।

সরল আরপিসি

সাধারণ RPC GetFeature কল করা লোকাল মেথড কল করার মতোই সহজ। main() ফাংশনে এটি যোগ করুন:

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

gRPC-Rust-এ, আমরা ` Point প্রোটোবাফ মেসেজটি সরাসরি ` client.get_feature(point) -এ পাস করি। ফেরত আসা `future`-টির জন্য অ্যাওয়েট করলে, অতিরিক্ত কোনো মেথড কলের প্রয়োজন ছাড়াই সরাসরি Feature রেসপন্সটি পাওয়া যায়।

এরপর, রেসপন্স থেকে ফিল্ডগুলো প্রিন্ট করুন:

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

সব মিলিয়ে, ক্লায়েন্টের main() ফাংশনটি দেখতে এইরকম হবে:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create a new gRPC channel
    let channel = Channel::builder(
        "dns:///[::1]:10000",
        Arc::new(LocalChannelCredentials::new()),
    )
    .build();

    // 2. Instantiate the generated RouteGuideClient
    let client = RouteGuideClient::new(channel);

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

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

৭. এটি পরীক্ষা করে দেখুন

আপনার ক্লায়েন্ট এবং সার্ভার চালানোর জন্য, প্রথমে যাচাই করুন যে Cargo.toml ফাইলে উভয় বাইনারি টার্গেট সংজ্ঞায়িত করা আছে কিনা।

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

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

তারপর, আমাদের ওয়ার্কিং ডিরেক্টরি থেকে নিম্নলিখিত কমান্ডগুলো চালান:

  1. একটি টার্মিনালে সার্ভারটি চালান:
cargo run --bin routeguide-server
  1. অন্য একটি টার্মিনাল থেকে ক্লায়েন্টটি চালান:
cargo run --bin routeguide-client

আপনি এইরকম আউটপুট দেখতে পাবেন, স্পষ্টতার জন্য টাইমস্ট্যাম্পগুলো বাদ দেওয়া হয়েছে:

*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906

৮. এরপর কী?

৯. এই কোডল্যাবের অবদানকারীগণ

  • ক্যাথি ঝাও
  • লুসিও ফ্রাঙ্কো
  • অরবিন্দ ব্রাইট
  • নাথানিয়েল ফোর্ড