ক্লায়েন্ট কিভাবে সেখানে গেল?
বিশদ বিবরণে ডুব দেওয়ার আগে, আসুন বোঝার চেষ্টা করি কীভাবে একটি অ্যাপ এই অবস্থায় শেষ হতে পারে। আমরা একটি সাধারণ users দিয়ে শুরু করি টেবিল কয়েক সপ্তাহ পরে, আমাদের শেষ সাইন ইনের সময় নির্ধারণ করতে সক্ষম হতে হবে যাতে আমরা users.last_sign_in_at যোগ করি . তারপর ব্যবহারকারীর নাম জানতে হবে। আমরা first_name যোগ করি এবং last_name . টুইটার হ্যান্ডেল? আরেকটি কলাম। GitHub প্রোফাইল? ফোন নম্বর? কয়েক মাস পরে টেবিলটি মন দোলা দেয়।
এতে সমস্যা কি?
একটি সারণী যা অনেক বড় সমস্যা নির্দেশ করে:
usersএকাধিক সম্পর্কহীন দায়িত্ব আছে। এটি বোঝা, পরিবর্তন এবং পরীক্ষা করা আরও কঠিন করে তোলে।- অ্যাপ এবং ডাটাবেসের মধ্যে ডেটা আদান-প্রদানের জন্য অতিরিক্ত ব্যান্ডউইথের প্রয়োজন৷
- অ্যাপ্লিকেশানটি ভারী মডেল সংরক্ষণ করতে আরও মেমরির প্রয়োজন৷
অ্যাপটি users নিয়ে এসেছে প্রমাণীকরণ এবং অনুমোদনের উদ্দেশ্যে প্রতিটি অনুরোধে কিন্তু সাধারণত শুধুমাত্র কয়েকটি কলাম ব্যবহার করা হয়। সমস্যা সমাধান করা হলে ডিজাইন এবং কর্মক্ষমতা উভয়ই উন্নত হবে।
একটি টেবিল বের করা
আমরা একটি নতুন টেবিলে (বা টেবিল) কদাচিৎ ব্যবহৃত কলাম বের করে সমস্যার সমাধান করতে পারি . উদাহরণস্বরূপ, আমরা প্রোফাইল তথ্য বের করতে পারি (first_name , ইত্যাদি) profiles-এ নিম্নলিখিত ধাপগুলি সহ:
-
profilesতৈরি করুনusers-এ প্রোফাইল-সম্পর্কিত কলামের নকল কলাম সহ . -
profile_idযোগ করুনusersকাছে . এটিকেNULLএ সেট করুন আপাতত। -
usersপ্রতিটি সারির জন্য ,profiles-এ একটি সারি ঢোকান যা প্রোফাইল-সম্পর্কিত কলামের নকল করে। - পয়েন্ট
profile_idusers-এর সংশ্লিষ্ট সারির 3-এ সন্নিবেশিত সারিতে। - করুন না
users.profile_idতৈরি করুন অ-NULL. অ্যাপটি এখনও তার অস্তিত্ব সম্পর্কে সচেতন নয় তাই এটি ভেঙে যাবে।
আমাদের users.first_name এর রেফারেন্স প্রতিস্থাপন করতে হবে profiles.first_name সহ এবং তাই আমরা যদি মুষ্টিমেয় কিছু রেফারেন্স সহ কয়েকটি কলাম বের করি তাহলে আমি ম্যানুয়ালি এটি করার পরামর্শ দিই। কিন্তু যত তাড়াতাড়ি আমরা নিজেকে ধরি "ওহ, না। এটি এখন পর্যন্ত সবচেয়ে খারাপ কাজ!" আমাদের একটি বিকল্প সন্ধান করা উচিত।
সমস্যাটিকে অবহেলা করবেন না। কোডের একটি অংশ যা প্রত্যেকে এড়িয়ে চলে তা আরও খারাপ হবে এবং আরও বেশি অসাবধানতার শিকার হবে . দুষ্ট বৃত্ত ভাঙ্গার সবচেয়ে সহজ উপায় হল ছোট শুরু করা।
পড়ুন, যদি আপনি জানতে চান কিভাবে আমার ক্লায়েন্ট সমস্যার সমাধান করেছে।
কোডটি একবারে একটি লাইন ঠিক করা
সবচেয়ে ক্রমবর্ধমান পদ্ধতি হল এক সময়ে পুরানো কলামের একটি রেফারেন্স ঠিক করা। চলুন first_name সরানোর উপর ফোকাস করা যাক users থেকে profiles-এ .
প্রথমে, profiles তৈরি করুন সাথে:
rails generate model Profile first_name:string
তারপর users থেকে একটি রেফারেন্স যোগ করুন profiles-এ এবং users.first_name কপি করুন profiles-এ :
class ExtractUsersFirstNameToProfiles < ActiveRecord::Migration
# Redefine the models to break dependency on production code. We need
# vanilla models without callbacks, etc. Also, removing a model in the future
# might break the migration.
class User < ActiveRecord::Base; end
class Profile < ActiveRecord::Base; end
def up
add_reference :users, :profile, index: true, unique: true, foreign_key: true
User.find_each do |user|
profile = Profile.create!(first_name: user.first_name)
user.update!(profile_id: profile.id)
end
change_column_null :users, :profile_id, false
end
def down
remove_reference :users, :profile
end
end
কারণ এটি প্রতিটি ব্যবহারকারীকে ঠিক একটি প্রোফাইল, users থেকে একটি রেফারেন্স রাখতে বাধ্য করে profiles-এ বিপরীত রেফারেন্সের জন্য পছন্দনীয়।
ডাটাবেস কাঠামো ঠিক রেখে, আমরা first_name অর্পণ করতে পারি users থেকে Profile . আমার ক্লায়েন্টের বেশ কিছু প্রয়োজনীয়তা ছিল:
- অ্যাক্সেসরদের সংশ্লিষ্ট
profilesব্যবহার করা উচিত . তাদেরও লগ করা উচিত যেখান থেকে ডেপ্রেকেটেড অ্যাকসেসর কল করা হয়েছে। -
usersসংরক্ষণ করা হচ্ছে স্বয়ংক্রিয়ভাবেprofilesসংরক্ষণ করা উচিত অবচয়িত অ্যাক্সেসর ব্যবহার করে কোড ভাঙা এড়াতে। User#first_name_changed?এবং অন্যান্যActiveModel::Dirtyপদ্ধতিগুলি এখনও কাজ করা উচিত।
এর মানে users এই মত দেখতে হবে:
class User < ActiveRecord::Base
# We need autosave as the client code might be unaware of
# Profile#first_name and still reference User#first_name.
belongs_to :profile, autosave: true
def first_name
log_backtrace(:first_name)
profile.first_name
end
def first_name=(new_first_name)
log_backtrace(:first_name)
# Call super so that User#first_name_changed? and similar still work as
# expected.
super
profile.first_name = new_first_name
end
private
def log_backtrace(name)
filtered_backtrace = caller.select do |item|
item.start_with?(Rails.root.to_s)
end
Rails.logger.warn(<<-END)
A reference to an obsolete attribute #{name} at:
#{filtered_backtrace.join("\n")}
END
end
end
এই পরিবর্তনগুলির পরে, অ্যাপটি একই কাজ করে তবে profiles-এর অতিরিক্ত উল্লেখের কারণে এটি কিছুটা ধীর হতে পারে (যদি কর্মক্ষমতা একটি সমস্যা হয়ে ওঠে শুধু AppSignal এর মত একটি টুল ব্যবহার করুন)। কোডটি লিগ্যাসি অ্যাট্রিবিউটের সমস্ত রেফারেন্স লগ করে, এমনকি অপ্রতিরোধ্য (যেমন user[attr] = ... অথবা user.send("#{attr}=", ...) ) তাই আমরা grep থাকাকালীনও তাদের সকলকে সনাক্ত করতে সক্ষম হব অসহায়৷
এই পরিকাঠামোর জায়গায়, আমরা users.first_name এর একটি রেফারেন্স ঠিক করতে প্রতিশ্রুতিবদ্ধ হতে পারি একটি নিয়মিত সময়সূচীতে, যেমন প্রতিদিন সকালে (একটি দ্রুত জয় দিয়ে দিন শুরু করতে) বা দুপুরের দিকে (একটি ফোকাসড সকালের পরে সহজ কিছুতে কাজ করতে)। এই প্রতিশ্রুতি অপরিহার্য কারণ আমাদের লক্ষ্য হল সমস্যা সমাধানের জন্য মানসিক বাধাগুলি কমানো . কোনো ব্যবস্থা না নিয়ে উপরের কোডটি যথাস্থানে রেখে দিলে অ্যাপটি আরও দরিদ্র হয়ে যাবে।
সমস্ত অপসারিত রেফারেন্স মুছে ফেলার পরে (এবং grep দিয়ে নিশ্চিত করা এবং লগ) আমরা অবশেষে users.first_name বাদ দিতে পারি :
class RemoveUsersFirstName < ActiveRecord::Migration
def change
remove_column :users, :first_name, :string
end
end
আমাদের User-এ যোগ করা কোড থেকেও মুক্তি পাওয়া উচিত যেহেতু এটি আর প্রয়োজন নেই৷
সীমাবদ্ধতা
পদ্ধতিটি আপনার ক্ষেত্রে প্রযোজ্য হতে পারে তবে এর কিছু সীমাবদ্ধতা মনে রাখবেন:
- এটি
User.update_allএর মত বাল্ক প্রশ্নগুলি পরিচালনা করে না . - এটি কাঁচা SQL কোয়েরি পরিচালনা করে না।
- এটি বানর-প্যাচগুলি ভেঙে দিতে পারে (মনে রাখবেন যে নির্ভরতা তাদেরও পরিচয় করিয়ে দিতে পারে)।
usersএবংprofilesসিঙ্কের বাইরে হতে পারে, যদিprofiles.first_nameআপডেট করা হয়েছে কিন্তুusers.first_nameহয় না।
আপনি তাদের কিছু অতিক্রম করতে সক্ষম হতে পারে. উদাহরণস্বরূপ, আপনি মডেলগুলিকে কোনও পরিষেবা বস্তুর সাথে সিঙ্কে রাখতে পারেন বা profiles-এ একটি কলব্যাক রাখতে পারেন . অথবা যদি আপনি PostgreSQL ব্যবহার করেন তাহলে আপনি অন্তর্বর্তী সময়ে একটি বস্তুগত দৃশ্য ব্যবহার করার কথা বিবেচনা করতে পারেন।
এটাই!
নিবন্ধের সবচেয়ে গুরুত্বপূর্ণ পাঠ হল গন্ধযুক্ত কোড এড়িয়ে যাবেন না বরং এটিকে সামলে নিন . যদি কাজটি অপ্রতিরোধ্য হয় তবে নিয়মিত সময়সূচীতে পুনরাবৃত্তিমূলকভাবে কাজ করুন। নিবন্ধটি উপস্থাপন করা হয়েছে a একটি টেবিল নিষ্কাশন করার সময় বিবেচনা করার পদ্ধতি কঠিন। আপনি যদি এটি প্রয়োগ করতে না পারেন তবে অন্য কিছু সন্ধান করুন। যদি আপনার কোন ধারণা না থাকে তবে আমাকে একটি লাইন ড্রপ করুন। আমি সাহায্য করার চেষ্টা করব. আপনার বিট পচা যাক না.