আজকের পোস্টে, আমরা একটি সফটওয়্যার ডিজাইন প্যাটার্ন যেও ফ্যাসেড (Facade) নিয়ে আলোচনা করব। যখন আমি প্রথম مرة এটিকে অ্যাডপ্ট করেছিলাম, তখন কিছুটা অস্বাভাবিক লেগেছিল, কিন্তু যত বেশি আমি আমার রেইলস অ্যাপগুলোতে এটিকে ব্যবহার করলাম, তত বেশি আমি এর উপযোগিতা বুঝতে পারলাম। আরও গুরুত্বপূর্ণভাবে, এটি আমাকে আমার কোড আরও থরোভাবে টেস্ট করতে, আমার কন্ট্রোলারগুলো পরিষ্কার করতে, আমার ভিউতে লজিক কমাতে এবং একটি অ্যাপ্লিকেশনের কোডের সামগ্রিক কাঠামো সম্পর্কে আরও স্পষ্টভাবে চিন্তা করতে সাহায্য করল।
এটি একটি সফটওয়্যার ডেভেলপমেন্ট প্যাটার্ন হওয়ায়, ফ্যাসেড ফ্রেমওয়ার্ক-অগ্নস্টিক (framework agnostic) কিন্তু আমি এখানে যে উদাহরণ দেব সেগুলো রুবি অন রেইলসের জন্য। যাই হোক, আমি আপনাকে এই আর্টিকেল পড়তে এবং আপনার ব্যবহার কिये जाने वाले ফ্রেমওয়ার্ক নির্বিশেষে এগুলো ট্রাই করার জন্য উৎসাহিত করছি। আমি নিশ্চিত যে একবার আপনি এই প্যাটার্নের সাথে পরিচিত হলে, আপনি আপনার কোডবেসের অনেক জায়গায় এটি ব্যবহার করার সুযোগ খুঁজে পাবেন।
বिना দেরি করেই ডুবে যাই!
MVC প্যাটার্নের সমস্যা
MVC (Model-View-Controller) প্যাটার্ন হল একটি সফটওয়্যার ডেভেলপমেন্ট প্যাটার্ন যা ১৯৭০-এর দশকে শুরু হয়েছিল। এটি সফটওয়্যার ইন্টারফেস ডিজাইন করার একটি ব্যাটল-টেস্টেড সমাধান, প্রোগ্রামিং কনসার্নগুলো তিনটি প্রধান গ্রুপে আলাদা করে যারা এক অন্যপাশাপাশি একটি অনন্যভাবে যোগাযোগ করে।
২০০০年代初তে অনেক বড় ওয়েব ফ্রেমওয়ার্ক MVC প্যাটার্নকে ফাউন্ডেশন হিসেবে ব্যবহার করে উত্থান পেল। Spring (জাভার জন্য), Django (পাইথনের জন্য) এবং Ruby on Rails (রুবির জন্য), সবই এই তিনটি সংযুক্ত উপাদানকে কোরে রাখে তৈরি হয়েছিল। এটি ব্যবহার না করে সফটওয়্যার থেকে ফলাফলস্বরূপ স্প্যাগেটি কোডের সাথে তুলনা করলে, MVC প্যাটার্ন সফটওয়্যার ডেভেলপমেন্ট এবং ইন্টারনেটের বিবর্তনে একটি বিশাল অর্জন এবং টার্নিং পয়েন্ট ছিল।
মূলত, Model-View-Controller প্যাটার্ন নিম্নলিখিতগুলোর অনুমতি দেয়: একজন ব্যবহারকারী ভিউতে একটি অ্যাকশন করে। ভিউ একটি কন্ট্রোলারের কাছে রিকোয়েস্ট ট্রিগার করে যা সম্ভবত একটি মডেল তৈরি/পড়া/আপডেট/ডিলিট করতে পারে। মডেল ট্রানজ্যাকশন কন্ট্রোলারের কাছে রেসপন্স করে, যা এমনকি ভিউতে কিছু পরিবর্তন রেন্ডার করে যা ব্যবহারকারী দেখতে পায়।
এই প্রোগ্রামিং প্যাটার্নের অনেক প্রো আছে। কিছু লিস্ট করা যাক:
- কনসার্ন আলাদা করে কোড মেইনটেইনযোগ্যতা উন্নত করে
- বেশির ভালো টেস্টযোগ্যতা অনুমতি দেয় (মডেল, ভিউ এবং কন্ট্রোলার আলাদা আলাদা টেস্ট করা যায়)
- SOLID-এর সিঙ্গল রেসপন্সিবিলিটি প্রিন্সিপল এনফোর্স করে ভালো কোডিং প্র্যাকটিস অনুশীলন করায়: "এক ক্লাসের পরিবর্তনের শুধু একটি কারণ হওয়া উচিত।"
তৎকালীন সময়ের জন্য একটি চমৎকার অর্জন হলেও, ডেভেলপাররা দ্রুত বুঝতে পেরেছিল যে MVC প্যাটার্ন কিছুটা লিমিটিংও। ভেরিয়েন্টগুলো উত্থান পেল, যেমন HMVC (হায়ারার্কিক্যাল মডেল-ভিউ-কন্ট্রোলার), MVA (মডেল-ভিউ-অ্যাডাপ্টার), MVP (মডেল-ভিউ-প্রেজেন্টার), MVVM (মডেল-ভিউ-ভিউমডেল) এবং অন্যান্য, যারা সবই MVC প্যাটার্নের সীমাবদ্ধতা সমাধান করতে চেয়েছিল।
MVC প্যাটার্ন যে সমস্যায় আনে, এবং আজকের আর্টিকেলের বিষয় হল: কমপ্লেক্স ভিউ লজিক হ্যান্ডেল করার দায়িত্ব কার? ভিউ শুধু ডেটা প্রেজেন্ট করার সাথে ചিন্তিত হওয়া উচিত, কন্ট্রোলার শুধু মডেল থেকে পাওয়া মেসেজ রिले করে, এবং মডেল কোনো ভিউ লজিকের সাথে চিন্তিত হওয়া উচিত নয়।
এই সাধারণ সমস্যা সমাধানে সাহায্য করার জন্য, সব রেইলস অ্যাপ্লিকেশনে একটি helpers ডিরেক্টরি থাকে। helper ডিরেক্টরিতে মডিউল থাকতে পারে যাতে মেথড থাকে কমপ্লেক্স ভিউ লজিকে সাহায্য করতে।
এখানে একটি রেইলস অ্যাপ্লিকেশনে হেল্পারের উদাহরণ:
app/helpers/application_helper.rb
module ApplicationHelper def display_ad_type(advertisement) type = advertisement.ad_type case type when 'foo' content_tag(:span, class: "foo ad-#{type}") { type } when 'bar' content_tag(:p, 'bar advertisement') else content_tag(:span, class: "badge ads-badge badge-pill ad-#{type}") { type } end end end
এই উদাহরণটি সহজ হলেও এই টেমপ্লেট থেকে এই ধরনের ডিসিশন মেকিং এক্সট্রাক্ট করার ফ্যাক্টটি প্রদর্শন করে যাতে এর কমপ্লেক্সিটি কমানো যায়।
হেল্পারগুলো ভালো, কিন্তু কমপ্লেক্স ভিউ লজিক হ্যান্ডেল করার জন্য বছরের পর বছর স্বীকৃত আরেকটি প্যাটার্ন আছে, সেটি হল ফ্যাসেড প্যাটার্ন।
ফ্যাসেড প্যাটার্ন পরিচিতি
একটি রুবি অন রেইলস অ্যাপ্লিকেশনে, ফ্যাসেডগুলো সাধারণত app/facades ডিরেক্টরিতে রাখা হয়।
helpers-এর সাথে সাদৃশ্য থাকা সত্ত্বেও, facades একটি মডিউলে মেথডের গ্রুপ নয়। একটি Facade হল একটি PORO (Plain Old Ruby Object) যা কন্ট্রোলারের ভিতরে ইনস্ট্যানশিয়েট হয়, কিন্তু যেটি জটিল ভিউ বিজনেস লজিক হ্যান্ডেল করে। যেহেতু, এটি নিচের সুবিধাগুলো দেয়:
UsersHelperবাArticlesHelperবাBooksHelperএর জন্য একটি সিঙ্গল মডিউল না থাকতে, প্রতিটি কন্ট্রোলার অ্যাকশনের নিজস্ব ফ্যাসেড থাকতে পারে:Users::IndexFacade,Articles::ShowFacade,Books::EditFacade।- মডিউলের চেয়ে বেশি ফ্যাসেডগুলো ভালো কোডিং প্র্যাকটিস অনুশীলন করায় নেস্টেড ফ্যাসেডের মাধ্যমে যাতে সিঙ্গল রেসপন্সিবিলিটি প্রিন্সিপল এনফোর্স হয়। আপনি যেতে পারেন না সैकরো লেভেল ডিপ নেস্টেড ফ্যাসেড, কিন্তু এক বা দুটি লেভেল নেস্টিং মেইনটেইনযোগ্যতা এবং টেস্ট কভারেজ উন্নত করতে পারে।
এখানে একটি উদাহরণ:
module Books class IndexFacade attr_reader :books, :params, :user def initialize(user:, params:) @params = params @user = user @books = user.books end def filtered_books @filtered_books ||= begin scope = if query.present? books.where('name ILIKE ?', "%#{query}%") elsif isbn.present? books.where(isbn: isbn) else books end scope.order(created_at: :desc).page(params[:page]) end end def recommended # এখানে একটি নেস্টেড ফ্যাসেড আছে। # `Recommended Books` ভিউ পার্টের একক দায়িত্ব আছে তাই সেটি এক্সট্রাক্ট করা الأفضل এনক্যাপসুলেশন এবং টেস্টেবিলিটি উন্নত করতে। @recommended ||= Books::RecommendedFacade.new( books: books, user: user ) end private def query @query ||= params[:query] end def isbn @isbn ||= params[:isbn] end end end
ফ্যাসেড প্যাটার্ন কখন ব্যবহার করবেন না
একটু সময় নিই ফ্যাসেড কী নয় সেটি চিন্তা করার জন্য।
ফ্যাসেড ক্লাসগুলোর মধ্যে রাখা উচিত নয় যেগুলো রয়েছে, উদাহরণস্বরূপ,
libডিরেক্টরিতে কোড যা ভিউতে দেখাতে হবে। ফ্যাসেডের লাইফসাইকেল কন্ট্রোলার অ্যাকশনে জেনারেট হওয়া উচিত এবং এর asosheated ভিউতে ব্যবহার করা উচিত।ফ্যাসেড CRUD অ্যাকশন পারফর্ম করার জন্য বিজনেস লজিকের জন্য নয় (সেটির জন্য অন্যান্য প্যাটার্ন আছে, যেমন Services বা Interactors—কিন্তু সেটি অন্য দিনের বিষয়)। অন্য কথায়, ফ্যাসেড তৈরি, আপডেট বা ডিলিট করার সাথে চিন্তিত হওয়া উচিত নয়। তাদের লক্ষ্য হল জটিল প্রেজেন্টেশন লজিক ভিউ বা কন্ট্রোলার থেকে এক্সট্রাক্ট করা এবং সকল তথ্য অ্যাক্সেস করার একটি সিঙ্গল ইন্টারফেস প্রদান করা।
শেষ কিন্তু অবশেষে না, Facades কোনো সিলভার বুলেট নয়। সেগুলো আপনাকে MVC প্যাটার্ন বাইপাস করতে দেয় না, বরং সেগুলোর সাথে খেলে। যদি একটি মডেলে পরিবর্তন হয়, তবে তা অবিলম্বে ভিউতে রিফ্লেক্ট হবে না। যেন MVC-র সাথে সবসময় হয়, কন্ট্রোলার অ্যাকশন রি-রেন্ডার হওয়া প্রয়োজন ফ্যাসেড ভিউতে পরিবর্তনগুলো দেখানোর জন্য।
কন্ট্রোলার সুবিধা
ফ্যাসেডের একটি প্রধান, স্পষ্ট সুবিধা হল এটি আপনাকে কন্ট্রোলার লজিক নাটকিয়ভাবে কমাতে দেয়।
আপনার কন্ট্রোলার কোড এমন থেকে:
class BooksController < ApplicationController def index @books = if params[:query].present? current_user.books.where('name ILIKE ?', "%#{params[:query]}%") elsif params[:isbn].present? current_user.books.where(isbn: params[:isbn]) else current_user.books end @books.order(created_at: :desc).page(params[:page]) @recommended = @books.where(some_complex_query: true) end end
একদম এটিতে পরিণত হবে:
class BooksController < ApplicationController def index @index_facade = Books::IndexFacade.new(user: current_user, params: params) end end
ভিউ সুবিধা
ভিউগুলোর জন্য, ফ্যাসেড ব্যবহার করার দুটি প্রধান সুবিধা আছে:
- কন্ডিশনাল চেক, ইনলাইন কোয়েরি এবং অন্যান্য লজিক টেমপ্লেট থেকে সুন্দরভাবে এক্সট্রাক্ট করা যায় কোড অনেক বেশি রিডেবল করার জন্য। উদাহরণস্বরূপ, আপনি একটি ফর্মে এটি ব্যবহার করতে পারেন:
<%= f.label :location %> <%= f.select :location, options_for_select(User::LOCATION_TYPES.map { |type| [type.underscore.humanize, type] }.sort.prepend(['All', 'all'])), multiple: (current_user.active_ips.size > 1 && current_user.settings.use_multiple_locations?) %>
সেটি শুধু এভাবে হতে পারে:
<%= f.label :location %> <%= f.select :location, options_for_select(@form_facade.user_locations), multiple: @form_facade.multiple_locations? %>
- বহুবার কল হওয়া ভেরিয়েবলগুলো ক্যাশ করা যায়। এটি আপনার অ্যাপে উল্লেখযোগ্য পারফরম্যান্স ইম্প্রুভমেন্ট আনতে পারে এবং пески N+1 কোয়েরি সরাতে সাহায্য করে:
// ভিউতে কোথাও একটি কোয়েরি পারফর্ম হয়। <% current_user.books.where(isbn: params[:isbn]).each do |book| %> // Do things <% end %> // ভিউতে অন্য কোথাও, একই কোয়েরি আবার পারফর্ম হয়। <% current_user.books.where(isbn: params[:isbn]).each do |book| %> // Do things <% end %>
এতে পরিণত হবে:
// ভিউতে কোথাও একটি কোয়েরি পারফর্ম হয়। <% @index_facade.filtered_books.each do |book| %> // Do things <% end %> // ভিউতে অন্য কোথাও। // সেকেন্ড কোয়েরি পারফর্ম হয় না ইনস্ট্যান্স ভেরিয়েবল ক্যাশিংয়ের কারণে। <% @index_facade.filtered_books.each do |book| %> // Do things <% end %>
টেস্টিং সুবিধা
ফ্যাসেডের একটি বড় সুবিধা হল এটি আপনাকে সিঙ্গুলার বিজনেস লজিকের বিটগুলো টেস্ট করতে দেয় পুরো কন্ট্রোলার টেস্ট না লিখেও, বা بدতর, একটি ইন্টিগ্রেশন টেস্ট না লিখেও যা একটি ফ্লো থ্রু করে একটি পেজে পৌঁছায় শুধু ডেটা প্রেজেন্টেশন অপেক্ষা কিনা তা নিশ্চিত করার জন্য।
যেহেতু আপনি সিঙ্গুলার POROs টেস্ট করবেন, এটি দ্রুত টেস্ট স্যুট মেইনটেইন করতে সাহায্য করবে।
এখানে Minitest-এ লিখা একটি সিম্পল টেস্ট উদাহরণ ডেমোনের জন্য:
require 'test_helper' module Books class IndexFacadeTest < ActiveSupport::TestCase attr_reader :user, :params setup do @user = User.create(first_name: 'Bob', last_name: 'Dylan') @params = {} end test "#filtered_books returns all user's books when params are empty" index_facade = Books::IndexFacade.new(user: user, params: params) expectation = user.books.order(created_at: :desc).page(params[:page]) # পুরো কন্ট্রোলার টেস্ট বা # ইন্টিগ্রেশন টেস্ট না লিখেও, আমরা চেক করতে পারি ফ্যাসেড ব্যবহার করে # খালি প্যারামিটারসহ করলে সঠিক ফলাফল ইউজারের কাছে ফেরত দেয় কিনা। assert_equal expectation, index_facade.filtered_books end test "#filtered_books returns books matching a query" @params = { query: 'Lord of the Rings' } index_facade = Books::IndexFacade.new(user: user, params: params) expectation = user .books .where('name ILIKE ?', "%#{params[:query]}%") .order(created_at: :desc) .page(params[:page]) assert_equal expectation, index_facade.filtered_books end end end
ফ্যাসেড ইউনিট টেস্ট করা টেস্ট স্যুট পারফরম্যান্স উল্লেখযোগ্যভাবে উন্নত করে, এবং প্রতিটি বড় কোম্পানি sluttish টেস্ট স্যুটের সম্মুখীন হবে যদি এই সমস্যাগুলো কিছুটা সিরিয়াসলি এড্রেস না করা হয়।
এক ফ্যাসেড, দুই ফ্যাসেড, তিন ফ্যাসেড, আরও?
আপনি এমন একটি সিনারিওতে পড়তে পারেন যেখানে একটি ভিউ একটি পার্শিয়াল রেন্ডার করে যেটি কিছু ডেটা আউটপুট করে। সেক্ষেত্রে, আপনার অপশন আছে পেরেন্ট ফ্যাসেড ব্যবহার করবেন নাকি নেস্টেড ফ্যাসেড। এটি বড়াংশে নির্ভর করে কতটুকু লজিক ইনভল্ভড, আপনি কি আলাদা টেস্ট করতে চান এবং কি ফাংশনালিটি এক্সট্রাক্ট করা মেনে চলে।
কতগুলো ফ্যাসেড ব্যবহার করবেন বা কতগুলো নেস্ট করবেন এর জন্য কোনো গোল্ডেন রুল নেই। এটি ডেভেলপারের বিবেচনায় রয়েছে। আমি সাধারণত একটি কন্ট্রোলার অ্যাকশনের জন্য একটি ফ্যাসেড পছন্দ করি এবং নেস্টিং এক লেভেলে লিমিট করি কোড অনুসরণ করা সহজ রাখতে।
এখানে কিছু সাধারণ প্রশ্ন যা আপনি ডেভেলপমেন্টের সময় নিজে থেকে জিজ্ঞেস করতে পারেন:
- কি ফ্যাসেডটি আমি ভিউতে প্রেজেন্ট করতে চাই সেই লজিক এনক্যাপসুলেট করছে?
- ফ্যাসেডের মেথডটি এই কনটেক্সটে মেনে চলে কিনা?
- কোডটি এখন কি সহজে অনুসরণ করা যায়, নাকি কঠিন?
সন্দেহ হলে, সবসময় চেষ্টা করুন আপনার কোড যতটা সম্ভব সহজে অনুসরণ করা যায় সেটি বানাতে।
উপসংহার
উপসংহারে, ফ্যাসেডগুলো আপনার কন্ট্রোলার ও ভিউ স্লিম রাখতে, কোড মেইনটেইনযোগ্যতা, পারফরম্যান্স এবং টেস্টেবিলিটি উন্নত করতে একটি চমৎকার প্যাটার্ন।
তবে, যেকোনো প্রোগ্রামিং প্যারাডাইমের মতো, কোনো সিলভার বুলেট নেই। недавিত বছরে উত্থান পেয়েছে এমন প্যাটার্নের বহুলতা (HMVC, MVVM, ইত্যাদি) সফটওয়্যার ডেভেলপমেন্টের জটিলতার বি-অল-এন্ড-অল সমাধান নয়।
থার্মোডাইনামিক্সের দ্বিতীয় নিয়মের মতো, যা বলে একটি ক্লোজড সিস্টেমে এনট্রপির অবস্থা সবসময় বৃদ্ধি পাবে, যেকোনো সফটওয়্যার প্রজেক্টে কমপ্লেক্সিটি সময়ের সাথে সাথে বৃদ্ধি পায় এবং বিবর্তন পায়। দীর্ঘমেয়াদে, লক্ষ্য হল এমন কোড লিখা যা যতটা সম্ভব সহজে পড়া, টেস্ট করা, মেইনটেইন করা এবং অনুসরণ করা যায়; ফ্যাসেড সেটি সরাসরি অফার করে।
P.S. আপনি যদি Ruby Magic পোস্টগুলো প্রেস থেকে বের होते সাথে সাথে পড়তে চান, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একটি পোস্টও মিস করবেন না!