পাঠ ৩৫ · ৫৭-এর মধ্যে · মডিউল ৮
Home / Courses / Mobile App Development / লোকাল স্টোরেজ

মোবাইলে ডেটা মাইগ্রেশন

Data migrations on mobile
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • মাইগ্রেশন কেন দরকার — অ্যাপ আপডেট হলে লোকাল স্কিমাও কীভাবে বদলাতে হয়, ডেটা না হারিয়ে
  • একটি মাইগ্রেশন-ফাংশন-তালিকা + current_version ট্র্যাকার প্যাটার্ন
  • কলাম যোগ করা বনাম ফিল্ড রিনেম করা — দুটো ভিন্ন ধরনের মাইগ্রেশন
  • একাধিক মাইগ্রেশন ধারাবাহিকভাবে প্রয়োগ করে চূড়ান্ত স্কিমা যাচাই করা

১ · মাইগ্রেশন কেন দরকার

ধরুন আপনার নোট-অ্যাপের প্রথম সংস্করণে notes টেবিলে শুধু id, title, body কলাম ছিল। পরের সংস্করণে আপনি একটি "পিন করা" ফিচার যোগ করলেন — এর জন্য একটি নতুন pinned কলাম দরকার। কিন্তু যেসব ইউজার আগের সংস্করণ ব্যবহার করছেন, তাদের ডিভাইসে ইতিমধ্যে ডেটাসহ পুরনো স্কিমার টেবিল আছে — নতুন অ্যাপ ইনস্টল হওয়ার সাথে সাথে সেই টেবিলটি নিরাপদে নতুন আকৃতিতে বদলাতে হবে, বিদ্যমান নোটগুলো না হারিয়ে। এই ধাপে ধাপে, সংস্করণ-ট্র্যাকড পরিবর্তনকেই মাইগ্রেশনMigrationএকটি ডেটাবেস স্কিমাকে এক সংস্করণ থেকে পরের সংস্করণে নিরাপদে রূপান্তর করার একটি ছোট, নামযুক্ত, ধারাবাহিক ধাপ। বলা হয়।

Full-Stack Web Frameworks কোর্সের সাথে সম্পর্ক

Full-Stack Web Frameworks কোর্সের M7 মাইগ্রেশন পাঠে এই একই "মাইগ্রেশন-ফাংশন-তালিকা + current_version ট্র্যাকার" প্যাটার্নটি একটি ওয়েব ব্যাকএন্ডের users টেবিলে প্রয়োগ করা হয়েছে (ইমেইল কলাম যোগ, ফিল্ড রিনেম, রোলব্যাক-সহ)। এই পাঠ সেই ধারণা পুনরায় শেখাবে না — শুধু সেই একই প্যাটার্ন একটি মোবাইল অ্যাপের লোকাল স্কিমায় কীভাবে প্রয়োগ হয় তা দেখাবে।

২ · একটি ভার্সন-ট্র্যাকড মাইগ্রেশন তালিকা

নিচের কোড সেলে notes টেবিলের স্কিমায় তিনটি মাইগ্রেশন ধারাবাহিকভাবে প্রয়োগ করা হচ্ছে — একটি কলাম যোগ (pinned), একটি ফিল্ড রিনেম (body থেকে content), আরেকটি কলাম যোগ (updated_at)। প্রতিটি মাইগ্রেশন ফাংশন copy.deepcopy দিয়ে একটি নতুন স্কিমা কপি তৈরি করে বদলায়, মূল স্কিমা সরাসরি না বদলিয়ে।

Python
import copy

# ---------- মোবাইল "notes" টেবিলের স্কিমা মাইগ্রেশন ----------

def migration_001_add_pinned(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["pinned"] = "boolean"
    return new_schema

def migration_002_rename_body_to_content(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["content"] = new_schema["columns"].pop("body")
    return new_schema

def migration_003_add_updated_at(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["updated_at"] = "timestamp"
    return new_schema

MIGRATIONS = [
    ("0001_add_pinned", migration_001_add_pinned),
    ("0002_rename_body_to_content", migration_002_rename_body_to_content),
    ("0003_add_updated_at", migration_003_add_updated_at),
]

schema = {"table": "notes", "columns": {"id": "integer", "title": "string", "body": "string"}}
current_version = 0

print(f"ভার্সন {current_version} (শুরু): {schema}")

for name, apply_fn in MIGRATIONS:
    schema = apply_fn(schema)
    current_version += 1
    print(f"ভার্সন {current_version} ({name}): {schema}")

# ---------- চূড়ান্ত স্কিমা প্রত্যাশিত আকৃতির সাথে মিলছে কি না যাচাই ----------
expected_final = {
    "table": "notes",
    "columns": {
        "id": "integer",
        "title": "string",
        "content": "string",
        "pinned": "boolean",
        "updated_at": "timestamp",
    },
}

matches = schema == expected_final
print(f"\nচূড়ান্ত স্কিমা প্রত্যাশিত আকৃতির সাথে হুবহু মিলছে: {matches}")
assert matches
print(f"চূড়ান্ত ভার্সন: {current_version}")

    
লক্ষ্য করুন migration_002_rename_body_to_content-এ new_schema["columns"].pop("body") দিয়ে পুরনো body কী-এর মানটি বের করে এনে সরাসরি নতুন কী content-এ বসানো হয়েছে — এটাই একটি রিনেমের সবচেয়ে সহজ উপস্থাপন, শুধু del করলে কলামের ডেটা-টাইপ তথ্যসহ পুরো কলামটিই হারিয়ে যেত। চূড়ান্ত schema == expected_final তুলনাটি Python-এ কী-অর্ডার নয়, শুধু কী-ভ্যালু জোড়াগুলো মেলে কি না তা যাচাই করে — তাই তিনটি মাইগ্রেশনের পর কলামগুলোর প্রকৃত ইনসার্শন-অর্ডার (id, title, pinned, content, updated_at) প্রত্যাশিত ডিকশনারির লেখার ক্রম থেকে আলাদা হলেও matches ঠিকই True হয়।
মূল কথা · Key takeaway

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

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ প্রতিটি মাইগ্রেশন ফাংশন কেন মূল schema dict সরাসরি না বদলিয়ে copy.deepcopy দিয়ে একটি নতুন কপি তৈরি করে বদলায়?

যদি মূল schema সরাসরি বদলানো হতো, তাহলে সেই একই dict-এর অন্য কোনো রেফারেন্স (যেমন লগিং বা আগের কোনো স্ন্যাপশট) নিঃশব্দে বদলে যেত, কারণ Python-এ dict মিউটেবল। deepcopy নিশ্চিত করে প্রতিটি মাইগ্রেশন ধাপ একটি স্বাধীন নতুন অবজেক্ট তৈরি করে, যা মাইগ্রেশন সিস্টেমকে অনেক বেশি পূর্বানুমানযোগ্য করে তোলে।

প্র ০২ যদি MIGRATIONS তালিকায় migration_002_rename_body_to_content-এর আগে migration_003_add_updated_at বসানো হতো, তাহলে চূড়ান্ত স্কিমার উপর কি কোনো প্রভাব পড়ত?

এই নির্দিষ্ট উদাহরণে চূড়ান্ত ফলাফল (কলামগুলো ও তাদের টাইপ) একই থাকত, কারণ দুটো মাইগ্রেশন একে অপরের উপর নির্ভরশীল নয় (একটি body-কে content-এ রিনেম করে, আরেকটি সম্পূর্ণ নতুন updated_at কলাম যোগ করে)। কিন্তু বাস্তবে ক্রম সবসময় গুরুত্বপূর্ণ — যদি কোনো মাইগ্রেশন আরেকটির তৈরি করা কলামের উপর নির্ভর করত (যেমন একটি মাইগ্রেশন content কলাম রিনেম করত, যা প্রথমে migration_002-এর তৈরি করা), তাহলে ক্রম বদলালে সেটি KeyError দিয়ে ব্যর্থ হতো।

প্র ০৩ assert matches লাইনটি যদি ব্যর্থ হতো (অর্থাৎ matches False হতো), তাহলে কী ঘটত এবং এটি কেন একটি গুরুত্বপূর্ণ সেফটি-নেট?

assert False একটি AssertionError ছুঁড়ে কোড সেলের বাকি অংশ থামিয়ে দিত — অর্থাৎ current_version প্রিন্ট হওয়ার আগেই এক্সিকিউশন বন্ধ হয়ে যেত। এটি একটি গুরুত্বপূর্ণ সেফটি-নেট কারণ এটি নিশ্চিত করে মাইগ্রেশনগুলো ঠিক যা প্রত্যাশিত ঠিক তাই করেছে কি না তা কোডেই যাচাই হয় — শুধু prose-এ দাবি করা নয়, বাস্তব সমতা-চেক দিয়ে প্রমাণিত।

অনুশীলন

  1. চিন্তা করুন: আপনার একটি "টু-ডু" মোবাইল অ্যাপে tasks টেবিলে একটি priority কলাম যোগ করতে চান, যার ডিফল্ট মান হওয়া উচিত "medium" — শুধু কলাম যোগ করলেই কি যথেষ্ট, নাকি বিদ্যমান রেকর্ডগুলোর জন্য অতিরিক্ত কিছু ভাবতে হবে?

    শুধু স্কিমায় কলাম যোগ করলেই যথেষ্ট নয় — বিদ্যমান রেকর্ডগুলোতে (যেগুলো নতুন কলাম আসার আগেই তৈরি হয়েছে) কোনো priority মান থাকবে না। একটি সম্পূর্ণ মাইগ্রেশনে তাই দুটো কাজ থাকা উচিত — স্কিমায় কলাম যোগ করা, এবং প্রতিটি বিদ্যমান রেকর্ডে priority = "medium" ব্যাকফিল করা, যাতে পুরনো ডেটাও নতুন কলামের সাথে সামঞ্জস্যপূর্ণ থাকে।

  2. পরীক্ষা করুন: কোড সেলে MIGRATIONS তালিকায় একটি চতুর্থ মাইগ্রেশন যোগ করুন যা title কলামটি রিনেম করে heading করে দেয় (আগের migration_002-এর প্যাটার্ন অনুসরণ করে), তারপর expected_final ডিকশনারিটিও সেই অনুযায়ী আপডেট করে দেখুন assert matches এখনও পাস করে কি না।

    নতুন মাইগ্রেশন ফাংশনে new_schema["columns"]["heading"] = new_schema["columns"].pop("title") লিখে সেটিকে MIGRATIONS তালিকার শেষে একটি নতুন টাপল হিসেবে যোগ করলে, এবং expected_final["columns"]-এ "title"-এর বদলে "heading": "string" রাখলে, assert matches ঠিকই পাস করবে — কারণ চূড়ান্ত স্কিমার কী-ভ্যালু জোড়াগুলো এখন expected_final-এর সাথে হুবহু মিলে যাবে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Full-Stack Web Frameworks — মাইগ্রেশন ও স্কিমা বিবর্তন সহোদর কোর্স এই একই মাইগ্রেশন-ফাংশন-তালিকা + ভার্সন-ট্র্যাকার প্যাটার্ন একটি ওয়েব ব্যাকএন্ডের প্রেক্ষাপটে (রোলব্যাক-সহ) সেই পাঠে বিস্তারিত দেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।
আগের পাঠ
ফাইল স্টোরেজ ও ক্যাশিং স্ট্র্যাটেজি