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

Rails-এ Dry-Monads দিয়ে রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং ইমপ্লিমেন্ট করুন

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

রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং

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

validate user -> update address -> send mail upon successful update

এই প্রতিটি স্টেপ বা ফেইল হতে পারে বা সফল হতে পারে, এবং যেকোনো স্টেপের ব্যর্থতা পুরো প্রসেসের ব্যর্থতাকে নিয়ে আসে, কারণ ফাংশনের উদ্দেশ্য অর্জিত হয় না।

রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং (ROP) হলো Scott Wlaschin আবিষ্কৃত একটি টার্ম যা রেলওয়ে সুইচ অ্যানালজি আপ্লাই করে এই ধরনের ফাংশনগুলোর এরর হ্যান্ডলিং-এ। রেলওয়ে সুইচ (UK-এ "points" বলা হয়) ট্রেনগুলকে এক ট্র্যাক থেকে অন্য ট্র্যাকে গাইড করে। স্কট এই অ্যানালজিটি ব্যবহার করেন এই অর্থে যে প্রতিটি স্টেপের সাকসেস/ফেইল আউটপুট ঠিক রেলওয়ে সুইচের মত কাজ করে, কারণ এটি আপককে সাকসেস ট্র্যাক বা ফেইল ট্র্যাকএ মুভ করতে পারে।

Rails-এ Dry-Monads দিয়ে রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং ইমপ্লিমেন্ট করুন সাকসেস/ফেইল আউটপুট রেলওয়ে সুইচের মত কাজ করছে

যখন যেকোনো স্টেপে এরর থাকে, ফেইল আউটপুট আমাকে ফেইল ট্র্যাকএ নিয়ে যায়, যার ফলে বাকি স্টেপগুলো বাইপাস হয়। তবে, যখন সাকসেস আউটপুট থাকে, সেটি পরবর্তী স্টেপের ইনপুটের সাথে কানেক্টেড থাকে যা আমাদের চূড়ান্ত গন্তব্যে পৌঁছাতে সাহায্য করে, নিচের ইমেজে দেখানো মত।

Rails-এ Dry-Monads দিয়ে রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং ইমপ্লিমেন্ট করুন অনেক স্টেপের সাকসেস/ফেইল আউটপুট চেইন করা হয়েছে

এই টু-ট্র্যাক অ্যানালজিই রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং-এর আন্ডারলাইনিং আইডিয়া। এটি প্রতিটি স্টেপে (অর্থাৎ একটি প্রসেসের অংশ olan প্রতিটি মেথড) সাকসেস/ফেইল আউটপুট রিটার্ন করার জন্য চেষ্টা করে যাতে এক স্টেপের ব্যর্থতা পুরো প্রসেসের ব্যর্থতা হয়। কেবল প্রতিটি স্টেপের সফল সমাপ্তিplessly সার্বিক সাফল্য நீয়ে আসে।

একটি প্রতিদিনের জীবনের উদাহরণ

কল্পনা করুন আপনার লক্ষ্য হলো The Milk Shakers নামক একটি স্টোর থেকে সরাসরি এক কার্টন দুধ কেনা। সম্ভাব্য স্টেপগুলো হবে:

Leave your house -> Arrive at The Milk Shakers -> Pick up a carton of milk -> Pay for the carton of milk

যদি আপনি আপনার বাড়ি থেকে বের হতে না পারেন, পুরো প্রসেস ব্যর্থ কারণ প্রথম স্টেপই ব্যর্থ। কি হয় যদি আপনি বাড়ি থেকে বের হয়ে Walmart-এ যান? প্রসেসটি এখনও ব্যর্থ কারণ আপনি নির্দিষ্ট স্টোরে যাননি। তथ্য এই যে আপনি Walmart-এ থেকে দুধ পেতে পারেন সেটি মানে না যে প্রসেস চালু থাকবে। ROP প্রসেসটিকে Walmart-এ থামিয়ে দিয়ে একটি ফেইল আউটপুট রিটার্ন করে যেটি জানিয়ে দেয় যে প্রসেসটি ব্যর্থ হয়েছে কারণ স্টোরটি The Milk Shakers ছিল না। তবে, আপনি যদি সঠিক স্টোরে যেতেন, প্রসেসটি চালু থাকত, আউটপুট চেক করে এবং হয় প্রসেস শেষ হতো বা পরবর্তী স্টেপে যেত। এটি আরও রিডেবল এবং এলিগ্যান্ট এরর হ্যান্ডলিং নিশ্চিত করে if/else এবং return স্টেটমেন্টস ছাড়া ইনডিভিজুয়াল স্টেপগুলো লিংক করা।

Rails-এ, আমরা এই টু-ট্র্যাক রেলওয়ে আউটপুট অর্জন করতে পারি Dry Monads জেম ব্যবহার করে।

Dry Monads পরিচিতি এবং কীভাবে কাজ করে

Monads মূলত একটি গাণিতিক কনসেপ্ট। বেসিক্যালি, এগুলো কিছু স্পেশাল ফাংশন들의 কম্পোজিশন বা অ্যাবস্ট্রাকশন যা কোডে ব্যবহার করা হলে এক্সপ্লিসিট স্টেটফুল ভ্যালু হ্যান্ডলিং এলিমিনেট করতে পারে। সেগুলো প্রোগ্রাম লজিকের জন্য দরকারি কম্পিউটেশনাল বয়লারপ্লেট কোডের অ্যাবস্ট্রাকশন ও হতে পারে। স্টেটফুল ভ্যালুগুলো কোনো নির্দিষ্ট ফাংশনের জন্য লোকাল নয়, উদাহরণস্বরূপ: ইনপুটস, গ্লোবাল ভেরিয়েবলস, এবং আউটপুটস। Monads একটি বাইন্ড ফাংশন ধারণ করে যা এই ভ্যালুগুলোকে এক মোনাড থেকে অন্য মোনাডে পাস করার সম্ভব করে তোলে; তাই সেগুলো কখনো এক্সপ্লিসিটলি হ্যান্ডেল করা হয় না। সেগুলো এক্সেপশন হ্যান্ডলিং, রোলব্যাক কমিট, রিট্রাই লজিক, ইত্যাদি হ্যান্ডল করতে বিল্ড করা যেতে পারে। মোনাড সম্পর্কে আরও জানতে পারেন এখানে।

এর ডকুমেন্টেশনে বলা আছে, যা আমি আপনাকে রিভিউ করার পরামর্শ দিচ্ছি, dry monads হলো Ruby-র জন্য কমন মোনাডসের একটি সেট। Monads এরর, এক্সেপশন, এবং ফাংশন চেইনিং হ্যান্ডল করার একটি এলিগ্যান্ট উপায় প্রদান করে যাতে কোড অনেক বোঝার সহজ হয় এবং সব চাইতে এরর হ্যান্ডলিং থাকে সব if এবং else ছাড়া। আমরা Result Monad-এ ফোকাস করব কারণ এটি ঠিক সেটি যা আমাদের সাকসেস/ফেইল আউটপুট অর্জনের জন্য দরকার।

নিচের কমান্ড দিয়ে একটি নতুন Rails অ্যাপ শুরু করুন Railway-app নামে:

rails new railway-app -T

কমান্ডে -T মানে আমরা টেস্ট ফোল্ডার স্কিপ করব কারণ আমরা RSpec ব্যবহার করতে চাই টেস্টিং-এর জন্য।

এরপর, আমাদের Gemfile-এ দরকারি জেমগুলো যোগ করুন: gem dry-monads সাকসেস/ফেইল রেজাল্টের জন্য এবং gem rspec-rails টেস্ট এবং ডেভেলপমেন্ট গ্রুপে আমাদের টেস্টিং ফ্রেমওয়ার্ক হিসেবে। এখন, আমাদের অ্যাপে bundle install রান করুন অ্যাড করা জেমগুলো ইনস্টল করতে। আমাদের টেস্ট ফাইল এবং হেল্পার জেনারেট করতে, তবে, আমাদের নিচের কমান্ড রান করতে হবে:

rails generate rspec:install

এক ফাংশনকে বেশ কিছু স্টেপে ভাগ করুন

আপনার ফাংশনকে ছোট ছোট মেথডে ভাগ করা যা একসাথে কাজ করে আপনার চূড়ান্ত লক্ষ্য অর্জন করতে সবসময় পরামর্শদেয়। এই মেথডগুলো থেকে এররها, যদি থাকে, আমাদের একজ্যাক্টলি শনাক্ত করতে সাহায্য করে আমাদের প্রসেস কোথায় ফেইল হয়েছে এবং আমাদের কোড ক্লিন এবং রিডেবল রাখে। এটি যতটা সম্ভব সিম্পল রাখতে, আমরা একটি টয়ота কার ডিলারশিপের ক্লাস কন্সট্রাক্ট করব যা একটি কার ডেলিভার করবে একজন ইউজারের কাছে যদি ডিমांडেড মডেল এবং কালার উপলব্ধ থাকে, ম্যানুফ্যাকচারিং ইয়ার ২০০০ সালের আগের না হয়, এবং ডেলিভারি করার সিটি নিকটবর্তী সিটির লিস্টে থাকে। এটি আকর্ষণীয় হবে। :)

ডেলিভারি প্রসেসকে বেশ কিছু স্টেপে ভাগ করা যাক:

  • ম্যানুফ্যাকচারিং ইয়ার ২০০০ সালের আগের নয় তা ভেরিফাই করুন।
  • মডেল উপলব্ধ কিনা ভেরিফাই করুন।
  • কালার উপলব্ধ কিনা ভেরিফাই করুন।
  • ডেলিভারি করার সিটি নিকটবর্তী সিটি কিনা ভেরিফাই করুন।
  • কার ডেলিভারি হবে বলে একটি মেসেজ পাঠান।

এখন বিভিন্ন স্টেপগুলো সেটল হয়ে গেলে, কোডের মধ্যে ঢুকি।

সাকসেস/ফেইল আউটপুট রেজাল্ট ইনপুট করা

আমাদের app/model ফোল্ডারে, car_dealership.rb নামে একটি ফাইল তৈরি করুন এবং এই ক্লাসকে গুরুত্বপূর্ণ ডিটেইলস দিয়ে ইনিশিয়ালাইজ করুন। ফাইলের টপে, আমাদের dry/monads রিকোয়ার করতে হবে, এবং ক্লাস নামের ঠিক পরে, আমাদের Dry::Monads[:result, :do] ইনক্লুড করতে হবে। এটি রেজাল্ট মোনাড এবং ডু নোটেশন (যা yield শব্দ ব্যবহার করে বেশ কিছু মোনাডিক অপারেশন কম্বাইন করা সম্ভব করে) আমাদের উপলব্ধ করে দেয়।

require 'dry/monads'
class CarDealership
  include Dry::Monads[:result, :do]
  def initialize
    @available_models = %w[Avalon Camry Corolla Venza]
    @available_colors = %w[red black blue white]
    @nearby_cities = %w[Austin Chicago Seattle]
  end
end

এরপর, আমাদের deliver_car মেথডটি যোগ করুন, যেটি অন্যান্য সব স্টেপগুলো সম্বলিত হবে এবং সাকসেস মেসেজ রিটার্ন করবে যদি সব স্টেপ সফল হয়। আমরা yield শব্দ যোগ করি এই স্টেপগুলোকে একে অপর সাথে কম্বাইন বা বাইন্ড করতে। এর মানে হলো এই স্টেপগুলোর যেকোনো একটিয় ফেইল মেসেজ deliver_car মেথডের ফেইল মেসেজ হয়ে যায়, এবং যেকোনো একটিয় সাকসেস আউটপুট লিস্টের পরবর্তী স্টেপ কল করার দিকে যিল্ড করে।

def deliver_car(year, model, color, city)
  yield check_year(year)
  yield check_model(model)
  yield check_city(city)
  yield check_color(color)
  Success("A #{color} #{year} Toyota #{model} will be delivered to #{city}")
end

এখন, বাকি সব মেথড যোগ করুন এবং তাদের চেকের রেজাল্ট অনুযায়ী সাকসেস/ফেইল রেজাল্ট আট্যাচ করুন।

def check_year(year)
  year < 2000 ? Failure("We have no cars manufactured in year #{year}") : Success('Cars of this year are available')
end

def check_model(model)
  @available_models.include?(model) ? Success('Model available') : Failure('The model requested is unavailable')
end
def check_color(color)
  @available_colors.include?(color) ? Success('This color is available') : Failure("Color #{color} is unavailable")
end

def check_city(city)
  @nearby_cities.include?(city) ? Success("Car deliverable to #{city}") : Failure('Apologies, we cannot deliver to this city')
end

আমাদের ক্লাস এবং সব দরকারি মেথডগুলো আছে। এটি কীভাবে প্লে আউট করবে? একে নতুন ইনস্ট্যান্স তৈরি করে এবং deliver_car মেথডটি ভিন্ন আর্গুমেন্টস দিয়ে কল করে দেখি।

good_dealer = CarDealership.new
good_dealer.deliver_car(1990, 'Venza', 'red', 'Austin') # Failure("We have no cars manufactured in year 1990")
good_dealer.deliver_car(2005, 'Rav4', 'red', 'Austin') # Failure("The model requested is unavailable")
good_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin') # Failure("Color yellow is unavailable")
good_dealer.deliver_car(2000, 'Venza', 'red', 'Surrey') # Failure("Apologies, we cannot deliver to this city")
good_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin') # Success("A blue 2000 Toyota Avalon will be delivered to Austin")

উপর দেখানো মত, deliver_car মেথডের ফেইল রেজাল্ট ভিন্ন হয় সেটি কোন মেথডে ফেইল হয়েছে তার উপর নির্ভর করে। সেই মেথডের ফেইলুরই এর ফেইলুর হয়ে যায়, এবং সব মেথডের সাফল্যের ওপর তার নিজস্ব সাকসেস রেজাল্ট রিটার্ন করে। এছাড়া, ভুলবেন না যে এই স্টেপগুলো আলাদা মেথড যেগুলো deliver_car মেথড থেকে স্বাধীনভাবে ও কল করা যায়। নিচে একটি উদাহরণ:

good_dealer.check_color('wine') # Failure("Color wine is unavailable")
good_dealer.check_model('Camry') # Success('Model available')

RSpec-এ টেস্টিং

উপরোক্ত কোড টেস্ট করতে, আমাদের spec ফোল্ডারে যেতে হবে এবং car_dealership_spec.rb নামে একটি ফাইল তৈরি করতে হবে spec/models পাথে। প্রথম লাইনে, আমাদের 'rails_helper' রিকোয়ার করতে হবে। আমরা প্রথমে ফেইলুর কন্টেক্স্টে টেস্ট লিখব এবং তারপর সাকসেস।

require 'rails_helper'
describe CarDealership do
  describe "#deliver_car" do
    let(:toyota_dealer) { CarDealership.new }
    context "failure" do
      it "does not deliver a car with the year less than 2000" do
        delivery = toyota_dealer.deliver_car(1990, 'Venza', 'red', 'Austin')
        expect(delivery.success).to eq nil
        expect(delivery.failure).to eq 'We have no cars manufactured in year 1990'
      end
      it "does not deliver a car with the year less than 2000" do
        delivery = toyota_dealer.deliver_car(2005, 'Venza', 'yellow', 'Austin')
        expect(delivery.success).to eq nil
        expect(delivery.failure).to eq 'Color yellow is unavailable'
      end
    end
  end
end

উপর দেখানো মত, আমরা result.failure বা result.success ব্যবহার করে ফেইলুর বা সাকসেস রেজাল্ট অ্যাক্সেস করতে পারি। সাকসেস কন্টেক্স্টের জন্য, টেস্টগুলো এরকম দেখাবে:

context "success" do
  it "delivers a car when all conditions are met" do
    delivery = toyota_dealer.deliver_car(2000, 'Avalon', 'blue', 'Austin')
    expect(delivery.success).to eq 'A blue 2000 Toyota Avalon will be delivered to Austin'
    expect(delivery.failure).to eq nil
  end
end

এখন, আপনি ফেইলুর কন্টেক্স্টে অন্যান্য টেস্ট যোগ করতে পারেন deliver_car মেথডে সরবরাহ করা আর্গুমেন্টস টুইক করে। আপনি আপনার কোডে অন্যান্য চেকও যোগ করতে পারেন এমন পরিস্থিতির জন্য যেখানে ইনভ্যালিড আর্গুমেন্ট প্রদান করা হয় (যেমন, ইয়ার ভেরিয়েবলের জন্য স্ট্রিং দেওয়া হয় এবং অন্য موارد)। আপনার টার্মিনালে bundle exec rspec রান করলে টেস্টগুলো রান হবে এবং দেখাবে সব টেস্ট পাস করেছে। আপনি বেসিক্যালি একসাথে ফেইলুর এবং সাকসেস রেজাল্টের জন্য চেক যোগ করতে হবে না আপনার টেস্টে, কারণ এক মেথডের আউটপুট হিসেবে দুটোই একসাথে থাকতে পারে না। আমি শুধু বোঝার সুবিধার্থে যোগ করেছি সাকসেস রেজাল্ট কেমন দেখে যখন ফেইলুর রেজাল্ট থাকে এবং বিপরীত।

উপসংহার

এটি শুধু dry-monads এবং কীভাবে এটি আপনার অ্যাপে ব্যবহার করা যেতে পারে রেলওয়ে-ওরিয়েন্টেড প্রোগ্রামিং অর্জন করার জন্য একটি ইন্ট্রোডাকশন। এই বেসিক বোঝাপড়া আরও জটিল অপারেশন এবং ট্রানজ্যাকশনে প্রয়োগ করা যেতে পারে। আমরা দেখেছি, একটি ক্লিনার এবং আরও রিডেবল কোড শুধু ROP ব্যবহার করে অর্জনযোগ্য নয়, বরং এরর হ্যান্ডলিং ডিটেইলড এবং কম স্ট্রেসফুল। আপনার প্রসেস tạo করা বিভিন্ন মেথডগুলোর সাথে সংক্ষিপ্ত ফেইলার/সাকসেস মেসেজ আট্যাচ করা স্মরণ রাখুন, কারণ এই পদ্ধতিটি এরর কোথায় এবং কেন ঘটেছে সেটি শনাক্ত করতে সাহায্য করে। আপনি যদি ROP সম্পর্কে আরও জানতে চান, আমরা স্কট উইলশিনের এই প্রেজেন্টেশন দেখার পরামর্শ দিচ্ছি।

  1. ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

  2. Hotwire দিয়ে Rails অ্যাপস দ্রুত করুন: JavaScript ছাড়া সার্ভার-সাইড রেন্ডারিং

  3. রুবি অন রেইলসে টেইলউইন্ড CSS যোগ করা: একটি প্র্যাকটিক্যাল ধাপে ধাপে গাইড

  4. Angular এবং Rails 5 ইন্টিগ্রেশন: একটি স্মুথ ফুল-স্ট্যাক পদ্ধতি