কম্পিউটার

রেলে রুবিতে পরিষেবা বস্তু ব্যবহার করা

এই নিবন্ধটি প্লেবুক থার্টি নাইন-এ এর আসল চেহারা থেকে পরিবর্তন করা হয়েছে - নূন্যতম টুলিং সহ ইন্টারেক্টিভ ওয়েব অ্যাপ শিপিং করার জন্য একটি নির্দেশিকা , এবং AppSignal-এর জন্য এই অতিথি পোস্টের সাথে মানানসই।

আপনার অ্যাপটিকে পরিচালনা করতে হবে এমন অনেক কার্যকারিতা রয়েছে, তবে সেই যুক্তিটি অগত্যা নিয়ামক বা এমনকি মডেলের অন্তর্ভুক্ত নয়। কিছু উদাহরণের মধ্যে রয়েছে একটি কার্ট দিয়ে চেক আউট করা, সাইটের জন্য নিবন্ধন করা বা সদস্যতা শুরু করা।

আপনি কন্ট্রোলারে এই সমস্ত যুক্তি অন্তর্ভুক্ত করতে পারেন, তবে আপনি নিজেকে পুনরাবৃত্তি করতে থাকবেন, সেই সমস্ত জায়গায় একই যুক্তিকে কল করবেন। আপনি যুক্তিটিকে একটি মডেলে রাখতে পারেন, কিন্তু কখনও কখনও, আপনার এমন জিনিসগুলিতে অ্যাক্সেসের প্রয়োজন হয় যা কন্ট্রোলারে সহজে পাওয়া যায়, যেমন একটি আইপি ঠিকানা, বা একটি URL এ একটি প্যারামিটার৷ আপনার যা প্রয়োজন তা হল একটি পরিষেবা বস্তু৷

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

একটি পরিষেবা বস্তু কেবল একটি সাধারণ ওল্ড রুবি অবজেক্ট ("PORO")। এটি শুধুমাত্র একটি ফাইল যা একটি নির্দিষ্ট ডিরেক্টরির অধীনে থাকে। এটি একটি রুবি ক্লাস যা একটি অনুমানযোগ্য প্রতিক্রিয়া প্রদান করে। তিনটি মূল অংশের কারণে যা প্রতিক্রিয়াকে অনুমানযোগ্য করে তোলে। সমস্ত পরিষেবা বস্তু একই প্যাটার্ন অনুসরণ করা উচিত।

  • একটি প্যারাম আর্গুমেন্ট সহ একটি প্রাথমিক পদ্ধতি রয়েছে৷
  • কল নামে একটি একক সর্বজনীন পদ্ধতি রয়েছে৷
  • সফলতার সাথে একটি OpenStruct ফেরত দেয়? এবং হয় একটি পেলোড বা একটি ত্রুটি৷

একটি OpenStruct কি?

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

যদি সাফল্য true হয় , এটি ডেটার একটি পেলোড প্রদান করে৷

OpenStruct.new({success ?:true, payload: 'some-data'})

যদি সাফল্য false হয় , এটি একটি ত্রুটি প্রদান করে।

OpenStruct.new({success ?:false, error: 'some-error'})

এখানে একটি পরিষেবা অবজেক্টের উদাহরণ দেওয়া হল যা অ্যাপসিগন্যাল নতুন API থেকে ডেটা সংগ্রহ করে, যা বর্তমানে বিটাতে রয়েছে৷

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 . আমি একটি অনুমানযোগ্য প্রতিক্রিয়া ফেরাতে OpenStruct এর উদার ব্যবহার করি। লেখার পরীক্ষার ক্ষেত্রে এটি সত্যিই মূল্যবান কারণ লজিকের সমস্ত স্থাপত্য নিদর্শন অভিন্ন৷

একটি মডিউল কি?

মডিউল ব্যবহার করে আমাদের নাম-স্পেসিং প্রদান করে এবং অন্যান্য শ্রেণীর সাথে সংঘর্ষ প্রতিরোধ করে। এর মানে হল যে আপনি সমস্ত ক্লাসে একই পদ্ধতির নাম ব্যবহার করতে পারেন এবং তারা সংঘর্ষ করবে না কারণ তারা একটি নির্দিষ্ট নামস্থানের অধীনে রয়েছে।

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

আমি একাধিক ফোল্ডারে আমার পরিষেবা ডিরেক্টরি সংগঠিত করি, প্রতিটিতে অ্যাপ্লিকেশনের একটি নির্দিষ্ট অংশের জন্য কার্যকারিতা রয়েছে৷

উদাহরণস্বরূপ, CloudflareServices ক্লাউডফ্লেয়ারে সাবডোমেন তৈরি এবং অপসারণের জন্য ডিরেক্টরিতে নির্দিষ্ট পরিষেবার বস্তু রয়েছে। Wistia এবং Zapier পরিষেবাগুলি তাদের নিজ নিজ পরিষেবা ফাইল ধারণ করে৷

আপনার পরিষেবার বস্তুগুলিকে এভাবে সংগঠিত করার ফলে এটি বাস্তবায়নে নেমে আসার সময় আরও ভাল পূর্বাভাস পাওয়া যায় এবং 10k-ফুট ভিউ থেকে অ্যাপটি কী করছে তা এক নজরে দেখা সহজ৷

চলুন StripeServices খনন করা যাক ডিরেক্টরি স্ট্রাইপস এপিআই-এর সাথে ইন্টারঅ্যাক্ট করার জন্য এই ডিরেক্টরিতে পৃথক পরিষেবা বস্তু রয়েছে। আবার, এই ফাইলগুলি যে কাজটি করে তা হল আমাদের অ্যাপ্লিকেশন থেকে ডেটা নেওয়া এবং এটি স্ট্রাইপে পাঠানো। আপনার যদি কখনও StripeService-এ API কল আপডেট করতে হয় অবজেক্ট যা একটি সাবস্ক্রিপশন তৈরি করে, আপনার কাছে এটি করার জন্য শুধুমাত্র একটি জায়গা আছে।

পাঠানোর জন্য ডেটা সংগ্রহ করে এমন সমস্ত যুক্তি AppServices-এ বসবাস করে একটি পৃথক পরিষেবা বস্তুতে করা হয় ডিরেক্টরি এই ফাইলগুলি আমাদের অ্যাপ্লিকেশন থেকে ডেটা সংগ্রহ করে এবং বহিরাগত API-এর সাথে ইন্টারফেস করার জন্য সংশ্লিষ্ট পরিষেবা ডিরেক্টরিতে পাঠায়৷

এখানে একটি চাক্ষুষ উদাহরণ:ধরা যাক আমাদের এমন কেউ আছেন যিনি একটি নতুন সদস্যতা শুরু করছেন। সবকিছু একটি নিয়ামক থেকে উদ্ভূত হয়. এখানে 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

একটি উচ্চ-স্তরের ওভারভিউ থেকে, আমরা যা দেখছি তা এখানে:

আমাদের প্রথমে স্ট্রাইপ গ্রাহক আইডি পেতে হবে যাতে আমরা সাবস্ক্রিপশন তৈরি করতে এটি স্ট্রাইপে পাঠাতে পারি। এটি নিজেই একটি সম্পূর্ণ আলাদা পরিষেবা বস্তু যা এটি ঘটানোর জন্য অনেকগুলি কাজ করে৷

  1. আমরা stripe_customer_id কিনা তা পরীক্ষা করি ব্যবহারকারীর প্রোফাইলে সংরক্ষিত হয়। যদি তা হয়, আমরা গ্রাহককে স্ট্রাইপ থেকে পুনরুদ্ধার করি শুধুমাত্র নিশ্চিত করার জন্য যে গ্রাহক প্রকৃতপক্ষে বিদ্যমান, তারপরে আমাদের OpenStruct-এর পেলোডে এটি ফেরত দেই।
  2. যদি গ্রাহকের অস্তিত্ব না থাকে, আমরা গ্রাহক তৈরি করি, 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
 

আশা করি, আপনি দেখতে শুরু করতে পারেন কেন আমরা একাধিক পরিষেবা বস্তু জুড়ে আমাদের যুক্তি গঠন করি। আপনি এই যুক্তি সব সঙ্গে একটি ফাইলের একটি দৈত্য behemoth কল্পনা করতে পারেন? কোন উপায় নেই!

আমাদের 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

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

এখানে জিনিসগুলি আকর্ষণীয় হয়। আমরা এখানে একটি উদাহরণ হিসাবে Mavenseed ব্যবহার করছি, যদিও এই একই যুক্তি SportKeeper-এও চলে। আমাদের মাল্টি-টেন্যান্ট অ্যাপ হল একটি একক মনোলিথ, শেয়ারিং টেবিল, একটি site_id কলাম দ্বারা পৃথক করা। প্রতিটি ভাড়াটে স্ট্রাইপ কানেক্টের মাধ্যমে স্ট্রাইপের সাথে সংযোগ করে এবং তারপরে আমরা ভাড়াটেদের অ্যাকাউন্টে সংরক্ষণ করার জন্য একটি স্ট্রাইপ অ্যাকাউন্ট আইডি পাই।

আমাদের একই স্ট্রাইপ API কলগুলি ব্যবহার করে, আমরা কেবল সংযুক্ত অ্যাকাউন্টের স্ট্রাইপ অ্যাকাউন্টটি পাস করতে পারি এবং স্ট্রাইপ সংযুক্ত অ্যাকাউন্টের পক্ষে API কলটি সম্পাদন করবে৷

তাই একটি উপায়ে, আমাদের 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% সংরক্ষণ করুন!


  1. রেলের সাথে Tailwind CSS ব্যবহার করা

  2. রেলের সাথে কৌণিক ব্যবহার 5

  3. রেলে রুবি কি এবং কেন এটি দরকারী?

  4. রুবি ফ্রিজ পদ্ধতি - বস্তুর পরিবর্তনশীলতা বোঝা