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

১. ভূমিকা

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

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

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

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

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

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

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

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

পূর্বশর্ত

  • JDK সংস্করণ ৮ বা তার উচ্চতর

কোডটি নিন

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

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

mkdir grpc-java-getting-started && cd grpc-java-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-java-getting-started/start_here

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

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

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

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

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

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

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

যেহেতু আমরা এই উদাহরণে জাভা কোড তৈরি করছি, তাই আমরা আমাদের .proto ফাইলে একটি java_package ফাইল অপশন এবং জাভা ক্লাসের জন্য একটি নাম নির্দিষ্ট করে দিয়েছি:

option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";

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

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

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

route_guide.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 সার্ভিস ডেফিনিশন থেকে gRPC ক্লায়েন্ট এবং সার্ভার ইন্টারফেস তৈরি করতে হবে। আমরা একটি বিশেষ gRPC জাভা প্লাগইন সহ প্রোটোকল বাফার কম্পাইলার protoc ব্যবহার করে এটি করে থাকি। gRPC সার্ভিস তৈরি করার জন্য আপনাকে proto3 কম্পাইলার ব্যবহার করতে হবে (যা proto2 এবং proto3 উভয় সিনট্যাক্সই সমর্থন করে)।

Gradle বা Maven ব্যবহার করার সময়, protoc বিল্ড প্লাগইনটি বিল্ডের অংশ হিসেবে প্রয়োজনীয় কোড তৈরি করতে পারে। আপনার নিজের .proto ফাইলগুলো থেকে কীভাবে কোড তৈরি করবেন, তা জানতে আপনি grpc-java README দেখতে পারেন।

এই প্রজেক্টটি বিল্ড করার জন্য আমরা কোডল্যাবের সোর্স কোডে একটি গ্রেডল এনভায়রনমেন্ট ও কনফিগারেশন প্রদান করেছি।

grpc-java-getting-started ডিরেক্টরির ভিতরে, নিম্নলিখিত কমান্ডটি চালান:

$ chmod +x gradlew
$ ./gradlew generateProto

আমাদের সার্ভিস ডেফিনিশন থেকে নিম্নলিখিত ক্লাসগুলো তৈরি করা হয়:

  • Feature.java , Point.java এবং অন্যান্য ফাইলগুলোতে আমাদের অনুরোধ এবং প্রতিক্রিয়া বার্তার প্রকারগুলো পূরণ, ক্রমিকীকরণ এবং পুনরুদ্ধার করার জন্য সমস্ত প্রোটোকল বাফার কোড থাকে।
  • RouteGuideGrpc.java ফাইলে (অন্যান্য কিছু দরকারি কোডের সাথে) RouteGuide সার্ভারগুলোর ইমপ্লিমেন্ট করার জন্য একটি বেস ক্লাস, RouteGuideGrpc.RouteGuideImplBase , রয়েছে, যেখানে ক্লায়েন্টদের ব্যবহারের জন্য RouteGuide সার্ভিস এবং স্টাব ক্লাসগুলোতে সংজ্ঞায়িত সমস্ত মেথড অন্তর্ভুক্ত আছে।

৫. সার্ভারটি বাস্তবায়ন করুন

প্রথমে দেখা যাক আমরা কীভাবে একটি RouteGuide সার্ভার তৈরি করি। আমাদের RouteGuide সার্ভিসকে তার কাজ করানোর জন্য দুটি অংশ রয়েছে:

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

রুটগাইড বাস্তবায়ন করুন

যেমনটি আপনি দেখতে পাচ্ছেন, আমাদের সার্ভারে একটি RouteGuideService ক্লাস আছে যা জেনারেট করা RouteGuideGrpc.RouteGuideImplBase অ্যাবস্ট্রাক্ট ক্লাসটিকে এক্সটেন্ড করে:

private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}

আপনার সার্ভারটি বিভিন্ন বৈশিষ্ট্যসহ চালু করার জন্য আমরা নিম্নলিখিত ২টি ফাইল সরবরাহ করেছি:

./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java

./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json

চলুন একটি সাধারণ RPC বাস্তবায়ন বিস্তারিতভাবে দেখে নেওয়া যাক।

একক আরপিসি

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

@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
  responseObserver.onNext(checkFeature(request));
  responseObserver.onCompleted();
}

getFeature() মেথডটি দুটি প্যারামিটার গ্রহণ করে:

  • Point : অনুরোধটি।
  • StreamObserver<Feature> : একটি রেসপন্স অবজারভার, যা সার্ভারের রেসপন্স জানানোর জন্য কল করার একটি বিশেষ ইন্টারফেস।

ক্লায়েন্টকে আমাদের প্রতিক্রিয়া ফেরত দিতে এবং কলটি শেষ করতে:

  1. আমাদের সার্ভিস ডেফিনিশনে যেমন উল্লেখ করা আছে, সেই অনুযায়ী আমরা ক্লায়েন্টকে ফেরত পাঠানোর জন্য একটি Feature রেসপন্স অবজেক্ট তৈরি ও তাতে ডেটা যুক্ত করি। এই উদাহরণে, আমরা এই কাজটি একটি আলাদা প্রাইভেট checkFeature() মেথডের মধ্যে করি।
  2. আমরা রেসপন্স অবজারভারের onNext() মেথড ব্যবহার করে Feature টি রিটার্ন করি।
  3. আমরা যে RPC-এর কাজ শেষ করেছি, তা নির্দিষ্ট করার জন্য রেসপন্স অবজারভারের onCompleted() মেথডটি ব্যবহার করি।

সার্ভার চালু করুন

আমাদের সমস্ত সার্ভিস মেথড ইমপ্লিমেন্ট করার পরে, আমাদের একটি gRPC সার্ভার চালু করতে হবে যাতে ক্লায়েন্টরা আমাদের সার্ভিসটি ব্যবহার করতে পারে। আমরা আমাদের বয়লারপ্লেটে ServerBuilder অবজেক্টটি তৈরি করার বিষয়টি অন্তর্ভুক্ত করি:

ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)

আমরা কনস্ট্রাক্টরে সার্ভিসটি তৈরি করি:

  1. বিল্ডারের forPort() মেথড ব্যবহার করে ক্লায়েন্ট অনুরোধ শোনার জন্য আমরা যে পোর্টটি ব্যবহার করতে চাই তা নির্দিষ্ট করুন (এটি ওয়াইল্ডকার্ড অ্যাড্রেস ব্যবহার করবে)।
  2. আমাদের সার্ভিস ইমপ্লিমেন্টেশন ক্লাস RouteGuideService এর একটি ইনস্ট্যান্স তৈরি করুন এবং সেটিকে বিল্ডারের addService() মেথডে পাস করুন।
  3. আমাদের সার্ভিসের জন্য একটি আরপিসি সার্ভার তৈরি করতে বিল্ডারে build() কল করুন।

নিচের কোড স্নিপেটটিতে দেখানো হয়েছে কিভাবে একটি ServerBuilder অবজেক্ট তৈরি করতে হয়।

/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
    this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
        port, RouteGuideUtil.parseFeatures(featureFile));
  }

নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে কিভাবে আমরা আমাদের RouteGuide সার্ভিসের জন্য একটি সার্ভার অবজেক্ট তৈরি করি।

/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
  this.port = port;
  server = serverBuilder.addService(new RouteGuideService(features))
      .build();
}

একটি start মেথড ইমপ্লিমেন্ট করুন যা উপরে তৈরি করা সার্ভারটির start কল করে।

public void start() throws IOException {
  server.start();
  logger.info("Server started, listening on " + port);
}

সার্ভারের কাজ শেষ হওয়া পর্যন্ত অপেক্ষা করার জন্য একটি পদ্ধতি প্রয়োগ করুন, যাতে এটি সাথে সাথে বন্ধ না হয়ে যায়।

/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
  if (server != null) {
    server.awaitTermination();
  }
}

যেমনটি দেখতে পাচ্ছেন, আমরা একটি ServerBuilder ব্যবহার করে আমাদের সার্ভার তৈরি ও চালু করি।

প্রধান পদ্ধতিতে আমরা:

  1. একটি RouteGuideServer ইনস্ট্যান্স তৈরি করুন।
  2. আমাদের সার্ভিসের জন্য একটি আরপিসি সার্ভার সক্রিয় করতে start() কল করুন।
  3. blockUntilShutdown() কল করে সার্ভিসটি বন্ধ হওয়া পর্যন্ত অপেক্ষা করুন।
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

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

এই অংশে, আমরা আমাদের RouteGuide সার্ভিসের জন্য একটি ক্লায়েন্ট তৈরি করার পদ্ধতি দেখব।

একটি স্টাব ইনস্ট্যানশিয়েট করুন

সার্ভিস মেথড কল করার জন্য, আমাদের প্রথমে একটি স্টাব তৈরি করতে হবে। স্টাব দুই প্রকারের হয়, কিন্তু এই কোডল্যাবের জন্য আমাদের শুধু ব্লকিং স্টাবটি ব্যবহার করতে হবে। এই দুই প্রকার হলো:

  • একটি ব্লকিং/সিঙ্ক্রোনাস স্টাব যা একটি RPC কল করে এবং সার্ভারের প্রতিক্রিয়ার জন্য অপেক্ষা করে, এবং হয় একটি প্রতিক্রিয়া ফেরত দেয় অথবা একটি এক্সেপশন তৈরি করে।
  • একটি নন-ব্লকিং/অ্যাসিঙ্ক্রোনাস স্টাব যা সার্ভারে নন-ব্লকিং কল করে, যেখানে প্রতিক্রিয়াটি অ্যাসিঙ্ক্রোনাসভাবে ফেরত আসে। আপনি শুধুমাত্র অ্যাসিঙ্ক্রোনাস স্টাব ব্যবহার করে নির্দিষ্ট ধরণের স্ট্রিমিং কল করতে পারেন।

প্রথমে আমাদের একটি gRPC চ্যানেল তৈরি করতে হবে এবং তারপর সেই চ্যানেলটি ব্যবহার করে আমাদের স্টাব তৈরি করতে হবে।

আমরা সরাসরি ManagedChannelBuilder ব্যবহার করে চ্যানেলটি তৈরি করতে পারতাম।

ManagedChannelBuilder.forAddress(host, port).usePlaintext().build

কিন্তু চলুন এমন একটি ইউটিলিটি মেথড ব্যবহার করি যা hostname:port সহ একটি স্ট্রিং গ্রহণ করে।

Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();

এখন আমরা চ্যানেলটি ব্যবহার করে আমাদের ব্লকিং স্টাব তৈরি করতে পারি। এই কোডল্যাবের জন্য, আমাদের কেবল ব্লকিং RPC রয়েছে, তাই আমরা আমাদের .proto থেকে জেনারেট করা RouteGuideGrpc ক্লাসে দেওয়া newBlockingStub মেথডটি ব্যবহার করব।

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

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

এবার দেখা যাক আমরা আমাদের সার্ভিস মেথডগুলোকে কীভাবে কল করি।

সরল আরপিসি

সাধারণ RPC GetFeature কল করা প্রায় একটি লোকাল মেথড কল করার মতোই সহজ।

আমরা একটি রিকোয়েস্ট প্রোটোকল বাফার অবজেক্ট (আমাদের ক্ষেত্রে Point ) তৈরি ও তাতে ডেটা যুক্ত করি, সেটিকে আমাদের ব্লকিং স্টাবের getFeature() মেথডে পাঠাই এবং একটি Feature ফেরত পাই।

যদি কোনো ত্রুটি ঘটে, তবে তা একটি Status হিসেবে এনকোড করা হয়, যা আমরা StatusRuntimeException থেকে পেতে পারি।

Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();

Feature feature;
try {
  feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
  logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
  return;
}

নির্দিষ্ট স্থানে কোনো ফিচার ছিল কি না, তার ওপর ভিত্তি করে বয়লারপ্লেটটি একটি বার্তা লগ করে।

৭. চেষ্টা করে দেখুন!

  1. start_here ডিরেক্টরির ভিতরে, নিম্নলিখিত কমান্ডটি চালান:
$ ./gradlew installDist

এটি আপনার কোড কম্পাইল করবে, সেটিকে একটি জার ফাইলে প্যাকেজ করবে এবং উদাহরণটি চালানোর জন্য স্ক্রিপ্টগুলো তৈরি করবে। স্ক্রিপ্টগুলো build/install/start_here/bin/ ডিরেক্টরিতে তৈরি হবে। স্ক্রিপ্টগুলো হলো: route-guide-server এবং route-guide-client

ক্লায়েন্ট চালু করার আগে সার্ভারটি চালু থাকতে হবে।

  1. সার্ভারটি চালান:
$ ./build/install/start_here/bin/route-guide-server
  1. ক্লায়েন্টটি চালান:
$ ./build/install/start_here/bin/route-guide-client

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

INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906
INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619
INFO: *** GetFeature: lat=0 lon=0
INFO: Found no feature at 0, 0

৮. এরপর কী?