इस लेख को Playbook उनतीस - न्यूनतम टूलिंग के साथ इंटरएक्टिव वेब ऐप्स शिपिंग के लिए एक गाइड में इसके मूल स्वरूप से संशोधित किया गया है। , और AppSignal के लिए इस अतिथि पोस्ट को फिट करने के लिए तैयार किया गया है।
आपके ऐप को संभालने के लिए बहुत सारी कार्यक्षमता है, लेकिन यह तर्क जरूरी नहीं कि नियंत्रक या मॉडल में भी हो। कुछ उदाहरणों में कार्ट से चेक आउट करना, साइट के लिए पंजीकरण करना या सदस्यता शुरू करना शामिल है।
आप इस सारे लॉजिक को कंट्रोलर में शामिल कर सकते हैं, लेकिन आप उन सभी जगहों पर एक ही लॉजिक को कॉल करते हुए खुद को दोहराते रहेंगे। आप तर्क को एक मॉडल में रख सकते हैं, लेकिन कभी-कभी, आपको उन चीजों तक पहुंच की आवश्यकता होती है जो नियंत्रक में आसानी से उपलब्ध होती हैं, जैसे आईपी पता, या यूआरएल में पैरामीटर। आपको जो चाहिए वह एक सेवा वस्तु है।
सर्विस ऑब्जेक्ट का काम कार्यक्षमता को एनकैप्सुलेट करना, एक सेवा को निष्पादित करना और विफलता का एकल बिंदु प्रदान करना है। सर्विस ऑब्जेक्ट का उपयोग करने से डेवलपर्स को एक ही कोड को बार-बार लिखने से रोकता है जब इसे एप्लिकेशन के विभिन्न हिस्सों में उपयोग किया जाता है।
एक सेवा वस्तु सिर्फ एक सादा पुरानी रूबी वस्तु ("पोरो") है। यह सिर्फ एक फाइल है जो एक विशिष्ट निर्देशिका के अंतर्गत रहती है। यह एक रूबी वर्ग है जो अनुमानित प्रतिक्रिया देता है। प्रतिक्रिया को प्रेडिक्टेबल बनाता है जो तीन प्रमुख भागों के कारण होता है। सभी सेवा वस्तुओं को समान पैटर्न का पालन करना चाहिए।
- परम तर्क के साथ एक आरंभीकरण विधि है।
- कॉल नाम की एक ही सार्वजनिक विधि है।
- सफलता के साथ ओपनस्ट्रक्चर लौटाता है? और या तो पेलोड या त्रुटि।
ओपनस्ट्रक्चर क्या है?
यह एक वर्ग और हैश के दिमाग की उपज जैसा है। आप इसे एक मिनी-क्लास के रूप में सोच सकते हैं जो मनमाना गुण प्राप्त कर सकता है। हमारे मामले में, हम इसे एक अस्थायी डेटा संरचना के रूप में उपयोग कर रहे हैं जो केवल दो विशेषताओं को संभालती है।
अगर सफलता true
है , यह डेटा का पेलोड लौटाता है।
OpenStruct.new({success ?:true, payload: 'some-data'})
अगर सफलता false
है तो , यह एक त्रुटि देता है।
OpenStruct.new({success ?:false, error: 'some-error'})
यहां एक सेवा ऑब्जेक्ट का उदाहरण दिया गया है जो ऐपसिग्नल्स के नए एपीआई से डेटा तक पहुंचता है और पकड़ लेता है, जो वर्तमान में बीटा में है।
module AppServices
class AppSignalApiService
require 'httparty'
def initialize(params)
@endpoint = params[:endpoint] || 'markers'
end
def call
result = HTTParty.get("https://appsignal.com/api/#{appsignal_app_id}/#{@endpoint}.json?token=#{appsignal_api_key}")
rescue HTTParty::Error => e
OpenStruct.new({success?: false, error: e})
else
OpenStruct.new({success?: true, payload: result})
end
private
def appsignal_app_id
ENV['APPSIGNAL_APP_ID']
end
def appsignal_api_key
ENV['APPSIGNAL_API_KEY']
end
end
end
आप उपरोक्त फ़ाइल को AppServices::AppSignalApiService.new({endpoint: 'markers'}).call
से कॉल करेंगे। . मैं उम्मीद के मुताबिक प्रतिक्रिया देने के लिए ओपनस्ट्रक्चर का उदार उपयोग करता हूं। जब परीक्षण लिखने की बात आती है तो यह वास्तव में मूल्यवान होता है क्योंकि तर्क के सभी वास्तुशिल्प पैटर्न समान होते हैं।
मॉड्यूल क्या है?
मॉड्यूल का उपयोग हमें नाम-रिक्ति प्रदान करता है और अन्य वर्गों से टकराने से रोकता है। इसका मतलब है कि आप सभी वर्गों में एक ही विधि नामों का उपयोग कर सकते हैं और वे एक विशिष्ट नाम स्थान के अंतर्गत होने के कारण आपस में टकराएंगे नहीं।
मॉड्यूल नाम का एक अन्य महत्वपूर्ण हिस्सा यह है कि हमारे ऐप में फाइलों को कैसे व्यवस्थित किया जाता है। सेवा वस्तुओं को प्रोजेक्ट में सेवा फ़ोल्डर में रखा जाता है। ऊपर दिया गया सर्विस ऑब्जेक्ट उदाहरण, मॉड्यूल नाम AppServices
. के साथ , AppServices
. के अंतर्गत आता है सेवा निर्देशिका में फ़ोल्डर।
मैं अपनी सेवा निर्देशिका को कई फ़ोल्डरों में व्यवस्थित करता हूं, प्रत्येक में एप्लिकेशन के एक विशिष्ट भाग के लिए कार्यक्षमता होती है।
उदाहरण के लिए, CloudflareServices
निर्देशिका Cloudflare पर उप डोमेन बनाने और हटाने के लिए विशिष्ट सेवा ऑब्जेक्ट रखती है। Wistia और Zapier सेवाओं में उनकी संबंधित सेवा फ़ाइलें होती हैं।
अपनी सेवा वस्तुओं को इस तरह व्यवस्थित करने से कार्यान्वयन की बात आने पर बेहतर पूर्वानुमान मिलता है, और एक नज़र में यह देखना आसान है कि ऐप 10k-foot दृश्य से क्या कर रहा है।
आइए StripeServices
के बारे में जानें निर्देशिका। यह निर्देशिका स्ट्राइप्स एपीआई के साथ बातचीत करने के लिए अलग-अलग सेवा ऑब्जेक्ट रखती है। दोबारा, ये फ़ाइलें केवल एक चीज करती हैं जो हमारे एप्लिकेशन से डेटा लेती है और इसे स्ट्राइप को भेजती है। यदि आपको कभी भी StripeService
. में API कॉल को अपडेट करने की आवश्यकता है ऑब्जेक्ट जो सदस्यता बनाता है, आपके पास ऐसा करने के लिए केवल एक ही स्थान है।
भेजे जाने वाले डेटा को एकत्रित करने वाले सभी तर्क AppServices
में रहते हुए, एक अलग सेवा ऑब्जेक्ट में किए जाते हैं निर्देशिका। ये फ़ाइलें हमारे एप्लिकेशन से डेटा एकत्र करती हैं और बाहरी एपीआई के साथ इंटरफेस करने के लिए इसे संबंधित सेवा निर्देशिका में भेजती हैं।
यहां एक दृश्य उदाहरण दिया गया है:मान लें कि हमारे पास कोई है जो एक नई सदस्यता शुरू कर रहा है। सब कुछ एक नियंत्रक से उत्पन्न होता है। यह रहा SubscriptionsController
।
class SubscriptionsController < ApplicationController
def create
@subscription = Subscription.new(subscription_params)
if @subscription.save
result = AppServices::SubscriptionService.new({
subscription_params: {
subscription: @subscription,
coupon: params[:coupon],
token: params[:stripeToken]
}
}).call
if result && result.success?
sign_in @subscription.user
redirect_to subscribe_welcome_path, success: 'Subscription was successfully created.'
else
@subscription.destroy
redirect_to subscribe_path, danger: "Subscription was created, but there was a problem with the vendor."
end
else
redirect_to subscribe_path, danger:"Error creating subscription."
end
end
end
हम पहले ऐप में सब्सक्रिप्शन बनाएंगे, और अगर यह सफल होता है, तो हम उसे, स्ट्राइपटोकन, और कूपन जैसी सामग्री को AppServices::SubscriptionService
नामक फ़ाइल में भेजते हैं। ।
AppServices::SubscriptionService
. में फ़ाइल, ऐसी कई चीजें हैं जिन्हें होने की आवश्यकता है। जो कुछ हो रहा है उसमें जाने से पहले यह रही वह वस्तु:
module AppServices
class SubscriptionService
def initialize(params)
@subscription = params[:subscription_params][:subscription]
@token = params[:subscription_params][:token]
@plan = @subscription.subscription_plan
@user = @subscription.user
end
def call
# create or find customer
customer ||= AppServices::StripeCustomerService.new({customer_params: {customer:@user, token:@token}}).call
if customer && customer.success?
subscription ||= StripeServices::CreateSubscription.new({subscription_params:{
customer: customer.payload,
items:[subscription_items],
expand: ['latest_invoice.payment_intent']
}}).call
if subscription && subscription.success?
@subscription.update_attributes(
status: 'active',
stripe_id: subscription.payload.id,
expiration: Time.at(subscription.payload.current_period_end).to_datetime
)
OpenStruct.new({success?: true, payload: subscription.payload})
else
handle_error(subscription&.error)
end
else
handle_error(customer&.error)
end
end
private
attr_reader :plan
def subscription_items
base_plan
end
def base_plan
[{ plan: plan.stripe_id }]
end
def handle_error(error)
OpenStruct.new({success?: false, error: error})
end
end
end
एक उच्च-स्तरीय अवलोकन से, हम यहां देख रहे हैं:
हमें पहले स्ट्राइप ग्राहक आईडी प्राप्त करनी होगी ताकि हम सब्सक्रिप्शन बनाने के लिए इसे स्ट्राइप को भेज सकें। यह अपने आप में एक पूरी तरह से अलग सेवा वस्तु है जो ऐसा करने के लिए कई चीजें करती है।
- हम यह देखने के लिए जांच करते हैं कि
stripe_customer_id
उपयोगकर्ता की प्रोफ़ाइल पर सहेजा गया है। यदि ऐसा है, तो हम ग्राहक को स्ट्राइप से केवल यह सुनिश्चित करने के लिए पुनः प्राप्त करते हैं कि ग्राहक वास्तव में मौजूद है, फिर इसे हमारे ओपनस्ट्रक्चर के पेलोड में वापस कर दें। - यदि ग्राहक मौजूद नहीं है, तो हम ग्राहक बनाते हैं, stripe_customer_id सहेजते हैं, फिर उसे OpenStruct के पेलोड में वापस कर देते हैं।
किसी भी तरह से, हमारी CustomerService
स्ट्राइप ग्राहक आईडी लौटाता है, और यह वही करेगा जो इसे करने के लिए आवश्यक है। यह रही वह फ़ाइल:
module AppServices
class CustomerService
def initialize(params)
@user = params[:customer_params][:customer]
@token = params[:customer_params][:token]
@account = @user.account
end
def call
if @account.stripe_customer_id.present?
OpenStruct.new({success?: true, payload: @account.stripe_customer_id})
else
if find_by_email.success? && find_by_email.payload
OpenStruct.new({success?: true, payload: @account.stripe_customer_id})
else
create_customer
end
end
end
private
attr_reader :user, :token, :account
def find_by_email
result ||= StripeServices::RetrieveCustomerByEmail.new({email: user.email}).call
handle_result(result)
end
def create_customer
result ||= StripeServices::CreateCustomer.new({customer_params:{email:user.email, source: token}}).call
handle_result(result)
end
def handle_result(result)
if result.success?
account.update_column(:stripe_customer_id, result.payload.id)
OpenStruct.new({success?: true, payload: account.stripe_customer_id})
else
OpenStruct.new({success?: false, error: result&.error})
end
end
end
end
उम्मीद है, आप यह देखना शुरू कर सकते हैं कि हम अपने तर्क को कई सेवा वस्तुओं में क्यों बनाते हैं। क्या आप कल्पना कर सकते हैं कि इस तर्क के साथ एक फ़ाइल के एक विशाल विशालकाय व्यक्ति की कल्पना की जा सकती है? बिलकुल नहीं!
हमारे AppServices::SubscriptionService
. पर वापस जाएं फ़ाइल। अब हमारे पास एक ग्राहक है जिसे हम स्ट्राइप को भेज सकते हैं, जो स्ट्राइप पर सब्सक्रिप्शन बनाने के लिए आवश्यक डेटा को पूरा करता है।
अब हम अंतिम सर्विस ऑब्जेक्ट को कॉल करने के लिए तैयार हैं, StripeServices::CreateSubscription
फ़ाइल।
फिर से, StripeServices::CreateSubscription
सेवा वस्तु कभी नहीं बदलती। इसकी एक ही जिम्मेदारी है, और वह है डेटा लेना, उसे स्ट्राइप को भेजना, और या तो एक सफलता लौटाना या वस्तु को पेलोड के रूप में वापस करना।
module StripeServices
class CreateSubscription
def initialize(params)
@subscription_params = params[:subscription_params]
end
def call
subscription = Stripe::Subscription.create(@subscription_params)
rescue Stripe::StripeError => e
OpenStruct.new({success?: false, error: e})
else
OpenStruct.new({success?: true, payload: subscription})
end
end
end
बहुत आसान है ना? लेकिन आप शायद सोच रहे हैं, यह छोटी सी फाइल ओवरकिल है। आइए ऊपर दी गई फ़ाइल के समान फ़ाइल का एक और उदाहरण देखें, लेकिन इस बार हमने इसे स्ट्राइप कनेक्ट के माध्यम से एक बहु-किरायेदार एप्लिकेशन के साथ उपयोग के लिए संवर्धित किया है।
यहां चीजें दिलचस्प हो जाती हैं। हम यहां एक उदाहरण के रूप में मावेनसीड का उपयोग कर रहे हैं, हालांकि यही तर्क स्पोर्टकीपर पर भी चलता है। हमारा बहु-किरायेदार ऐप साइट_आईडी कॉलम द्वारा अलग किए गए एक एकल मोनोलिथ, साझा करने वाली टेबल है। प्रत्येक टैनेंट स्ट्राइप कनेक्ट के माध्यम से स्ट्राइप से जुड़ता है, और फिर हमें टैनेंट के खाते में सहेजने के लिए एक स्ट्राइप खाता आईडी प्राप्त होता है।
हमारे समान स्ट्राइप एपीआई कॉल का उपयोग करके, हम बस कनेक्टेड अकाउंट के स्ट्राइप अकाउंट को पास कर सकते हैं, और स्ट्राइप कनेक्टेड अकाउंट की ओर से एपीआई कॉल करेगा।
तो एक तरह से हमारी StripeService
ऑब्जेक्ट एक ही फ़ाइल को कॉल करने के लिए, लेकिन अलग-अलग डेटा भेजने के लिए, मुख्य एप्लिकेशन और किरायेदारों दोनों के साथ डबल-ड्यूटी कर रहा है।
module StripeServices
class CreateSubscription
def initialize(params)
@subscription_params = params[:subscription_params]
@stripe_account = params[:stripe_account]
@stripe_secret_key = params[:stripe_secret_key] ? params[:stripe_secret_key] : (Rails.env.production? ? ENV['STRIPE_LIVE_SECRET_KEY'] : ENV['STRIPE_TEST_SECRET_KEY'])
end
def call
subscription = Stripe::Subscription.create(@subscription_params, account_params)
rescue Stripe::StripeError => e
OpenStruct.new({success?: false, error: e})
else
OpenStruct.new({success?: true, payload: subscription})
end
private
attr_reader :stripe_account, :stripe_secret_key
def account_params
{
api_key: stripe_secret_key,
stripe_account: stripe_account,
stripe_version: ENV['STRIPE_API_VERSION']
}
end
end
end
इस फ़ाइल पर कुछ तकनीकी नोट:मैं एक सरल उदाहरण साझा कर सकता था, लेकिन मुझे वास्तव में लगता है कि आपके लिए यह देखना महत्वपूर्ण है कि एक उचित सेवा वस्तु कैसे संरचित होती है, जिसमें उसकी प्रतिक्रियाएँ भी शामिल हैं।
सबसे पहले, "कॉल" विधि में बचाव और अन्य कथन होता है। यह निम्नलिखित लिखने जैसा ही है:
def call
begin
rescue Stripe ::StripeError => e
else
end
end
लेकिन रूबी विधियां स्वचालित रूप से एक ब्लॉक को अंतर्निहित रूप से शुरू करती हैं, इसलिए प्रारंभ और अंत जोड़ने का कोई कारण नहीं है। यह कथन पढ़ता है, "सदस्यता बनाएं, यदि कोई त्रुटि है तो एक त्रुटि लौटाएं, अन्यथा सदस्यता वापस कर दें।"
सरल, संक्षिप्त और सुरुचिपूर्ण। रूबी वास्तव में एक सुंदर भाषा है और सेवा वस्तुओं का उपयोग वास्तव में इस पर प्रकाश डालता है।
मुझे आशा है कि आप हमारे अनुप्रयोगों में सेवा फ़ाइलों का महत्व देख सकते हैं। वे हमारे तर्क को व्यवस्थित करने का एक बहुत ही संक्षिप्त तरीका प्रदान करते हैं जो न केवल अनुमान लगाने योग्य है बल्कि आसानी से बनाए रखने योग्य है!
पी.एस. यदि आप रूबी मैजिक पोस्ट को प्रेस से बाहर होते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!
——
मेरी नई किताब प्लेबुक उनतालीस - न्यूनतम टूलिंग के साथ इंटरएक्टिव वेब ऐप्स शिपिंग के लिए एक गाइड उठाकर इस अध्याय और अधिक पढ़ें। . इस पुस्तक में, मैं सामान्य पैटर्न और तकनीकों को कवर करने में एक टॉप-डाउन दृष्टिकोण लेता हूं, जो पूरी तरह से एकल-डेवलपर बिल्डिंग के रूप में मेरे पहले अनुभव पर आधारित है और कई उच्च-ट्रैफ़िक, उच्च-राजस्व वेबसाइट अनुप्रयोगों को बनाए रखता है।
कूपन कोड का प्रयोग करें appsignalrocks और 30% बचाएं!