7 মিনিট

Postgres স্কিমা ডিজাইন পরিকল্পনা মোড: ধাপে ধাপে পদ্ধতি

Postgres স্কিমা পরিকল্পনা মোড আপনাকে এনটিটি, কনস্ট্রেইন্ট, ইনডেক্স এবং মাইগ্রেশন কোড জেনারেশনের আগে নির্ধারণ করতে সাহায্য করে—পরে রি-রাইট কমায়।

Postgres স্কিমা ডিজাইন পরিকল্পনা মোড: ধাপে ধাপে পদ্ধতি

কেন কোড জেনারেট করার আগে আপনার Postgres স্কিমা পরিকল্পনা করবেন

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

বেশিরভাগ রি-রাইট তিনটি পূর্বানুমেয় সমস্যার কারণে ঘটে:

  • এনটিটিগুলো পরিষ্কার নয় (নামকরণ বা গঠন খারাপ ছিল)।
  • নিয়মগুলো বাধ্যতামূলক করা হয়নি (কনস্ট্রেইন্ট অনুপস্থিত)।
  • কর্মক্ষমতা সমস্যা পরে প্রকাশ পায় (ইনডেক্স ব্যবহারকারী অভিযোগের পরে যোগ করা হয়েছে)।

প্রতিটি পরিবর্তন কোড, টেস্ট, এবং ক্লায়েন্ট অ্যাপে ছড়িয়ে দেয়।

Postgres স্কিমা পরিকল্পনা করার মানে হলো প্রথমে ডেটা কন্ট্রাক্ট নির্ধারণ করা, তারপর সেই অনুযায়ী কোড জেনারেট করা। বাস্তবে এর অর্থ হলো এনটিটি, সম্পর্ক, এবং কয়েকটি গুরুত্বপুর্ন কুয়েরি লিখে নেওয়া, তারপর কনস্ট্রেইন্ট, ইনডেক্স, এবং মাইগ্রেশন পদ্ধতি বেছে নেওয়া—যখনও টুল টেবিল বা CRUD স্ক্যাফোল্ড না করে।

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

পরিকল্পনা না করলে সাধারণত এগুলোই ভুল হয়:

  • একটি “User” টেবিল ধীরে ধীরে users, admins, এবং teams মিশিয়ে দেয়।
  • ইউনিকনেস নিশ্চিত না থাকায় ডুপ্লিকেট রেকর্ড আসে।
  • ডিলিট ইতিহাস ভেঙে দেয় কারণ রেফারেন্স আচরণ আগে নির্ধারিত হয়নি।
  • একটি জনপ্রিয় তালিকা পেজ ধীর হয়ে যায় কারণ সঠিক ইনডেক্স পরিকল্পনা করা হয়নি।

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

টেবিলের চেয়ে ডেটা চাহিদা দিয়ে শুরু করুন

স্কিমা পরিকল্পনা সবচেয়ে ভালো কাজ করে যখন আপনি অ্যাপকে কী মনে রাখতে হবে এবং ব্যবহারকারীরা ডেটা দিয়ে কী করতে পারবে তা দিয়ে শুরু করেন। লক্ষ্যটি 2–3 সহজ বাক্যে লিখুন। যদি সহজে ব্যাখ্যা না করতে পারেন, সম্ভবত অপ্রয়োজনীয় অতিরিক্ত টেবিল তৈরি করবেন।

এরপর, এমন অ্যাকশনগুলোর দিকে মনোযোগ দিন যেগুলো ডেটা তৈরি বা পরিবর্তন করে। এই অ্যাকশনগুলোই আসলে আপনার সারির উৎস এবং এরা বলে দেয় কী যাচাই করা দরকার। নিউনবাচক নয়—ক্রিয়াপদের দিকে চিন্তা করুন।

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

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

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

যদি আপনি পরে কোড জেনারেট করতে যান, এই নোটগুলো শক্তিশালী প্রম্পট হয়ে যায়। এটা বলে দেয় কী লাগবে, কী পরিবর্তনশীল, এবং কী সার্চেবল হতে হবে। Koder.ai ব্যবহার করলে, জেনারেট করার আগে এগুলো লিখলে Planning Mode অনেক বেশি কার্যকর হয় কারণ প্ল্যাটফর্ম অনুমানের বদলে বাস্তব চাহিদা থেকে কাজ করে।

সাধারণ ভাষায় এনটিটি ও সম্পর্ক নির্ধারণ করুন

টেবিল ছোঁয়ার আগে আপনার অ্যাপ কী সংরক্ষণ করে তা সাধারণ ভাষায় লিখুন। প্রায়ই বারবার যেসব নাম আসে সেগুলো তালিকাভুক্ত করুন: user, project, message, invoice, subscription, file, comment। প্রতিটি নাম একটি সম্ভাব্য এনটিটি।

তারপর প্রতিটি এনটিটির জন্য একটি বাক্য যোগ করুন: এটা কী এবং কেন আছে? উদাহরণ: “A Project is a workspace a user creates to group work and invite others.” এটা অস্পষ্ট টেবিল যেমন data, items, বা misc এড়াতে সাহায্য করবে।

অধিকার (ownership) পরবর্তী বড় সিদ্ধান্ত এবং এটি প্রায় প্রতিটি কুয়েরিকে প্রভাবিত করে। প্রতিটি এনটিটির জন্য সিদ্ধান্ত নিন:

  • কে এটা তৈরি করে,
  • কে এটা দেখতে পারে,
  • কে এটা পরিবর্তন করতে পারে,
  • মালিক মুছে গেলে কী হবে (রাখবে, ট্রান্সফার করবে, নাকি মুছে দেবে)।

এখন সিদ্ধান্ত নিন কীভাবে রেকর্ড সনাক্ত করবেন। UUID ভাল যখন রেকর্ড বহু জায়গা থেকে তৈরি হতে পারে (ওয়েব, মোবাইল, ব্যাকগ্রাউন্ড জব) অথবা অনুমেয় আইডি চান না। Bigint ছোট ও দ্রুত। যদি মানুষ-মুখী আইডেন্টিফায়ার দরকার হয়, আলাদা রাখুন (যেমন একটি সংক্ষিপ্ত project_code যা অ্যাকাউন্টের মধ্যে ইউনিক) প্রাইমারি কী হিসেবে জোর না করে।

শেষে, সম্পর্কগুলো শব্দে লিখুন আগে ডায়াগ্রাম করার: a user has many projects, a project has many messages, এবং users অনেক projects-এ থাকতে পারে। প্রতিটি লিংককে required বা optional হিসাবে মার্ক করুন, যেমন “a message must belong to a project” বনাম “an invoice may belong to a project।” এই বাক্যগুলো পরে কোড জেনারেশনের জন্য সূত্র হবে।

এনটিটিকে টেবিল ও কলামে আলাদাভাবে অনুবাদ করুন

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

নাম ও টাইপ নিয়ে এমন প্যাটার্ন বেছে নিন যা আপনি ধরে রাখতে পারবেন। অনুশাসন: snake_case কলাম নাম, একই ধারণার জন্য একই টাইপ, এবং পূর্বানুমেয় প্রাইমারি কী। টাইমস্ট্যাম্পের জন্য timestamptz ব্যবহার করুন যাতে টাইমজোন পরে সমস্যা না তৈরি করে। অর্থের জন্য numeric(12,2) (অথবা সেন্ট হিসেবে একটি integer) ব্যবহার করুন, float নয়।

স্ট্যাটাস ফিল্ডের জন্য Postgres enum বা text কলাম + CHECK কনস্ট্রেইন্ট বেছে নিন যাতে অনুমোদিত মানগুলো নিয়ন্ত্রিত হয়।

কোনটা required বা optional হবে তা NOT NULL এ অনুবাদ করে সিদ্ধান্ত নিন। যদি একটি মান না থাকলে সারি অর্থহীন হয়, তাহলে তা required করুন; যদি আসলেই অজানা বা প্রযোজ্য না হয়, null অনুমতি দিন।

একটি ব্যবহারিক ডিফল্ট কলাম সেট:

  • id (uuid বা bigint, এক পদ্ধতি বেছে নিন এবং লেগে থাকুন)
  • created_at এবং updated_at
  • deleted_at কেবল যদি আপনি সত্যিই সফট ডিলিট চান এবং রিস্টোর করার দরকার থাকে
  • created_by যখন আপনাকে স্পষ্ট অডিট ট্রেইল দরকার

Many-to-many সম্পর্ক সাধারণত join টেবিলে রূপ নেয়। উদাহরণস্বরূপ, যদি একাধিক ব্যবহারকারী একটি অ্যাপে সহযোগীতা করতে পারে, তৈরি করুন app_members যাতে app_id এবং user_id আছে, তারপর জোড়ায় ইউনিকনেস জোর দিন যাতে ডুপ্লিকেট না ঘটে।

ইতিহ্য পরিকল্পনা সম্পর্কে ভাবুন। যদি আপনি জানেন ভার্সনিং লাগবে, একটি ইমিউটেবল টেবিল পরিকল্পনা করুন যেমন app_snapshots, যেখানে প্রতিটি সারি একটি সেভড ভার্সন এবং app_id দ্বারা লিঙ্কড এবং created_at দ্বারা স্ট্যাম্প করা থাকে।

আপনার ডেটা রক্ষার জন্য কনস্ট্রেইন্ট যোগ করুন

Index for real screens
Use your top 5 query patterns to guide indexes before performance becomes a fire drill.

কনস্ট্রেইন্ট আপনার স্কিমার গার্ডরেইল। সিদ্ধান্ত নিন কোন নিয়মগুলো সত্য হতে হবে যেকোন সার্ভিস, স্ক্রিপ্ট, বা অ্যাডমিন টুল ডাটাবেসে লিখুক।

পরিচয় ও সম্পর্ক থেকে শুরু করুন। প্রতিটি টেবিলে একটি প্রাইমারি কী দরকার, এবং কোন “belongs to” ফিল্ড হলে তা আসল foreign key হওয়া উচিত, শুধুমাত্র একটি integer নয়।

তারপর ইউনিকনেস যোগ করুন যেখানে ডুপ্লিকেট বাস্তব ক্ষতি করবে, যেমন একই ইমেইল সহ দুইটি অ্যাকাউন্ট বা একই (order_id, product_id) সহ দুইটি লাইন আইটেম।

উচ্চ-মানের কনস্ট্রেইন্ট যা আগে পরিকল্পনা করা উচিত:

  • Primary keys: একটি সঙ্গতিশীল স্টাইল বেছে নিন (UUID বা bigint) যাতে joins পূর্বানুমেয় থাকে।
  • Foreign keys: সম্পর্ক স্পষ্ট করুন এবং orphan সারি প্রতিরোধ করুন।
  • Unique constraints: ব্যবসায়িক পরিচয়ের জন্য (email, username) এবং “শুধুমাত্র একটি” নিয়মের জন্য ব্যবহার করুন।
  • Check constraints: সস্তা নিয়ম যেমন amount >= 0, status IN ('draft','paid','canceled'), বা rating BETWEEN 1 AND 5
  • Not null: বাস্তবে বাধ্যতামূলক ক্ষেত্রগুলোর জন্য

ক্যাসকেড আচরণ এমন জায়গা যেখানে পরিকল্পনা পরে আপনাকে বাঁচায়। ভাবুন ব্যবহারকারীরা আসলে কী প্রত্যাশা করবে। যদি একটি কাস্টমার মুছে যায়, তাদের অর্ডার সাধারণত অদক্ষী হওয়া উচিত না—এতে ইতিহাস রাখা উচিত। এই ক্ষেত্রে RESTRICT ভাল। অন্যদিকে নির্ভরশীল ডেটার জন্য যেমন order line items, প্যারেন্ট মুছলে CASCADE বুঝদার হতে পারে কারণ আইটেমগুলির কোন স্বাধীন মানে নেই।

যখন পরে আপনি মডেল ও এন্ডপয়েন্ট জেনারেট করবেন, এই কনস্ট্রেইন্টগুলো পরিষ্কার আইটেম হয়ে যাবে: কোন ত্রুটি ধরতে হবে, কোন ফিল্ড বাধ্যতামূলক, এবং কোন এজ-কেস ডিজাইন দ্বারা অসম্ভব।

বাস্তব কুয়েরি থেকে ইনডেক্স পরিকল্পনা করুন

ইনডেক্সগুলো একটাই প্রশ্নের উত্তর দেয়: বাস্তব ব্যবহারকারীদের জন্য কি দ্রুত হতে হবে।

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

কোনো ইনডেক্স বেছে নেওয়ার আগে ৫–১০টি কুয়েরি প্যাটার্ন সাদারন ভাষায় লিখে নিন। উদাহরণ: “Show my invoices for the last 30 days, filter by paid/unpaid, sort by created_at,” বা “Open a project and list its tasks by due_date.” এটা ইনডেক্স সিদ্ধান্তগুলোকে বাস্তব ব্যবহার অনুযায়ী রাখে।

ভাল প্রথম সেট সাধারণত foreign key কলামগুলো (joins-এ ব্যবহৃত), সাধারণ ফিল্টার কলাম (যেমন status, user_id, created_at), এবং একটি-দুটি কম্পোজিট ইনডেক্স যা স্থিতিশীল মルটিফিল্টার কেসগুলোর জন্য—যেমন (account_id, created_at) যখন আপনি সবসময় account_id দিয়ে ফিল্টার করে সময় অনুসারে সাজান।

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

প্রতি সম্ভাব্য ক্ষেত্রে ইনডেক্স যোগ করবেন না। প্রতিটি ইনডেক্স INSERT ও UPDATE-এ ওভারহেড বাড়ায় এবং বিরল কিন্তু খারাপ কুয়েরির চেয়ে তা বেশি ক্ষতিকর হতে পারে।

টেক্সট সার্চ আলাদা পরিকল্পনা করুন। যদি শুধু সরল “contains” ম্যাচিং দরকার, প্রথমে ILIKE যথেষ্ট হতে পারে। যদি সার্চ কোর ফিচার হয়, তখন আগে থেকেই full-text search (tsvector) পরিকল্পনা করুন যাতে পরে পুনরায় ডিজাইন করতে না হয়।

কোড জেনারেট করার আগে আপনার মাইগ্রেশন কৌশল নির্ধারণ করুন

একবার প্রথম টেবিল তৈরি হলে স্কিমা “শেষ” হয় না। এটি প্রতিটি ফিচার যোগ করা, ভুল ঠিক করা, বা ডেটা সম্পর্কে আরও জানার সাথে বদলে যায়। যদি আপনি শুরুতেই আপনার মাইগ্রেশন কৌশল ঠিক করেন, তাহলে কোড জেনারেশনের পরে কষ্টকর রি-রাইট এড়ানো যায়।

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

ব্রেকিং চেঞ্জ নিরাপদে কীভাবে হ্যান্ডেল করবেন

বেশিরভাগ ব্রেকিং চেঞ্জ কলাম রিনেম বা রিমুভ করা, অথবা টাইপ পরিবর্তন থেকে আসে। সবকিছু একবারে করার বদলে একটি নিরাপদ পথ পরিকল্পনা করুন:

  • প্রথমে নতুন কলাম যোগ করুন (প্রয়োজন হলে nullable), তারপর ডেটা ব্যাকফিল করুন।
  • যদি অ্যাপ পরিবর্তনের সময় লাইভ থাকতে হয়, সংক্ষিপ্ত সময়ের জন্য dual-write ব্যবহার করুন (পুরানো ও নতুন উভয় ফিল্ডে লিখুন)।
  • পড়া নতুন ফিল্ডে স্ফটল করে দিন, তারপর পরের মাইগ্রেশনে পুরানো কলাম ক্লিন আপ করুন।

এটি ধাপে ধাপে হলেও বাস্তবে দ্রুত কাজ করে কারণ এটি আউটেজ ও জরুরী প্যাচ কমায়।

সিড ডেটাও মাইগ্রেশনের অংশ। সিদ্ধান্ত নিন কোন রেফারেন্স টেবিলগুলো “সবসময় আছে” (roles, statuses, countries, plan types) এবং সেগুলো পূর্বানুমেয় রাখুন। এই টেবিলের জন্য ইনসার্ট ও আপডেট আলাদা মাইগ্রেশনে রাখুন যাতে প্রতিটি ডেভেলপার ও প্রতিটি ডিপ্লয় একরকম ফল পায়।

লোকাল, ডেভ এবং প্রোডে কনসিস্টেন্সি নিয়ম

শুরুতেই প্রত্যাশা সেট করুন:

  • একই মাইগ্রেশন, একই ক্রম, সব জায়গায়।
  • প্রোডাকশন-এ কোনো ম্যানুয়াল হটফিক্স নেই যদি না তার মিলছে এমন মাইগ্রেশন থাকে।
  • প্রতিটি মাইগ্রেশনের একটি স্পষ্ট ফরোয়ার্ড স্টোরি এবং বাস্তবসম্মত রোলব্যাক প্ল্যান আছে।

রোলব্যাক সবসময় নিখুঁত “down migration” নাও হতে পারে। কখনও কখনও সবচেয়ে ভালো রোলব্যাক হলো ব্যাকআপ রিস্টোর। যদি আপনি Koder.ai ব্যবহার করেন, ঝুঁকিপূর্ণ পরিবর্তনের আগে স্ন্যাপশট ও রোলব্যাকের উপর নির্ভর করা সুবিধাজনক হতে পারে।

একটি সহজ উদাহরণ যা আপনি কপি করে সমন্বয় করতে পারেন

APIs shaped by constraints
Create endpoints that respect keys, constraints, and delete rules from day one.

ধরা যাক একটি ছোট SaaS অ্যাপ যেখানে মানুষ টিমে যোগ দেয়, প্রজেক্ট তৈরি করে, এবং টাস্ক ট্র্যাক করে।

শুরু করুন এনটিটিগুলো ও প্রথম দিনের জন্য দরকারি শুধুমাত্র ফিল্ডগুলো তালিকা করে:

  • users: id, email, full_name, created_at
  • teams: id, org_id, name, created_at
  • team_members: team_id, user_id, role, joined_at
  • projects: id, team_id, name, status, created_at
  • tasks: id, project_id, assignee_user_id (nullable), title, state, due_date (nullable), created_at

সম্পর্কগুলো সরল: একটি team-এর অনেক projects আছে, একটি project-এ অনেক tasks আছে, এবং users team_members মারফত টিমে যোগ দেয়। টাস্ক একটি project-এ থাকে এবং কোনো ব্যবহারকারীকে নিয়োগ করা থাকতে পারে।

এখন কয়েকটি কনস্ট্রেইন্ট যোগ করুন যা সাধারণত পরে বাগ দেয়:

  • users.email ইউনিক করুন (case-insensitive করতে চাইলে citext ব্যবহার করুন)।
  • একই org-এর মধ্যে টিম নাম ইউনিক করুন: UNIQUE (org_id, name) on teams.
  • ডুপ্লিকেট মেম্বারশিপ প্রতিরোধ করুন: UNIQUE (team_id, user_id) on team_members.

ইনডেক্সগুলো বাস্তব স্ক্রিনের সাথে মিলিয়ে পরিকল্পনা করুন। উদাহরণ: যদি টাস্ক তালিকা project এবং state দিয়ে ফিল্টার করে এবং newest অনুযায়ী সাজায়, তাহলে একটি ইনডেক্স রাখতে পারেন tasks (project_id, state, created_at DESC)। যদি “My tasks” গুরুত্বপূর্ণ ভিউ হয়, তাহলে tasks (assignee_user_id, state, due_date) মতো ইনডেক্স সাহায্য করবে।

মাইগ্রেশনের জন্য প্রথম সেট নিরাপদ ও সরল রাখুন: টেবিল তৈরি, প্রাইমারি কী, ফরেন কী, এবং মূল ইউনিক কনস্ট্রেইন্ট। পরে ব্যবহার প্রমাণ করলে যোগ করুন—উদাহরণস্বরূপ টাস্কে সফট ডিলিট (deleted_at) যোগ করে “active tasks” ইনডেক্স সামঞ্জস্য করা।

সাধারণ ভুল ও কীভাবে এড়াবেন

বেশিরভাগ রি-রাইট হয় কারণ প্রথম স্কিমায় নিয়ম ও বাস্তব ব্যবহার বিবরণ অনুপস্থিত ছিল। একটি ভালো পরিকল্পনা পারফেকশন নয়—এটা জাল ছিদ্র আগে চিহ্নিত করার কাজ।

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

আরেকটি সাধারণ ত্রুটি হলো ইনডেক্সকে পরে দেখার অনুমান করা। লঞ্চের পরে ইনডেক্স যোগ করা প্রায়শই আন্দাজভিত্তিক হয়ে যায়, এবং আপনি ভুল জিনিস ইনডেক্স করতে পারেন, যখন আসল ধীর কুয়েরি হতে পারে একটি join বা status ফিল্টার।

Many-to-many টেবিলও নীরবে বাগের উৎস। যদি আপনার join টেবিল ডুপ্লিকেট প্রতিরোধ না করে, একই সম্পর্ক বারবার সংরক্ষিত হতে পারে এবং আপনি ঘণ্টার পর ঘণ্টা ডিবাগ করতে থাকতে পারেন যে “কেন এই ব্যবহারকারী দ্বিগুণ রো?”

এছাড়া সহজে টেবিল তৈরি করার পরে আপনি বুঝতে পারেন অডিট লগ, সফট ডিলিট, বা ইভেন্ট ইতিহাস দরকার। এসব পরিবর্তন এন্ডপয়েন্ট ও রিপোর্টে প্রভাব ফেলে।

শেষে, JSON কলাম লোভনীয় কারণ “ফ্লেক্সিবল” বলে মনে হয়, কিন্তু তারা যাচাই ও ইনডেক্সিং কঠিন করে দেয়। JSON ঠিক আছে যদি সত্যিই ভ্যারিয়েবল পে-লোড থাকে—কোর ব্যবসায়িক ফিল্ডগুলোতে নয়।

জেনারেট করার আগে এই দ্রুত চেকলিস্টটি চালান:

  • মূল নিয়মগুলো কনস্ট্রেইন্টে স্থানান্তর করুন: NOT NULL, CHECK, UNIQUE, এবং foreign keys।
  • ৩ থেকে ৫টি বাস্তব কুয়েরি লিখুন এবং তাদের জন্য ইনডেক্স পরিকল্পনা করুন, “হয়তো পরে” নয়।
  • Join টেবিলগুলিতে একটি কম্পোজিট UNIQUE যোগ করুন (উদাহরণ: user_id + role_id)।
  • আগে থেকেই নির্ধারণ করুন কি অডিট ইতিহাস দরকার এবং এটিকে আলাদা টেবিল হিসেবে মডেল করুন।
  • JSON কেবল বিরল, ঐচ্ছিক অ্যাট্রিবিউটের জন্য রাখুন, গুরুত্বপূর্ণ ফিল্ডগুলোকে কলামে উন্নীত করুন।

মডেল ও এন্ডপয়েন্ট জেনারেট করার আগে দ্রুত চেকলিস্ট

Own the generated source
Keep full control by exporting source code after you generate your app.

এখানে একটি দ্রুত প্রি-ফ্লাইট চেক যা নিশ্চিত করবে প্ল্যান এতটাই সম্পূর্ণ যাতে কোড জেনারেট করে আপনি বারবার সমস্যা না খুঁজে থাকেন। লক্ষ্য পারফেকশন নয়—এটি সেই গ্যাপগুলো ধরা যা পরে রি-রাইটের কারণ হয়ে দাঁড়ায়: অনুপস্থিত সম্পর্ক, অস্পষ্ট নিয়ম, এবং ইনডেক্স যা অ্যাপের ব্যবহার মেলায় না।

  • প্রতিটি এনটিটির জন্য এক বাক্যে লিখুন কে এটাকে মালিকানাভুক্ত করে (user, org, system) এবং “deleted” মানে কী (hard delete, soft delete, archived)।
  • প্রতিটি টেবিলের জন্য কলাম টাইপ, required বনাম optional, ডিফল্ট, এবং টাইমস্ট্যাম্প কিভাবে সেট হবে (অ্যাপ না ডাটাবেস) নিশ্চিত করুন।
  • এমন নিয়মগুলো লিখুন যা কখনও ভাঙ্গা যাবে না: প্রাইমারি কী, ফরেন কী, ইউনিকনেস, এবং কয়েকটি সরল চেক (যেমন amount >= 0 বা অনুমোদিত স্ট্যাটাস)।
  • আপনার শীর্ষ ৩–৫ স্ক্রিন বা API কল বাছুন এবং সেগুলোর নির্দিষ্ট ফিল্টার ও সোর্ড অর্ডার তালিকা করুন। নিশ্চিত করুন আপনার ইনডেক্সগুলো মিলছে।
  • প্রথম মাইগ্রেশন খসড়া করুন: প্রাথমিক স্কিমা, দিন একে যে ডেটা থাকা উচিত (seed rows), এবং একটি পরিবর্তন যা শিগগিরই প্রত্যাশা করা (যেমন একটি স্ট্যাটাস যোগ করা, নাম ফিল্ড বিভক্ত করা, org পরিচয় চালু করা)।

একটি দ্রুত স্বাভাবিকতা পরীক্ষা: কল্পনা করুন একজন সহকর্মী আগামীকাল যোগ দিল। তারা কি প্রথম এন্ডপয়েন্ট তৈরি করতে পারবে প্রতিঅর্ধেক ঘণ্টায় প্রতিটি ক্ষেত্রে “এটি null হতে পারে?” অথবা “ডিলিট হলে কী হয়?” জিজ্ঞাসা করে না? যদি না পারে, আপনার পরিকল্পনা আরো স্পষ্ট করা দরকার।

পরবর্তী ধাপ: পরিকল্পনা থেকে কোডে যাওয়া কম রি-রাইট দিয়ে

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

একটি প্রাথমিক মাইগ্রেশন দিয়ে শুরু করুন যা টেবিল, টাইপ (যদি আপনি enum ব্যবহার করেন), এবং must-have কনস্ট্রেইন্ট তৈরি করে। প্রথম উত্তোলন ছোট কিন্তু সঠিক রাখুন। একটু সিড ডেটা লোড করুন এবং আপনার অ্যাপের যে কুয়েরিগুলো বাস্তবে প্রয়োজন সেগুলো চালান। যদি কোনো ফ্লো অদ্ভুত লাগে, মাইগ্রেশন ইতিহাস এখনও ছোট থাকা অবস্থায় স্কিমা ঠিক করুন।

টেবিল, কী, এবং নামকরণ এতটাই স্থির হলে কেবল তখনই মডেল ও এন্ডপয়েন্ট জেনারেট করুন যাতে পরেরদিন আপনি সবকিছু রিনেম না করছেন।

একটি কার্যকরী লুপ যা রি-রাইট কম রাখে:

  • টেস্ট থেকে কিছু শিখলে প্ল্যান আপডেট করুন।
  • একটি নতুন মাইগ্রেশন যোগ করুন (অন্যান্যরা এটি ব্যবহার করার পরে পুরানো মাইগ্রেশন এডিট করবেন না)।
  • স্কিমা বদলে গেলে মডেল ও এন্ডপয়েন্ট পুনরায় জেনারেট করুন।
  • একই ফ্লো ও কুয়েরি পুনরায় চালান নিশ্চিত করতে যে কিছু ভাঙেনি।
  • কোর পাথগুলো স্থির মনে হলে অতিরিক্ত ফিল্ড ও ইনডেক্স যোগ করুন।

শুরুতেই ঠিক করুন আপনি কোনটা ডাটাবেসে যাচাই করবেন বনাম API লেয়ারে। স্থায়ী নিয়মগুলো ডাটাবেসে রাখুন (ফরেন কী, ইউনিক কনস্ট্রেইন্ট, চেক কনস্ট্রেইন্ট)। নরম নিয়মগুলো API-তে রাখুন (ফিচার ফ্ল্যাগ, অস্থায়ী সীমা, এবং জটিল ক্রস-টেবিল লজিক যা প্রায়ই পরিবর্তিত হয়)।

যদি আপনি Koder.ai ব্যবহার করেন, একটি বুদ্ধিমান পদ্ধতি হলো প্রথমে Planning Mode-এ এনটিটি ও মাইগ্রেশন নিয়ে একমত হওয়া, তারপর Go প্লাস PostgreSQL ব্যাকএন্ড জেনারেট করা। যখন কোনো পরিবর্তন সমস্যা করে, স্ন্যাপশট ও রোলব্যাক দ্রুত পরিচিত-ভাল ভার্সনে ফিরতে সাহায্য করতে পারে যতক্ষণ আপনি স্কিমা প্ল্যান সামঞ্জস্য করেন।

সাধারণ প্রশ্ন

Postgres স্কিমা কেন মডেল ও এন্ডপয়েন্ট জেনারেট করার আগে পরিকল্পনা করা উচিত?

Plan the schema first. It sets a stable data contract (tables, keys, constraints) so generated models and endpoints don’t need constant renames and rewrites later.

In practice: write your entities, relationships, and top queries, then lock in constraints, indexes, and migrations before you generate code.

ডায়াগ্রাম ছাড়া দ্রুত স্কিমা পরিকল্পনা শুরু করার দ্রুত উপায় কী?

Write 2–3 sentences describing what the app must remember and what users must be able to do.

Then list:

  • The actions that create/change data (verbs)
  • The screens/reports people will use (read paths)
  • Non-functional needs like audit history, soft deletes, multi-tenancy, and privacy

This gives you enough clarity to design tables without overbuilding.

কীভাবে আমি জানতে পারি আমার কোর এনটিটিরা কী?

Start by listing the nouns you keep repeating (user, project, invoice, task). For each one, add one sentence: what it is and why it exists.

If you can’t describe it clearly, you’ll likely end up with vague tables like items or misc and regret it later.

Postgres-এ আমাকে কি UUID ব্যবহার করা উচিত নাকি bigint IDs?

Default to a single consistent ID strategy across your schema.

  • UUIDs: great when records can be created from many places (web/mobile/jobs) or you don’t want predictable IDs
  • bigint: smaller, a bit faster, and simpler when everything is server-created

If you need a human-friendly identifier, add a separate unique column (like project_code) instead of using it as the primary key.

ফরেন কি করে ডিলিট আচরণ নির্বাচন করব (CASCADE বনাম RESTRICT)?

Decide it per relationship based on what users expect and what must be preserved.

Common defaults:

  • Keep history: use RESTRICT/NO ACTION when deleting a parent would erase important records (like customers → orders)
  • Safe cascading: use CASCADE when child rows have no meaning without the parent (like order → line items)

Make this decision early because it affects API behavior and edge cases.

প্রথম দিনে কোন কোন কনস্ট্রেইন্ট যোগ করা উচিত?

Put permanent rules in the database so every writer (API, scripts, imports, admin tools) is forced to behave.

Prioritize:

  • PRIMARY KEY on every table
  • FOREIGN KEY for every “belongs to” column
  • UNIQUE where duplicates cause real harm (email, (team_id, user_id) in join tables)
  • CHECK for simple rules (non-negative amounts, allowed statuses)
  • NOT NULL for fields required for the row to make sense
আমি কীভাবে ওভার-ইন্ডেক্স করা ছাড়া উপযুক্ত ইনডেক্স বাছাই করব?

Start from real query patterns, not guesses.

Write 5–10 plain-English queries (filters + sort), then index for those:

  • Foreign key columns used in joins
  • Common filters like status, user_id, created_at
  • A few composite indexes that match stable patterns (for example, (account_id, created_at))

Avoid indexing everything; each index slows inserts and updates.

Many-to-many সম্পর্ক কীভাবে সঠিকভাবে মডেল করব?

Create a join table with two foreign keys and a composite unique constraint.

Example pattern:

  • team_members(team_id, user_id, role, joined_at)
  • Add UNIQUE (team_id, user_id) to prevent duplicates

This prevents subtle bugs like “why does this user appear twice?” and keeps your queries clean.

Postgres-এ কোন টাইপ ও কলাম প্যাটার্ন ভাল ডিফল্ট?

Default to:

  • timestamptz for timestamps (fewer time zone surprises)
  • numeric(12,2) or integer cents for money (avoid floats)
  • Enforced status values via Postgres enums or CHECK constraints

Keep types consistent across tables (same type for the same concept) so joins and validations stay predictable.

কোন মাইগ্রেশন কৌশল জেনারেটেড কোড পরে ভাঙ্গা এড়াতে সাহায্য করে?

Use small, reviewable migrations and avoid breaking changes in one step.

A safe path:

  • Add new columns first (nullable if needed)
  • Backfill data
  • Temporarily dual-write if the app must stay live
  • Switch reads to the new column
  • Remove old columns later

Also decide upfront how you’ll handle seed/reference data so every environment matches.

Related posts