सर्वरलेस फंक्शन्स क्लाउड सेवाओं के विकास और तैनाती का एक नया प्रोग्रामिंग प्रतिमान है। एक सर्वरलेस दुनिया में, हम अपने बैकएंड सेवाओं के प्रोविजनिंग, मेंटेनेंस और स्केलिंग को क्लाउड प्रोवाइडर पर अमूर्त करते हैं। यह डेवलपर्स को एक विशिष्ट समस्या को हल करने पर ध्यान केंद्रित करने की अनुमति देकर डेवलपर उत्पादकता में काफी सुधार करता है। सर्वरलेस फंक्शन्स बनाने के कई फायदे और नुकसान हैं, लेकिन उन्हें बनाने में एक चीज पर विचार करना भाषा समर्थन है। हाल ही में, Google ने Google Cloud Functions के लिए Ruby 2.7 के समर्थन की घोषणा की, और इस लेख में, मैं 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" } }
सरलता के लिए, हमारा क्लाउड फंक्शन एक पूर्ण SQL या NoSQL डेटाबेस के बजाय Cloud MemoryStore (GCP पर Redis या Memcache) द्वारा समर्थित होगा। यह हमें स्टेटलेस वातावरण में साझा राज्यों के बारे में जानने में भी सक्षम करेगा।
Ruby के साथ Google Cloud Function लिखना
GCF में फंक्शन्स लिखने के लिए, हम Google Cloud टीम द्वारा GCF फंक्शन्स बनाने के लिए प्रदान किए गए Functions Framework पर भरोसा करेंगे (इस पर बाद में और अधिक)।
सबसे पहले, ऐप डायरेक्टरी बनाएं और डायरेक्टरी में प्रवेश करें।
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::OtpResponseactive_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 प्रक्रियाओं को प्रोत्साहित करती है, और इस लेख के उद्देश्य के लिए, मैं Github Actions का उपयोग करके deploy-cloud-functions के साथ Github से अपने क्लाउड फंक्शन को तैनात करने पर ध्यान केंद्रित करूंगा।
आइए अपनी डेप्लॉयमेंट फाइल (.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 सीक्रेट्स का उपयोग कर रहे हैं। यह जांचने के लिए कि क्या हमारे टोकन ठीक से तैनात किए गए हैं, नीचे दिखाए अनुसार Google Console में अपने क्लाउड फंक्शन की जांच करें:

प्रमाणीकरण
Cloud Functions Admin और Service Account User भूमिकाओं के साथ एक Service Account बनाएं।
एक सर्विस अकाउंट मशीन-टू-मशीन IAM के लिए उपयोग किया जाता है। इस प्रकार, जब एक सिस्टम, चाहे वह Google Cloud पर चल रहा हो या नहीं, Google Cloud पर दूसरे सिस्टम से बात करता है, तो हमारी Google संसाधन तक पहुंच का अनुरोध करने वाले की पहचान करने में मदद के लिए एक सर्विस अकाउंट की आवश्यकता होती है। Cloud Functions Admin और Service Account User भूमिकाएं हमें यह निर्धारित करने में सक्षम बनाती हैं कि उपयोगकर्ता संसाधन तक पहुंचने के लिए अधिकृत है या नहीं। इस परिदृश्य में, एक Github Action रनर Google Cloud के साथ संचार करता है, एक सर्विस अकाउंट के रूप में प्रमाणित होता है जिसमें हमारे फंक्शन को तैनात करने के लिए आवश्यक अनुमतियां होती हैं।


एक सर्विस अकाउंट कुंजी बनाएं, JSON डाउनलोड करें, और इसे GitHub Secrets में जोड़ें।


वोइला! 🎉 हमारा क्लाउड फंक्शन सफलतापूर्वक तैनात किया गया है।
क्लाउड फंक्शन्स सीमाएं बनाम AWS सीमाएं
दो सबसे बड़े सर्वरलेस फंक्शन प्रोवाइडर्स की विस्तृत तुलना नीचे दी गई है:

Functions Framework अनुबंध बनाम Serverless Framework
इस लेख में, हमने Google Cloud Functions के लिए क्लाउड फंक्शन्स बनाने पर ध्यान केंद्रित किया है। इस खंड में, मैं Functions Framework बनाम Serverless Framework के साथ निर्माण की तुलना करना चाहता हूं।
Serverless Frameworkserverless.ymlपर आधारित है, जबकि Functions FrameworkFunctions Framework Contractपर आधारित है, जिसका उपयोग Google Cloud इन्फ्रास्ट्रक्चर में सर्वरलेस फंक्शन्स को तैनात करने के लिए किया जाता है।Serverless Frameworkके साथ, केवल कुछ उदाहरण हैं, और यह बिल्कुल स्पष्ट नहीं है कि Ruby के साथ विभिन्न Google सर्वरलेस वातावरण (क्लाउड फंक्शन्स, क्लाउड रन, और Knative वातावरण) में सर्वरलेस फंक्शन्स कैसे बनाएं और तैनात करें।Functions Framework Contractफंक्शन के साथ, इन विभिन्न Google उत्पादों में Ruby के साथ निर्माण सीधा है।- पिछले बिंदु से,
Functions Framework Contractआपके फंक्शन के पीछे की भाषा को बदलना बहुत आसान बनाता है बिना आपके डेप्लॉयमेंट प्रक्रिया में बहुत कुछ बदले।
- पिछले बिंदु से,
- इस लेखन के अनुसार,
Functions Frameworkकेवल Google Cloud सर्वरलेस वातावरण और Knative वातावरण में अंतरसंचालनीयता का समर्थन करता है।Serverless Framework, हालांकि, कई प्रदाताओं में कई प्लेटफार्मों का समर्थन करता है।
संदर्भ के लिए, पूरा कोड यहां उपलब्ध है।