কম্পিউটার টিউটোরিয়াল

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

সার্ভারলেস ফাংশন ক্লাউড সার্ভিস ডেভেলপ এবং ডেপ্লয় করার একটি নতুন প্রোগ্রামিং প্যারাডাইম। একটি সার্ভারলেস বিশ্বে, আমরা আমাদের ব্যাকএন্ড সার্ভিসের প্রোভিশনিং, মেইনটেনেন্স, এবং স্কেলিং ক্লাউড প্রোভাইডারের ওপর অ্যাবস্ট্রাক্ট করি। এটি ডেভেলপার প্রোডাক্টিভিটি önemliভাবে উন্নত করে ডেভেলপারদের একটি নির্দিষ্ট সমস্যা সমাধান করতে ফোকাস করতে দিয়ে। সার্ভারলেস ফাংশন বিল্ড করার অনেক সুবিধা এবং অসুবিধা থাকলেও, একটি বিষয় বিবেচনা করতে হবে সেটি হলো ল্যাঙ্গুয়েজ সাপোর্ট। gầnে, Google Cloud Functions-এ Ruby 2.7 সাপোর্ট ঘোষণা করেছে Google, এবং এই আর্টিকেলে আমি ফোকাস করব Google Cloud Functions-এ Ruby-তে সার্ভারলেস ফাংশন বিল্ড, টেস্ট, এবং ডেপ্লয় করতে এবং সার্ভারলেস ফাংশনগুলোর সুবিধা-অসুবিধা।

একটি সার্ভারলেস OTP সিস্টেম বিল্ড করা

ওয়ান-টাইম পাসওয়ার্ড (OTP) হলো ছোট Нуমেরিক কোড যা অথেনটিকেশন উদ্দেশ্যে ব্যবহার হয়, যেমন আপনার ব্যাংক আপনার পরিচয় ভেরিফাই করতে OTP ভিয়া টেক্সট পাঠায়।

এই আর্টিকেলে, আমরা একটি OTP ফাংশন বিল্ড করব যা তিনটি কোর রেসপন্সিবিলিটি হ্যান্ডেল করে:

POST /otp: প্রদত্ত phone_number-এ OTP মেসেজ জেনারেট করে এবং পাঠায়।

# Request
{ "phone_number": "+2347012345678" }
# Response
{ "status": true, "message": "OTP sent successfully", "data": { "phone_number": "+2347012345678", "otp": 6872, "expires_at": "2021-02-09 07:15:25 +0100" } }

PUT /otp/verify: ইউজার প্রদত্ত OTP-এর বিপরীতে OTP ভেরিফাই করে।

# Request
{ "phone_number": "+2347012345678", "otp": 7116 }
# Response
{ "status": true, "message": "OTP verified", "data": {} }

PUT /otp/resend: প্রদত্ত phone_number-এ OTP জেনারেট করে এবং পুনরায় পাঠাতে চেষ্টা করে।

# Request
{ "phone_number": "+2347012345678" }
# Response
{ "status": true, "message": "OTP sent successfully", "data": { "phone_number": "+2347012345678", "otp": 8533, "expires_at": "2021-02-09 08:59:16 +0100" } }

সিম্পলিসিটির জন্য, আমাদের ক্লাউড ফাংশনটি Cloud MemoryStore (GCP-এ Redis বা Memcache) দিয়ে ব্যাকড থাকবে একটি ফুল SQL বা NoSQL ডেটাবেসের বদলে। এটি আমাদেরকে স্টেটলেস এনভায়রনমেন্টে শেয়ার্ড স্টেট সম্পর্কে শিখতে সক্ষম করব।

Ruby-তে Google Cloud Function লিখুন

GCF-এ ফাংশন লিখতে, আমরা Google Cloud টিম দ্বারা প্রদত্ত Functions Framework-এর উপর নির্ভর করব GCF ফাংশন বিল্ড করার জন্য (এর সম্পর্কে পরে আরো)।

প্রথম, অ্যাপ ডিরেক্টরি তৈরি করুন এবং ডিরেক্টরিতে প্রবেশ করুন।

mkdir otp-cloud-function && cd otp-cloud-function

এরপর, আপনার Gemfile তৈরি করুন এবং ইনস্টল করুন।

বেশিরভাগ স্ট্যান্ডার্ড Ruby অ্যাপ্লিকেশনের মত, আমরা bundler ব্যবহার করব আমাদের ফাংশনের ডিপেন্ডেন্সি ম্যানেজ করতে।

source "https://rubygems.org"
# Core
gem "functions_framework", "~> 0.7"
# Twilio for SMS
gem 'twilio-ruby', '~> 5.43.0'
# Database
gem 'redis'
# Connection Pooling
gem 'connection_pool'
# Time management
gem 'activesupport'
# API Serialization
gem 'active_model_serializers', '~> 0.10.0'

group :development, :test do
  gem 'pry'
  gem 'rspec'
  gem 'rspec_junit_formatter'
  gem 'faker', '~> 2.11.0'
end
bundle install

ফাংশন তৈরি করা

সাধারণত, বিভিন্ন হোস্টিং এনভায়রনমেন্ট আপনাকে আলাদা ফাইল স্পেসিফাই করতে দেয় যেখানে আপনার ফাংশনগুলো লেখা থাকে। Google Cloud Functions, তবে, এটি app.rb হতে বাধ্য করে আপনার প্রজেক্ট ডিরেক্টরির রুটে। এখন, আমরা আমাদের ফাংশন লিখতে প্রস্তুত।

app.rb খুলুন এবং ফাংশন তৈরি করুন:

# Cloud Functions Entrypoint
require 'functions_framework'
require 'connection_pool'
require 'active_model_serializers'
require './lib/store'
require './lib/send_sms_notification'
require './lib/response'
require './lib/serializers/models/base_model'
require './lib/serializers/models/otp_response'
require './lib/serializers/application_serializer'
require './lib/serializers/base_model_serializer'
require './lib/serializers/otp_response_serializer'

FunctionsFramework.on_startup do |function|
  # Setup Shared Redis Client
  require 'redis'
  set_global :redis_client, ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
end

# Define HTTP Function
FunctionsFramework.http "otp" do |request|
  store = Store.new(global(:redis_client))
  data = JSON.parse(request.body.read)
  if request.post? && request.path == '/otp'
    phone_number = data['phone_number']
    record = store.get(phone_number)
    unless record.nil? || record.expired?
      data = Models::OtpResponse.new(phone_number: phone_number, otp: record['otp'], expires_at: record['expires_at'])
      json = Response.generate_json(status: true, message: 'OTP previously sent', data: data)
      return json
    end
    otp = rand(1111..9999)
    record = store.set(phone_number, otp)
    SendSmsNotification.new(phone_number, otp).call
    data = Models::OtpResponse.new(phone_number: phone_number, otp: record['otp'], expires_at: record['expires_at'])
    Response.generate_json(status: true, message: 'OTP sent successfully', data: data)
  elsif request.put? && request.path == '/otp/verify'
    phone_number = data['phone_number']
    record = store.get(phone_number)
    if record.nil?
      return Response.generate_json(status: false, message: "OTP not sent to number")
    elsif record.expired?
      return Response.generate_json(status: false, message: 'OTP code expired')
    end
    is_verified = data['otp'] == record['otp']
    if is_verified
      return Response.generate_json(status: true, message: 'OTP verified')
    else
      return Response.generate_json(status: false, message: 'OTP does not match')
    end
  elsif request.put? && request.path == '/otp/resend'
    phone_number = data['phone_number']
    store.del(phone_number)
    otp = rand(1111..9999)
    record = store.set(phone_number, otp)
    SendSmsNotification.new(phone_number, otp).call
    data = Models::OtpResponse.new(phone_number: phone_number, otp: record['otp'], expires_at: record['expires_at'])
    json = Response.generate_json(status: true, message: 'OTP sent successfully', data: data)
  else
    Response.generate_json(status: false, message: 'Request method and path did not match')
  end
end

এটি অনেক কোড, তাই আমরা এটিকে ব্রেক ডাউন করব:

  • Functions_Framework.on_startup হলো একটি কোড ব্লক যা প্রতি Ruby ইনস্ট্যান্সে রান হয় ফাংশনগুলো রিকোয়েস্ট প্রসেস করা শুরু করার আগে। এটি আমাদের ফাংশনগুলো কল হওয়ার আগে যেকোনো টাইপের ইনিশিয়ালাইজেশন রান করার জন্য আইডিয়াল। এই কেসে, আমি এটি ব্যবহার করছি আমাদের Redis সার্ভারের সাথে একটি কানেকশন পুল তৈরি এবং শেয়ার করতে:

    set_global :redis_client, ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
    এটি আমাদের একাধিক কনকারেন্ট ফাংশন ইনভোকেশন জুড়ে Redis কানেকশন অবজেক্টের একটি পুল শেয়ার করতে সক্ষম করে কোনো ভয় ছাড়া। একাধিক স্টার্টআপ ডিফাইন করা যাবে। সেগুলো ডিফাইন হওয়ার অর্ডারে রান করবে। এটি নোট করাও গুরুত্বপূর্ণ যে Functions Framework ফাংশন কমপ্লিশনের পর রান করার জন্য কোনো স্পেশাল হুক প্রদান করে না।

  • Functions_Framework.http 'otp' do |request| আমাদের ফাংশনগুলোর রিকোয়েস্ট এবং রেসপন্স প্রসেসিং হ্যান্ডেল করে। এই ফাংশনটি তিনটি ভিন্ন রাউট প্যাটার্ন সাপোর্ট করে। অন্যান্য টাইপের ফাংশন ডিফাইন করা সম্ভব (যেমন, Functions_Framework.cloud_event 'otp' do |event|) যা অন্যান্য Google সার্ভিস থেকে ইভেন্ট হ্যান্ডেল করে। একই ফাইলে একাধিক ফাংশন ডিফাইন করা সম্ভব কিন্তু স্বাধীনভাবে ডেপ্লয় করা।

  • store = Store.new(global(:redis_client))-এ, global মেথডটি গ্লোবাল শেয়ার্ড স্টেটে স্টোর করা যেকোনো অবজেক্ট রিট্রিভ করতে ব্যবহৃত হয়। উপরে যেমন ব্যবহার করা হয়েছে, আমরা আমাদের startup ব্লকে গ্লোবাল সেটআপে ডিফাইন করা কানেকশন পুল থেকে Redis ক্লায়েন্ট রিট্রিভ করছি।

  • Response এবং Models::OtpResponse active_model_serializers দিয়ে রেসপন্স সিরিয়ালাইজেশন হ্যান্ডেল করে যাতে প্রপারলি ফরম্যাট করা JSON রেসপন্স मिलে।

আমাদের ফাংশন লোকালি টেস্ট করা

Functions Framework লাইব্রেরি আমাদেরকে আমাদের ফাংশনগুলো লোকালি সহজে টেস্ট করতে সক্ষম করে ডেপ্লয় করার আগে ক্লাউডে। লোকালি টেস্ট করতে, আমরা রান করি

bundle exec functions-framework-ruby --target=otp --port=3000

--target ব্যবহার হয় ফাংশনটি সিলেক্ট করতে ডেপ্লয় করার জন্য।

ম্যানুয়াল টেস্টিং চমৎকার হলেও, অটোমেটেড টেস্টস এবং সেলফ-টেস্টিং সফ্টওয়ার টেস্টিং-এ হোলি গ্রেইল। Functions Framework হেল্পার মেথড প্রদান করে Minitest এবং RSpec দুটোর জন্যই যাতে আমরা আমাদের ফাংশনগুলো টেস্ট করতে পারি http এবং cloudevents হ্যান্ডলার দুটোর জন্যই। এখানে একটি টেস্ট উদাহরণ:

require './spec/spec_helper.rb'
require 'functions_framework/testing'
describe 'OTP Functions' do
  include FunctionsFramework::Testing
  describe 'Send OTP', redis: true do
    let(:phone_number) { "+2347012345678" }
    let(:body) { { phone_number: phone_number }.to_json }
    let(:headers) { ["Content-Type: application/json"] }
    it 'should send OTP successfully' do
      load_temporary "app.rb" do
        request = make_post_request "/otp", body, headers
        response = call_http "otp", request
        expect(response.status).to eq 200
        expect(response.content_type).to eq("application/json")
        parsed_response = JSON.parse(response.body.join)
        expect(parsed_response['status']).to eq true
        expect(parsed_response['message']).to eq 'OTP sent successfully'
      end
    end
  end
end

আমাদের ফাংশন ডেপ্লয় করা

প্রথমে, আমাদের Google Cloud Memorystore ব্যবহার করে একটি Redis সার্ভার ডেপ্লয় করতে হবে, যাতে আমাদের ফাংশনটি ডিপেন্ডেন্ট। আমি এখানে আরও ডিটেইলে যাবো না GCP-এ Redis সার্ভার ডেপ্লয় করার ऊपर, কারণ এটি এই আর্টিকেলের স্কোপের বাইরে।

আমাদের ফাংশনটি Google Cloud Function-এর এনভায়রনমেন্টে ডেপ্লয় করার একাধিক উপায় আছে: আপনার মেশিন থেকে ডেপ্লয়, GCP কনসোল থেকে ডেপ্লয়, এবং আমাদের কোড রিপোজিটরি থেকে ডেপ্লয়। মডার্ন সফ্টওয়্যার ইঞ্জিনিয়ারিং আমাদের অধিকাংশ ডেভেলপমেন্টের জন্য CI/CD প্রসেস এনকourage করে, এবং এই আর্টিকেলের উদ্দেশ্যে, আমি ফোকাস করব GitHub থেকে GitHub Actions ব্যবহার করে deploy-cloud-functions দিয়ে আমাদের Cloud Function ডেপ্লয় করার ওপর।

আমাদের ডেপ্লয়মেন্ট ফাইল সেট আপ করি (.github/workflows/deploy.yml)।

name: Deployment
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Function Deployment
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - id: deploy
        uses: google-github-actions/deploy-cloud-functions@main
        with:
          name: otp-cloud-function
          runtime: ruby26
          credentials: ${{ secrets.gcp_credentials }}
          env_vars: "TWILIO_ACCOUNT_SID=${{ secrets.TWILIO_ACCOUNT_SID }},TWILIO_AUTH_TOKEN=${{ secrets.TWILIO_AUTH_TOKEN }},TWILIO_PHONE_NUMBER=${{ secrets.TWILIO_PHONE_NUMBER }},REDIS_URL=${{ secrets.REDIS_URL }}"

এনভায়রনমেন্ট ভেরিয়েবলস

উপরোক্ত কোডে, লাস্ট লাইন আমাদের এনভায়রনমেন্ট ভেরিয়েবলস স্পেসিফাই করতে দেয় যা আমাদের ফাংশনে Google Cloud এনভায়রনমেন্টে উপলব্ধ থাকবে। নোট করুন যে সিকিউরিটি কারণে, আমরা এই ভেরিয়েবলস আমাদের কোডবেসে এক্সপোজ করছি না; বরং, আমরা GitHub Actions secrets ব্যবহার করছি এই ইনফরমেশন প্রাইভেট রাখতে। আমাদের টোকেনগুলো প্রপারলি ডেপ্লয় হয়েছে কিনা চেক করতে, আপনার ক্লাউড ফাংশন Google Console-এ চেক করুন, নিচে দেখানো মত:

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

অথেনটিকেশন

Cloud Functions Admin এবং Service Account User রোলসহ একটি Service Account তৈরি করুন।

একটি সার্ভিস একাউন্ট মেশিন-টু-মেশিন IAM-এর জন্য ব্যবহৃত হয়। অতএব, যখন একটি সিস্টেম, যেহেতু এটি Google Cloud-এ রান হয় বা না হয়, Google Cloud-এ অন্য একটি সিস্টেমের সাথে কথা বলে, একটি সার্ভিস একাউন্ট দরকার আমাদের Google রিসোর্সে কে এক্সেস রিকোয়েস্ট করছে সেটি আইডেন্টিফাই করতে। Cloud Functions Admin এবং Service Account User রোলস আমাদের সক্ষম করে নির্ধারণ করতে যে ইউজারটি রিসোর্স এক্সেস করার জন্য অথোরাইজড কিনা। এই সিনেরিওতে, একটি GitHub Action রানার Google Cloud-এ কমিউনিকেট করছে একটি সার্ভিস একাউন্ট হিসেবে অথেনটিকেট হয়ে যেটির আপনার ফাংশন ডেপ্লয় করার জন্য आवশ্যক পারমিশন আছে।

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

একটি সার্ভিস একাউন্ট কী তৈরি করুন, JSON ডাউনলোড করুন, এবং GitHub Secrets-এ যোগ করুন।

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

Voila! 🎉 আমাদের ক্লাউড ফাংশন সফলভাবে ডেপ্লয় হয়েছে।

Cloud Functions লিমিটস vs AWS লিমিটস

নিচে দুটি বড় সার্ভারলেস ফাংশন প্রোভাইডারের বিস্তারিত তুলনা দেওয়া হলো:

Google Cloud Functions-এ Ruby দক্ষতা অর্জন: সিমলেসলি বিল্ড, টেস্ট, এবং ডেপ্লয় করুন

Functions Framework Contract vs Serverless Framework

এই আর্টিকেলে, আমরা Google Cloud Functions-এ ক্লাউড ফাংশন বিল্ড করার ওপর ফোকাস করেছি। এই সেগমেন্টে, আমি Functions Framework vs Serverless Framework বিল্ড করার তুলনা করতে চাই।

  • Serverless Framework serverless.yml-এর ওপর বেসড, অন্যদিকে Functions Framework Functions Framework Contract-এর ওপর বেসড, যা Google Cloud Infrastructure জুড়ে সার্ভারলেস ফাংশন ডেপ্লয় করতে ব্যবহৃত হয়।
  • Serverless Framework-এর সাথে, কিছু উদাহরণ মাত্র আছে, এবং এটি ভাবেই স্পষ্ট নয় কীভাবে Ruby-তে সার্ভারলেস ফাংশন বিল্ড এবং ডেপ্লয় করতে হয় বিভিন্ন Google সার্ভারলেস এনভায়রনমেন্টে (Cloud Functions, Cloud Run, এবং Knative এনভায়রনমেন্ট)। Functions Framework Contract ফাংশনসহ, Ruby-তে এই বিভিন্ন Google প্রোডাক্ট জুড়ে বিল্ড করা স্ট্রেইটফরওয়ার্ড।
    • পূর্বের পয়েন্ট থেকে ফলো করে, Functions Framework Contract আপনার ফাংশনের পেছনের ব্যাকিং ল্যাঙ্গুয়েজ স্যুইচ করা খুব সহজ করে তোলে আপনার ডেপ্লয়মেন্ট প্রসেসে অনেক পরিবর্তন ছাড়া।
  • এই লেখা মুহূর্তে, Functions Framework শুধু Google Cloud Serverless Environments এবং Knative এনভায়রনমেন্ট জুড়ে ইন্টারঅপারেবিলিটি সাপোর্ট করে। Serverless Framework, যাইহোক, একাধিক প্রোভাইডার জুড়ে একাধিক প্ল্যাটফর্ম সাপোর্ট করে।

রেফারেন্সের জন্য, ফুল কোড এখানে উপলব্ধ।

  1. রুবি দিয়ে নিজের টয় ল্যাঙ্গুয়েজ তৈরি করুন: ধাপে ধাপে গাইড

  2. Vue, Vuex, এবং Rails দিয়ে ফুল-স্ট্যাক ডেভেলপমেন্ট মাস্টার করুন: একটি স্কেলেবল পদ্ধতি

  3. Fluentd এবং ObjectRocket দিয়ে হাইব্রিড ক্লাউড লগিং স্ট্রিমলাইন করুন

  4. গুগল ক্লাউড প্রিন্ট ব্যাখ্যা: যেকোনো জায়গা থেকে, যেকোনো সময় প্রিন্ট করুন