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

1. ভূমিকা

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

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

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

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

আপনি কি শিখবেন

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

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

2. আপনি শুরু করার আগে

পূর্বশর্ত

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

  • জিসিসি। এখানে নির্দেশাবলী অনুসরণ করুন
  • মরিচা , সংস্করণ 1.89.0। এখানে ইনস্টলেশন নির্দেশাবলী অনুসরণ করুন.

কোড পান

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

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

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

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

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

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

আপনি যদি বাস্তবায়নে টাইপ করা এড়িয়ে যেতে চান তবে সম্পূর্ণ সোর্স কোডটি GitHub-এ উপলব্ধ

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

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

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

এই RPC পদ্ধতি এবং এর মেসেজের ধরন সবই প্রদত্ত সোর্স কোডের proto/route_guide.proto ফাইলে সংজ্ঞায়িত করা হবে।

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

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

আসুন প্রথমে আমাদের পরিষেবা পদ্ধতি সংজ্ঞায়িত করি এবং তারপরে আমাদের বার্তার ধরন Point এবং Feature সংজ্ঞায়িত করি। proto/routeguide.proto ফাইলটিতে RouteGuide নামে একটি service কাঠামো রয়েছে যা অ্যাপ্লিকেশনটির পরিষেবা দ্বারা প্রদত্ত এক বা একাধিক পদ্ধতিকে সংজ্ঞায়িত করে৷

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

service RouteGuide {
  // Definition of the service goes here

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

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

বার্তার ধরন

সোর্স কোডের proto/route_guide.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;
}

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

আমরা আপনাকে ইতিমধ্যেই জেনারেটেড ডিরেক্টরিতে .proto ফাইল থেকে জেনারেট করা কোড দিয়েছি।

যে কোনো প্রকল্পের মতো, আমাদের কোডের জন্য প্রয়োজনীয় নির্ভরতা সম্পর্কে আমাদের ভাবতে হবে। মরিচা প্রকল্পগুলির জন্য, নির্ভরতাগুলি হবে Cargo.toml এ। আমরা ইতিমধ্যেই Cargo.toml ফাইলে প্রয়োজনীয় নির্ভরতা তালিকাভুক্ত করেছি।

আপনি যদি নিজেই .proto ফাইল থেকে কোড তৈরি করতে শিখতে চান তবে এই নির্দেশাবলী পড়ুন।

উত্পন্ন কোড রয়েছে:

  • বার্তার ধরন Point এবং Feature জন্য কাঠামোগত সংজ্ঞা।
  • একটি পরিষেবা বৈশিষ্ট্য যা আমাদের বাস্তবায়ন করতে হবে: route_guide_server::RouteGuide
  • একটি ক্লায়েন্ট প্রকার যা আমরা সার্ভারকে কল করতে ব্যবহার করব: route_guide_client::RouteGuideClient<T>

এর পরে, আমরা সার্ভার-সাইডে GetFeature পদ্ধতি প্রয়োগ করব, যাতে ক্লায়েন্ট যখন একটি অনুরোধ পাঠায়, সার্ভার একটি উত্তর দিয়ে উত্তর দিতে পারে।

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

src/server/server.rs include_generated_proto! ম্যাক্রো এবং RouteGuide বৈশিষ্ট্য এবং Point আমদানি করুন।

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

pub 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()))
    }
}

পদ্ধতিতে, প্রদত্ত Point জন্য উপযুক্ত তথ্য সহ একটি Feature অবজেক্ট পপুলেট করুন এবং তারপরে এটি ফেরত দিন।

একবার আমরা এই পদ্ধতিটি প্রয়োগ করার পরে, আমাদের একটি 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() কল করুন

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

এই বিভাগে, আমরা src/client/client.rs এ আমাদের RouteGuide পরিষেবার জন্য একটি মরিচা ক্লায়েন্ট তৈরি করার বিষয়ে দেখব।

যেমনটি আমরা src/server/server.rs এ করেছি, আমরা জেনারেট করা কোডটিকে জিআরপিসি-এর include_generated_code! ম্যাক্রো এবং RouteGuideClient প্রকার আমদানি করুন।

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

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

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

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

পরিষেবা পদ্ধতি কল করার জন্য, আমাদের প্রথমে সার্ভারের সাথে যোগাযোগের জন্য একটি চ্যানেল তৈরি করতে হবে। আমরা প্রথমে একটি এন্ডপয়েন্ট তৈরি করে, সেই এন্ডপয়েন্টের সাথে সংযোগ স্থাপন করে এবং RouteGuideClient::new() এর সাথে সংযুক্ত থাকাকালীন চ্যানেলটি পাস করে এটি তৈরি করি:

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

এই ফাংশনে, যখন আমরা ক্লায়েন্ট তৈরি করি, আমরা উপরে তৈরি করা জেনেরিক চ্যানেলটিকে জেনারেট করা কোড স্টাব দিয়ে মুড়ে দিই যা .proto পরিষেবাতে সংজ্ঞায়িত নির্দিষ্ট পদ্ধতি প্রয়োগ করে।

সরল আরপিসি

সাধারণ RPC GetFeature কল করা স্থানীয় পদ্ধতিতে কল করার মতোই সহজ। এটি main() এ যোগ করুন।

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(())

আপনি দেখতে পাচ্ছেন, আমরা আগে যে স্টাব পেয়েছিলাম তার পদ্ধতিটিকে আমরা কল করি। আমাদের পদ্ধতির পরামিতিগুলিতে আমরা একটি অনুরোধ প্রোটোকল বাফার অবজেক্ট তৈরি এবং পপুলেট করি (আমাদের ক্ষেত্রে Point )। যদি কলটি একটি ত্রুটি ফেরত না দেয়, তাহলে আমরা প্রথম রিটার্ন মান থেকে সার্ভার থেকে প্রতিক্রিয়া তথ্য পড়তে পারি।

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>> {
    //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. এটা চেষ্টা করে দেখুন

প্রথমে, আমাদের ক্লায়েন্ট এবং সার্ভার চালানোর জন্য, আমাদের ক্রেটে বাইনারি টার্গেট হিসাবে তাদের যোগ করা যাক। সেই অনুযায়ী আমাদের Cargo.toml সম্পাদনা করতে হবে এবং নিম্নলিখিত যোগ করতে হবে:

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

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

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

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

স্পষ্টতার জন্য টাইমস্ট্যাম্প বাদ দিয়ে আপনি এইরকম আউটপুট দেখতে পাবেন:

*** SIMPLE RPC ***

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

8. পরবর্তী কি