বারবারা লিস্কভের ডেটা অ্যাবস্ট্র্যাকশন: নির্ভরযোগ্য API তৈরি করা
বারবারা লিস্কভের ডেটা অ্যাবস্ট্র্যাকশন নীতিগুলি শিখুন: স্থিতিশীল ইন্টারফেস ডিজাইন করুন, ভাঙ্গন কমান, এবং স্পষ্ট, নির্ভরযোগ্য API দিয়ে রক্ষণযোগ্য সিস্টেম তৈরির উপায় জানুন।

কেন বারবারা লিস্কভ আজও API ডিজাইনের জন্য গুরুত্বপূর্ণ
বারবারা লিস্কভ এমন একজন কম্পিউটার বিজ্ঞানী যাঁর কাজ নিঃশব্দে আধুনিক সফটওয়্যার টিমগুলোকে এমনভাবে তৈরি করতে প্রভাব ফেলেছে যাতে সেগুলো সহজে ভেঙে না যায়। তাঁর গবেষণা ডেটা অ্যাবস্ট্র্যাকশন, ইনফরমেশন হাইডিং এবং পরে লিস্কভ প্রতিস্থাপন নীতি (LSP) প্রোগ্রামিং ভাষা থেকে শুরু করে প্রতিদিন আমরা API সম্পর্কে যেভাবে ভাবি তা প্রভাবিত করেছে: স্পষ্ট আচরণ সংজ্ঞায়িত করুন, অভ্যন্তরীণ বিষয়গুলো রক্ষিত রাখুন, এবং অন্যরা যেন আপনার ইন্টারফেসে নির্ভর করতে পারে সেটি নিশ্চিত করুন।
প্রোডাক্ট শর্তে “বিশ্বাসযোগ্য ইন্টারফেস”
একটি বিশ্বাসযোগ্য API কেবল তাত্ত্বিকভাবে “সঠিক” নয়। এটি এমন একটি ইন্টারফেস যা একটি প্রোডাক্টকে দ্রুতগতিতে এগোতে সাহায্য করে:
- নতুন ফিচার প্রকাশের সময় বিদ্যমান গ্রাহকদের ভেঙে না ফেলা।
- ভার্সনের মধ্যেই ইন্টিগ্রেশনগুলো কাজ চালিয়ে যাওয়া।
- অন-কলে ঘটনার সংখ্যা কমে যাওয়া কারণ ব্যর্থতাগুলো পূর্বানুমানযোগ্য।
- টিমগুলো অভ্যন্তরীণ পরিবর্তন করতে পারে বড় সমন্বয় ছাড়া।
এই বিশ্বাসযোগ্যতা একটি অভিজ্ঞতা: API কল করা ডেভেলপার, তা রক্ষণাবেক্ষণকারী টিম, এবং যাদের উপর তা পরোক্ষভাবে নির্ভর করে এমন ব্যবহারকারীদের জন্য।
ডেটা অ্যাবস্ট্র্যাকশন কীভাবে বাগ (এবং মিটিং) কমায়
ডেটা অ্যাবস্ট্র্যাকশন হচ্ছে ধারণা যে কলাররা একটি কনসেপ্ট (একটি অ্যাকাউন্ট, একটি কিউ, একটি সাবস্ক্রিপশন) সঙ্গে একটি ছোট সেট অপারেশনের মাধ্যমে ইন্টার্যাক্ট করবে—নৌ কিভাবে তা স্টোর করা বা গণনা করা হচ্ছে তার বিশৃঙ্খলতার মাধ্যমে নয়।
যখন আপনি প্রতিনিধিত্বগত বিশদগুলো লুকিয়ে রাখেন, আপনি ভুলের পুরো ক্যাটাগরি সরিয়ে ফেলেন: কেউই “বিরলভাবে” এমন একটি ডাটাবেস ফিল্ডে নির্ভর করতে পারবে না যা প্রকাশ্য হওয়ার কথা ছিল না, বা সিস্টেম মোকাবেলা করতে না সক্ষম এমনভাবে শেয়ার্ড স্টেটকে মিউটেট করতে পারবে না। ততটাই গুরুত্বপূর্ণ, অ্যাবস্ট্র্যাকশন সমন্বয়ের ওভারহেড কমায়: যতক্ষণ পাবলিক আচরণ একরকম থাকে, টিমগুলোর উচিত নয় অভ্যন্তরীণ রিফ্যাক্টর করার জন্য অনুমতি চাওয়া।
এই আর্টিকেল শেষে আপনি কী প্রয়োগ করতে পারবেন
এই লেখার শেষে, আপনার হাতে থাকবে ব্যবহারিক উপায়গুলো:
- API আচরণগুলো স্পষ্ট প্রতিশ্রুতি হিসেবে লিখতে (এজ কেসসহ)।
- ইন্টারফেসগুলো ছোট ও স্থিতিশীল রেখে সিস্টেমগুলো বিকশিত করা।
- কলাররা হ্যান্ডেল করতে পারে এমন পূর্বানুমানযোগ্য ব্যর্থতা মোডগুলো ডিজাইন করা।
দ্রুত সারসংক্ষেপ দেখতে এখানে যান: /blog/a-practical-checklist-for-designing-reliable-apis.
ডেটা অ্যাবস্ট্র্যাকশন — জার্গন ছাড়া ব্যাখ্যা
ডেটা অ্যাবস্ট্র্যাকশন একটি সহজ ধারণা: আপনি কোনো কিছুর সাথে তার কী করে সেই অনুযায়ী ইন্টারঅ্যাক্ট করেন, কিভাবে তা নির্মিত সেটা নয়।
একটি ভেন্ডিং মেশিন ভাবুন। আপনাকে মোটর কিভাবে ঘোরে বা কয়েন কিভাবে গোনা হয় তা জানা দরকার নেই। কেবল নিয়ন্ত্রণগুলো ("আইটেম নির্বাচন", "পে", "আইটেম পাওয়া") এবং নিয়মগুলো ("যদি আপনি যথেষ্ট পরিশোধ করেন, আপনি আইটেম পাবেন; যদি তা বিক্রি হয়ে যায়, রিফান্ড পাবেন") জানা দরকার। এটিই অ্যাবস্ট্র্যাকশন।
“কি করে” বনাম “কিভাবে কাজ করে”
সফটওয়্যারে, ইন্টারফেস হচ্ছে “কি করে”: অপারেশনগুলোর নাম, কোন ইনপুট গ্রহণ করে, কোন আউটপুট দেয়, এবং কোন ত্রুটি আশা করা উচিত। ইমপ্লিমেন্টেশন হচ্ছে “কিভাবে কাজ করে”: ডাটাবেস টেবিল, ক্যাশিং কৌশল, অভ্যন্তরীণ ক্লাস, এবং পারফরম্যান্স ট্রিকস।
এসব আলাদা রেখে আপনি এমন API পাবেন যা সিস্টেম বিকশিত হলেও স্থিতিশীল থাকে। আপনি অভ্যন্তরীন পুনর্লিখন করতে পারেন, লাইব্রেরি বদলাতে পারেন, বা স্টোরেজ অপ্টিমাইজ করতে পারেন—যতক্ষণ ইন্টারফেস ব্যবহারকারীদের জন্য একই থাকে।
এক মিনিটে অ্যাবস্ট্র্যাক্ট ডেটা টাইপ (ADT)
একটি অ্যাবস্ট্র্যাক্ট ডেটা টাইপ মানে হচ্ছে “কন্টেইনার + অনুমোদিত অপারেশন + নিয়ম”, নির্দিষ্ট অভ্যন্তরীণ স্ট্রাকচারে অব্দি অনুগত না হয়ে বর্ণনা করা।
উদাহরণ: একটি Stack (last in, first out).
- push(item): একটি আইটেম যোগ করা
- pop(): সর্বশেষ যোগ করা আইটেমটি সরিয়ে ফিরিয়ে দেওয়া
- peek(): সরানো ছাড়া শীর্ষ আইটেম দেখা
মূল প্রতিশ্রুতি হল: pop() সর্বশেষ push() করা আইটেম প্রদান করে। স্ট্যাক অ্যারে ব্যবহার করে, লিংকড লিস্ট ব্যবহার করে, বা অন্য কিছুও ব্যবহার করুক—সেটা ব্যক্তিগত।
এটা বাস্তব API-তে কিভাবে মানায়
একই পৃথকীকরণ সর্বত্র প্রযোজ্য:
- REST এন্ডপয়েন্টস:
POST /paymentsহচ্ছে ইন্টারফেস; তত্থ্য ফ্রড চেক, রিট্রাই, এবং ডাটাবেজ লেখাগুলো হচ্ছে ইমপ্লিমেন্টেশন। - SDK মেথডস:
client.upload(file)হচ্ছে ইন্টারফেস; চাঙ্কিং, কম্প্রেশন, প্যারালাল রিকোয়েস্ট হচ্ছে ইমপ্লিমেন্টেশন। - UI কম্পোনেন্টস: একটি “DatePicker” props/events প্রকাশ করে; DOM স্ট্রাকচার ও অ্যাক্সেসিবিলিটি বানানো ইমপ্লিমেন্টেশন।
অ্যাবস্ট্র্যাকশনের সাথে ডিজাইন করলে, আপনি যে চুক্তির উপর ব্যবহারকারীরা নির্ভর করে সেটার উপর ফোকাস রাখেন—এবং পর্দার পিছনের সবকিছু বদলালেও তাদের ভাঙতে দেবেন না।
ইনভ্যারিয়েন্টস: সেই লুকানো নিয়মগুলিই সিস্টেম ঠিক রাখে
একটি ইনভারিয়েন্ট হচ্ছে এমন একটি নিয়ম যা একটি অ্যাবস্ট্র্যাকশনের ভেতরে সবসময় সত্য থাকা উচিত। API ডিজাইন করলে ইনভ্যারিয়েন্টগুলো হচ্ছে সেই গার্ডরেইল যা আপনার ডেটাকে অসম্ভব অবস্থা থেকে রক্ষা করে—যেমন একটি ব্যাঙ্ক অ্যাকাউন্টে দুইটা ভিন্ন কারেন্সি থাকা, বা একটি “পরিপূর্ণ” অর্ডার যার কোনো আইটেম নেই।
ইনভ্যারিয়েন্ট কীভাবে দেখায় (গণিতে না বলে)
ইনভ্যারিয়েন্টকে ভাবুন আপনার টাইপের “বাস্তবতার আকৃতি” হিসেবে:
- একটি
Cart-এ নেতিবাচক পরিমাণ থাকতে পারে না। - একটি
UserEmailসদা বৈধ ইমেইল ঠিকানা (পরে “ভ্যালিডেটেড” নয়)। - একটি
Reservation-এstart < end, এবং উভয় সময় একই টাইমজোনে।
যদি এই বিবৃতিগুলো সত্য না থাকে, আপনার সিস্টেম অনির্বচনীয় হয়ে যায়, কারণ প্রতিটি ফিচার এখন অনুমান করে যে “বিকৃত” ডেটা মানে কী।
ইনভ্যারিয়েন্টস ভ্যালিডেশন ও ত্রুটি হ্যান্ডলিং গাইড করে
ভাল API ইনভ্যারিয়েন্টগুলো বর্ডারেই প্রয়োগ করে:
- তৈরির সময়: অবৈধ ইনপুট দ্রুত প্রত্যাখ্যান করুন (স্পষ্ট ত্রুটি ফেরত দিন)।
- আপডেটে: কেবল সেই পরিবর্তনসমূহ অনুমোদন করুন যা ইনভ্যারিয়েন্ট ধরে রাখে।
- পারসিং/IO-তে: বাইরের ডেটাকে অবিশ্বাস্য হিসেবে বিবেচনা করুন; সংরক্ষণের আগে যাচাই করুন।
এটি স্বাভাবিকভাবেই ত্রুটি হ্যান্ডলিং উন্নত করে: অস্পষ্ট ভবিষ্যতের ব্যর্থতার বদলে API বলা যায় কোন নিয়ম ভঙ্গ হয়েছে (“end অবশ্যই start-এর পরে হতে হবে”)।
ইনভ্যারিয়েন্টগুলো ইন্টারফেসে লিক করতে দেবেন না
কলারদেরকে অভ্যন্তরীণ নিয়মগুলো মুখস্থ করে রাখার প্রয়োজন হওয়া উচিত নয়, যেমন “এই মেথডটি কেবল normalize() কল করার পরে কাজ করে।” যদি ইনভ্যারিয়েন্ট কোনো বিশেষ রীতি-অনুশীলনের উপর নির্ভর করে, তাহলে সেটা invariants না—সে একটা ফু্যাটগান।
ইন্টারফেস এমনভাবে ডিজাইন করুন:
- অবৈধ স্টেটগুলো অনুপস্থিত বা কঠিনভাবে তৈরি করা যায়
- মেথডগুলো স্বয়ংক্রিয়ভাবে ইনভ্যারিয়েন্ট রক্ষা করে
ডকুমেন্টেশনের একটি ব্যবহারিক চেকলিস্ট
API টাইপ ডকুমেন্ট করলে লিখে রাখুন:
- ইনভারিয়েন্ট বিবৃতিগুলি (সাদাসিধে ইংরেজি, টেস্টযোগ্য)
- কোথায় প্রয়োগ করা হয় (কনস্ট্রাক্টর, সেটার, এন্ডপয়েন্ট)
- ভঙ্গ হলে কী হয় (ত্রুটি টাইপ/মেসেজ, স্ট্যাটাস কোড)
- কোন মেথডগুলো সেগুলো রক্ষা করে (এবং কোনো ব্যতিক্রম আছে কিনা)
- বৈধ বনাম অবৈধ ইনপুটের উদাহরণগুলো (সংক্ষিপ্ত, কংক্রিট)
কনট্রাক্টস: কলার ও রক্ষণরক্ষকদের জন্য আচরণ স্পষ্ট করা
ভাল একটি API কেবল ফাংশনের সেট নয়—এটি একটি প্রতিশ্রুতি। কনট্রাক্টগুলো সেই প্রতিশ্রুতি স্পষ্ট করে, যাতে কলাররা আচরণে নির্ভর করতে পারে এবং রক্ষণরক্ষকরা অভ্যন্তরীণ পরিবর্তন করলেও অন্য কাউকে অবাক না করে।
কনট্রাক্টে কি কি স্পষ্ট করতে হবে
অন্তত নিম্নলিখিতগুলো ডকুমেন্ট করুন:
- Preconditions: কল করার আগে কী সত্য হতে হবে (মান্য রেঞ্জ, প্রয়োজনীয় অনুমতি, থ্রেড-সেফটি প্রত্যাশা)।
- Postconditions: সফল কলের পরে কী সত্য হবে (রিটার্ন ভ্যালুর মানে, স্টেট পরিবর্তন)।
- Side effects: আর কী বদল হবে (ডিস্ক/নেটওয়ার্ক লেখ, ক্যাশ আপডেট, পাস করা অবজেক্ট মডিফাই) ।
এই স্পষ্টতা আচরণকে পূর্বানুমানযোগ্য করে তোলে: কলাররা জানবে কোন ইনপুট নিরাপদ এবং কোন আউটকামগুলো হ্যান্ডেল করতে হবে, এবং টেস্টগুলো উদ্দেশ্য যাচাই করতে পারবে।
কনট্রাক্টস “ট্রাইবল নলেজ” কমায়
কনট্রাক্ট না থাকলে টিমগুলো স্মৃতি ও অনানুষ্ঠানিক রীতি উপর নির্ভর করে: “সেখানে null পাস করবেন না”, “ওকল কলটি মাঝে মাঝে রিট্রাই করে”, “এরর হলে খালি রিটার্ন করে”। এসব নিয়ম অনবোর্ডিং, রিফ্যাক্টর বা ঘটনাকালীন হারিয়ে যায়।
একটি লিখিত কনট্রাক্ট সেই লুকানো নিয়মগুলোকে শেয়ার্ড জ্ঞানে পরিণত করে। এটি কোড রিভিউয়ের জন্যও স্থিতিশীল লক্ষ্য তৈরি করে: আলোচনা হবে “এ পরিবর্তন কি এখনো কনট্রাক্ট মেনে চলে?” পরিবর্তে “এটা আমার কাছে কাজ করেছে”।
ভাল বনাম অস্পষ্ট শব্দরূপ (উদাহরণ)
অস্পষ্ট: “Creates a user.”
ভালো: “Creates a user with a unique email.
- Preconditions:
emailmust be a valid address; caller must haveusers:createpermission. - Postconditions: returns the new
userId; the user is persisted and immediately retrievable. - Failure modes: returns
409if email already exists; returns400for invalid fields; no partial user is created.”
অস্পষ্ট: “Gets items quickly.”
ভাল: “Returns up to limit items sorted by createdAt descending.
- Side effects: none.
- Consistency: may be up to 60 seconds stale.
- Pagination: use
nextCursorfor the next page; cursors expire after 15 minutes.”
ইনফরমেশন হাইডিং: ইন্টারনালগুলো প্রাইভেট রাখুন, API স্থিতিশীল রাখুন
ইনফরমেশন হাইডিং হচ্ছে ডেটা অ্যাবস্ট্র্যাকশনের ব্যবহারিক পাশ: কলাররা API কী করে তাতে নির্ভর করবে, কিভাবে করে তাতে না। যদি ব্যবহারকারীরা আপনার ইন্টার্নাল দেখতে না পায়, আপনি সেগুলো বদলে দিতে পারবেন প্রতিটি রিলিজ ব্রেকিং করে না।
অপারেশন এক্সপোজ করুন, প্রতিনিধিত্ব নয়
একটি ভাল ইন্টারফেস একটি ছোট অপারেশন সেট প্রকাশ করে (create, fetch, update, list, validate) এবং প্রতিনিধিত্ব—টেবিল, ক্যাশ, কিউ, ফাইল লেআউট, সার্ভিস বাউন্ডারি—প্রাইভেট রাখে।
উদাহরণ: “add item to cart” একটি অপারেশন; আপনার ডাটাবেসের CartRowId একটি ইমপ্লিমেন্টেশন ডিটেইল। যখন আপনি ডিটেইল প্রকাশ করেন, ব্যবহারকারীরা তার উপর নিজেদের লজিক বানাবেন, যা আপনার বদলানোর ক্ষমতাকে জবরদস্তি করে।
ইন্টার্নাল লুকালেই রিফ্যাক্টর নিরাপদ হয়
যখন ক্লায়েন্টরা কেবল স্থিতিশীল আচরণে নির্ভর করে, আপনি করতে পারবেন:
- ডাটাবেস বা স্টোরেজ ফরম্যাট পরিবর্তন করা
- মনোলিথকে সার্ভিসে ভাগ করা
- ক্যাশিং যোগ করা বা ইনডেক্সিং পরিবর্তন করা
- অভ্যন্তরীণ মডেলগুলো পুনর্গঠন করা
এবং API সামঞ্জস্যপূর্ণ থাকবে কারণ চুক্তি নড়েনি। এটিই প্রকৃত রিটার্ন: ব্যবহারকারীদের জন্য স্থিতিশীলতা, রক্ষণকারীদের জন্য স্বাধীনতা।
সাধারণ লিকেজ প্যাটার্নগুলি দেখুন
ইনটার্নাল আকস্মিকভাবে বেরিয়ে আসার কিছু উপায়:
- অভ্যন্তরীণ আইডি ফিরিয়ে দেওয়া যা কেবল আপনার স্টোরেজ লেয়ারে অর্থপূর্ণ (অটো-ইঙ্ক্রিমেন্ট পূর্ণসংখ্যা, শার্ড কী)।
- মিউটেবল স্ট্রাকচার এক্সপোজ করা (উদাহরণ: একটি র ড নোড় অবজেক্ট ফিরিয়ে দেওয়া যা ক্লায়েন্ট প্যাচ করে এবং ফেরত পাঠায়), যা ক্লায়েন্টদের আপনার একেবারেই ক্ষেত্রগুলোর উপর নির্ভর করায়।
- ক্লায়েন্টদের ইন্টার্নাল স্টেট কনস্ট্রাক্ট করতে দেওয়া, যেমন
status=3গ্রহণ করা বরাবর স্পষ্ট নাম বা ডেডিকেটেড অপারেশন ব্যবহার না করে।
স্থিতিশীল রেসপন্স শেইপ ডিজাইন করা
অর্থ নয়, কৌশল ব্যাখ্যা করে এমন রেসপন্সগুলো প্রাধান্য দিন:
- পাবলিক আইডি ব্যবহার করুন যা স্থিতিশীল ও অপ্যাক (যেমন
"userId": "usr_…") ডাটাবেস রো-নম্বরের বদলে। - সংগ্রহের কপিগুলো বা রিড-অনলি ভিউ ফেরত দিন, এমন স্ট্রাকচার নয় যার অর্ডারিং বা অভ্যন্তরীণ ফিল্ডে “অ্যাকসিডেন্টালি” নির্ভর করা হয়।
- ব্যাকওয়ার্ড-কম্প্যাটিবলি ভাবে ফিল্ড যোগ করুন; বিদ্যমান ফিল্ডের মান বদলাবেন না।
কোনো ডিটেইল বদলাতে পারে বলে চিন্তা হলে, সেটি প্রকাশ করবেন না। যদি ব্যবহারকারীরা সত্যিই সেটির প্রয়োজন অনুভব করে, তা deliberateভাবে ইন্টারফেসের অংশ হিসেবে প্রচার করুন ও ডকুমেন্ট করুন।
লিস্কভ প্রতিস্থাপন নীতি — একটি ইন্টারফেস প্রতিশ্রুতি হিসেবে
লিস্কভ প্রতিস্থাপন নীতির এক বাক্যে: যদি কোনো কোড একটি ইন্টারফেস দিয়ে কাজ করে, তখন সেটি যে কোন বৈধ ইমপ্লিমেন্টেশন প্রতিস্থাপন করলেও কাজ চালিয়ে যাবে—কোনো বিশেষ কেস ছাড়া।
LSP ইনহেরিটেন্স সম্পর্কে কম এবং বিশ্বাস সম্পর্কে বেশি। যখন আপনি একটি ইন্টারফেস প্রকাশ করেন, আপনি আচরণ সম্পর্কে একটি প্রতিশ্রুতি দিচ্ছেন। LSP বলে প্রতিটি ইমপ্লিমেন্টেশনই সেই প্রতিশ্রুতি রাখবে, এমনকি এটি সম্পূর্ণ ভিন্ন অভ্যন্তরীণ পন্থা ব্যবহার করুক।
LSP মানে “কলারকে অবাক করবে না”
কলাররা আপনার API যা বলে তার উপর নির্ভর করে—এবং আজ যা করে তা উপর নির্ভর করে না। যদি একটি ইন্টারফেস বলে “আপনি যে কোন বৈধ রেকর্ড দিয়ে save() কল করতে পারেন,” তাহলে প্রতিটি ইমপ্লিমেন্টেশন সেই বৈধ রেকর্ডগুলি গ্রহণ করবে। যদি ইন্টারফেস বলে "get() একটি মান বা স্পষ্ট ‘not found’ ফলাফল দেয়", তাহলে ইমপ্লিমেন্টেশন হঠাৎ করে নতুন ত্রুটি ছুঁড়ে দিবে বা আংশিক ডেটা ফিরিয়ে দেবে না।
নিরাপদ এক্সটেনশন মানে আপনি নতুন ইমপ্লিমেন্টেশন যোগ করতে বা প্রোভাইডার বদলে দিতে পারবেন ক্লায়েন্টদের কোড পুনরায় লেখার বাধ্যবাধকতা ছাড়াই। এটাই LSP-এর ব্যবহারিক লাভ: ইন্টারফেসগুলো সহজে প্রতিস্থাপনযোগ্য রাখা।
API-তে সাধারণ LSP লঙ্ঘন
API চুক্তি ভঙ্গের দুইটি সাধারণ উপায়:
-
কঠোরতর ইনপুট (তীক্ষ্ণ প্রিকন্ডিশন): নতুন ইমপ্লিমেন্টেশন এমন ইনপুট প্রত্যাখ্যান করে যেগুলো ইন্টারফেস ডেফাইনেশন অনুমোদন করেছিল। উদাহরণ: বেস ইন্টারফেস যে কোনো UTF‑8 স্ট্রিং আইডি হিসেবে গ্রহণ করে, কিন্তু একটি ইমপ্লিমেন্টেশন শুধুমাত্র নমেরিক আইডি গ্রহণ করে বা খালি কিন্তু বৈধ ফিল্ডগুলো প্রত্যাখ্যান করে।
-
দুর্বল আউটপুট (কমপ্লিটনেস কমানো): নতুন ইমপ্লিমেন্টেশন প্রতিশ্রুতিটির তুলনায় কম ফেরত দেয়। উদাহরণ: ইন্টারফেস বলে ফলাফলগুলো সাজানো, ইউনিক বা সম্পূর্ণ—কিন্তু একটি ইমপ্লিমেন্টেশন অসাজানো ডেটা, ডুপ্লিকেট বা আইটেম গোপন করে ফেরত দেয়।
একটি তৃতীয় সূক্ষ্ম লঙ্ঘন হচ্ছে ব্যর্থতার আচরণ পরিবর্তন: একটি ইমপ্লিমেন্টেশন “not found” ফেরত দেয়, অন্যটি একই পরিস্থিতিতে এক্সসেপশন ফেলে—এতে কলাররা নিরাপদে প্রতিস্থাপন করতে পারে না।
প্লাগ-ইন আচরণ ডিজাইন করা যাতে অবাক না করে
প্লাগ-ইন সমর্থন করতে ইন্টারফেসকে চুক্তির মতো লিখুন:
- কোন ইনপুট বৈধ তা নির্দিষ্ট করুন এবং সকল ইমপ্লিমেন্টেশনে সেটি সঙ্গতিপূর্ণ রাখুন।
- আউটপুটের মানে নির্ধারণ করুন (অর্ডারিং, ডিফল্ট, এজকেস)।
- ব্যর্থতার মোডগুলো স্ট্যান্ডার্ডাইজ করুন: কোন ত্রুটি ঘটবে এবং সেগুলো কী বোঝায়।
যদি কোনো ইমপ্লিমেন্টেশন বাস্তবে কঠোরতর নিয়ম দাবি করে, একই ইন্টারফেসের নিচে তা লুকাবেন না। (1) আলাদা ইন্টারফেস ডিফাইন করুন, অথবা (2) স্পষ্ট ক্যাপাবিলিটি হিসেবে প্রকাশ করুন—উদাহরণ: supportsNumericIds()—তাহলে ক্লায়েন্ট জ্ঞানভিত্তিকভাবে অপশনে সাইন-আপ করবে, ব্লাইন্ড সাবস্টিটিউশন নয়।
ভাল ইন্টারফেসগুলো ছোট, সংহত এবং পড়তে সহজ
একটি ভাল ডিজাইনকৃত ইন্টারফেস “ব্যবহার করা সহজ” মনে হয় কারণ এটি কেবল কলারের যা দরকার তা প্রকাশ করে—আর তার বেশি নয়। লিস্কভের ডেটা অ্যাবস্ট্র্যাকশন দর্শন আপনাকে সংকীর্ণ, স্থিতিশীল এবং পাঠযোগ্য ইন্টারফেসের দিকে ঠেলে দেয়, যাতে ব্যবহারকারীরা অভ্যন্তরীণ বিবরণ না জেনে নির্ভর করতে পারে।
"সবকিছুকে করা" বনাম সংহত পছন্দ করুন
বিশাল API গুলো প্রায়ই অপ্রাসঙ্গিক দায়িত্ব মিশিয়ে দেয়: কনফিগারেশন, স্টেট চেঞ্জ, রিপোর্টিং ও ট্রাবলশুটিং এক জায়গায়। এতে বোঝা কঠিন হয়ে যায় কখন কি কল করা নিরাপদ।
একটি সংহত ইন্টারফেস একই অ্যাবস্ট্র্যাকশনের অপারেশনগুলোকে গ্রুপ করে। আপনার API যদি একটি কিউ উপস্থাপন করে, তাহলে কেবল কিউ আচরণগুলিই ফোকাস করুন (enqueue/dequeue/peek/size), সাধারণ ইউটিলিটি নয়। কম কনসেপ্ট = কম ভুল ব্যবহারের পথ।
অস্পষ্টভাবে নমনীয় প্যারামিটারগুলো এড়ান
“নমনীয়” প্রায়ই “অসম্পষ্ট” অর্থে আসে। options: any, mode: string, বা একাধিক বুলিয়ান (force, skipCache, silent) এমন কম্বিনেশন তৈরি করে যা স্পষ্টভাবে সংজ্ঞায়িত নয়।
পছন্দ করুন:
- ভিন্ন আচরণের জন্য নির্দিষ্ট মেথড (উদাহরণ:
publish()বনামpublishDraft()), অথবা - ছোট, ভাল-টাইপড অপশন অবজেক্ট যার ডিফল্ট ও অবৈধ কম্বিনেশন ডকুমেন্ট করা আছে।
যদি কোনো প্যারামিটার জানার জন্য কলারকে সোর্স পড়তে হয় তাহলে সেটি একটি ভাল অ্যাবস্ট্র্যাকশনের অংশ নয়।
নামকরণ ইন্টারফেসের অংশ
নামগুলো চুক্তি সংবেদনশীল করে। পর্যবেক্ষণযোগ্য আচরণ বর্ণনা করে এমন ক্রিয়াপদ বেছে নিন: reserve, release, validate, list, get। মজার রূপক বা ওভারলোডেড টার্ম এড়ান। যদি দুটি মেথডই শোনাতে মিলছে, ব্যবহারকারীরা ধরে নেবেন সেগুলো একইরকম—তাই তা নিশ্চিত করুন।
কখন মডিউলে/রিসোর্স এ ভাগ করবেন
এগুলো আলাদা করুন যখন দেখতে পাবেন:
- ভিন্ন ইউজার রোল (উদাহরণ: “admin” বনাম “consumer”) আলাদা ক্ষমতা চায়, অথবা
- ভিন্ন অংশের পরিবর্তন হার আলাদা (একটি অংশ দ্রুত পরিবর্তিত হয়, অন্যটি স্থিতিশীল থাকতে হবে)।
অ্যালাদা মডিউল আপনাকে অভ্যন্তরীণভাবে বিকাশ করতে দেয় যখন মূল প্রতিশ্রুতি স্থিতিশীল রাখা হয়। যদি বৃদ্ধির পরিকল্পনা থাকে, একটি পাতলা “কোর” প্যাকেজ প্লাস অ্যাড-অন বিবেচনা করুন; দেখুন /blog/evolving-apis-without-breaking-users।
ব্যবহারকারীদের ভাঙা ছাড়া API কীভাবে অভিযোজিত করবেন
API বিরলভাবে স্থির থাকে। নতুন ফিচার আসে, এজকেস আবিষ্কৃত হয়, এবং “ছোট উন্নতি” আস্তে আস্তে বাস্তব অ্যাপ্লিকেশন ভাঙতে পারে। লক্ষ্যটি ইন্টারফেস আটকে রাখা নয়—বরং এটি ব্যবহারকারীদের উপর নির্ভর করা চুক্তি ভঙ্গ না করে বিকশিত করা।
সেমান্টিক ভার্সনিং (বাস্তবসম্মত, সীমাবদ্ধতা সহ)
সেমান্টিক ভার্সনিং একটি যোগাযোগের উপায়:
- MAJOR: ব্রেকিং পরিবর্তন।
- MINOR: ব্যাকওয়ার্ড-কম্প্যাটিবলি নতুন ফিচার যোগ।
- PATCH: বাগ ফিক্স যা ইচ্ছাকৃত আচরণ বদলায় না।
সীমা: বিচারবোধ এখনও প্রয়োজন। একটি “বাগ ফিক্স” যদি এমন আচরণ বদলায় যাতে কলাররা নির্ভর করেছিল, তাহলে তা বাস্তবে ব্রেকিং—এটা সংখ্যা নীতিতে ধরবে না।
ব্রেকিং পরিবর্তনগুলো কনট্রাক্ট সংক্রান্ত
অনেক ব্রেকিং পরিবর্তন কম্পাইলারের বাইরে ঘটে:
- ইনপুট নিয়ম কড়াকড়ি করা (আগে গ্রহণ করা মানগুলো প্রত্যাখ্যান করা)।
- মানে বদলানো (একই ফিল্ড, ভিন্ন ব্যাখ্যা)।
- টাইমিং বদলানো (যে কল আগে দ্রুত ছিল এখন স্লো বা ব্লক করে)।
- ত্রুটি আচরণ বদলানো (নতুন ত্রুটি কোড, ভিন্ন রিট্রাই, ভিন্ন আংশিক ফলাফল)।
চিন্তা করুন preconditions ও postconditions এর দিক থেকে: কলাররা কি দিতে হবে, এবং তারা কি ফিরে পাবেন—এসবের উপর।
এমন ডিপ্রিকেশন পথ যা ব্যবহারকারীরা অনুসরণ করতে পারে
ডিপ্রিকেশন কাজ করে যখন তা স্পষ্ট ও টাইম-বাউন্ড:
- পুরোনো আচরণ ডকস ও রেসপন্সে ডিপ্রিকেটেড হিসেবে চিহ্নিত করুন (ওয়ার্নিং, হেডার, লগ)।
- ডুয়াল-সাপোর্ট উইন্ডো দিন (পুরোনো ও নতুন পাশাপাশি চলবে)।
- পরিষ্কার টাইমলাইন ঘোষণা করুন (উদাহরণ: “60 দিনে নতুন ডিফল্ট, 180 দিনে অপসারণ”)।
অ্যাবস্ট্র্যাকশন কীভাবে উন্নয়ন সহজ করে
লিস্কভ-স্টাইল ডেটা অ্যাবস্ট্র্যাকশন সাহায্য করে কারণ এটি সীমিত করে কী উপর ব্যবহারকারীরা নির্ভর করতে পারে। যদি কলাররা কেবল ইন্টারফেস চুক্তিতে নির্ভর করে—না অভ্যন্তরীণ স্ট্রাকচারে—আপনি স্টোরেজ ফরম্যাট, অ্যালগরিদম, অপ্টিমাইজেশন স্বাধীনভাবে পরিবর্তন করতে পারবেন।
প্রায়োগিকভাবে, শক্ত টুলিংও সহায়ক। উদাহরণস্বরূপ, আপনি যদি দ্রুত অভ্যন্তরীণ API-তে ইটারেট করছেন একটি React ওয়েব অ্যাপ বা Go + PostgreSQL ব্যাকএন্ড নির্মাণের সময়, একটি চ্যাট-চালিত বিল্ডার जैसे Koder.ai দ্রুত ইমপ্লিমেন্টেশনে সহায়তা করতে পারে—তবে মূল শৃঙ্খলা অপরিবর্তিত থাকে: পরিষ্কার কনট্রাক্ট, স্থিতিশীল আইডেন্টিফায়ার, ব্যাকওয়ার্ড-কম্প্যাটিবল ইভলিউশান। গতি একটি গুণনকারী—তাই সঠিক ইন্টারফেস অভ্যাসগুলোকে গুণুন।”
সাধারণ প্রশ্ন
বারবারা লিস্কভের কাজ আজও API ডিজাইনের জন্য কেন গুরুত্বপূর্ণ?
তিনি ডেটা অ্যাবস্ট্র্যাকশন ও ইনফরমেশন হাইডিংকে জনপ্রিয় করেছেন, যা আধুনিক API ডিজাইনের সরাসরি অনুবাদ: একটি ছোট, স্থিতিশীল চুক্তি প্রকাশ করুন এবং ইমপ্লিমেন্টেশনকে নমনীয় রাখুন। ফলাফল প্রায়োগিক: কম ব্রেকিং পরিবর্তন, নিরাপদ রিফ্যাক্টর, ও অধিক পূর্বনির্ধারিত ইন্টিগ্রেশন।
প্রোডাক্ট ও ইঞ্জিনিয়ারিং শর্তে “একটি বিশ্বাসযোগ্য ইন্টারফেস” মানে কী?
একটি বিশ্বাসযোগ্য API এমন যে কলাররা সময়ের সাথে নির্ভর করতে পারে:
- নতুন সংস্করণগুলি বিদ্যমান কনজিউমারকে ভেঙে ফেলে না।
- ফলস্বরূপ ত্রুটির ধরনগুলো সুসংহত ও নথিভুক্ত।
- ইন্টারনাল পরিবর্তনগুলো পাবলিক আচরণ বদল না করে করা যায়।
বিশ্বাসযোগ্যতা মানে “কখনও ব্যর্থ না হওয়া” নয় — বরং বিফল হলে পূর্বানুমানযোগ্যভাবে এবং চুক্তি রক্ষা করা।
কিভাবে আমি একটি API এন্ডপয়েন্ট বা মেথডকে স্পষ্ট আচরণগত প্রতিশ্রুতিতে পরিণত করব?
একটি চুক্তি হিসেবে আচরণ লিখুন:
- Preconditions: কলের আগে কী সত্য হতে হবে (মান্য সীমা, অনুমতি)।
- Postconditions: সফল হলে কী সত্য হবে (রিটার্ন ভ্যালু, স্টেট পরিবর্তন)।
- Side effects: আর কী বদল হবে (ডিস্কে লেখা, নেটওয়ার্ক কল, ক্যাশ আপডেট)।
শূন্য ফল, ডুপ্লিকেট, অর্ডারিং—এসব এজ কেসগুলোও অন্তর্ভুক্ত করুন যাতে কলাররা প্রমিস অনুযায়ী ইমপ্লিমেন্ট ও টেস্ট করতে পারে।
ইনভ্যারিয়েন্ট কি, এবং API কোথায় এগুলো প্রয়োগ করা উচিত?
ইনভ্যারিয়েন্ট হলো একটি নিয়ম যা একটি অ্যাবস্ট্র্যাকশনের ভেতরে সর্বদা সত্য থাকা উচিত (যেমন “পরিমাণ কখনই ঋণাত্মক হবে না”)। API-কে নীচের স্থায়ী সীমায় ইনভ্যারিয়েন্ট সঞ্চালন করা উচিত:
- তৈরির সময় ভ্যালিডেশন।
- আপডেটের সময় কেবলই সেই পরিবর্তনগুলি অনুমোদন করা যা ইনভ্যারিয়েন্ট অক্ষুন্ন রাখে।
- অবৈধ ইনপুটগুলোকে দ্রুত ও স্পষ্ট ত্রুটির মাধ্যমে প্রত্যাখ্যান করা।
এভাবে downstream ত্রুটি কমে, কারণ সিস্টেম আর “অসম্ভব” স্টেট নিয়ে হ্যান্ডেল করতে হয় না।
ইনফরমেশন হাইডিং কি, এবং আমি কিভাবে তা রেসপন্স শেইপ ও আইডিতে প্রয়োগ করব?
ইনফরমেশন হাইডিং মানে প্রকাশ করা অপারেশন ও মানে, নয় ইমপ্লিমেন্টেশনের উপস্থাপনা। কনসিউমারকে এমন জিনিসে নির্ভর করা থেকে বিরত করুন যা আপনি ভবিষ্যতে বদলাতে পারেন (টেবিল, ক্যাশ, শার্ড কী, ইন্টারনাল স্ট্যাটাস)।
প্রায়োগিক কৌশল:
- স্থির, অপ্যাক পাবলিক আইডি ব্যবহার করুন (যেমন
usr_...) ডেটাবেজ রো-আইডি নয়। - ক্লায়েন্টকে ইন্টারনাল স্টেট তৈরি করতে বাধ্য করবেন না (উদাহরণ:
status=3এড়িয়ে চলুন)। - ক্ষেত্র যোগ করলে ব্যাকওয়ার্ড-কম্প্যাটিবলি করুন; পুরোনো মানগুলোর অর্থ বদলাবেন না।
কেন ডাটাবেস ধারণাগুলো API-তে ফাঁস হওয়া দীর্ঘমেয়াদে এত বড় সমস্যা?
কারণ ক্লায়েন্ট যখন ডাটাবেস-আকৃতির ফিল্টার, জয়েন কী বা ইনটের্নাল আইডির উপর নির্ভর করে, তখন আপনার ইমপ্লিমেন্টেশন ‘ফ্রোজেন’ হয়ে যায়। স্কিমা পরিবর্তন করা মানে API ভাঙানো হয়ে পরে।
এখানে সিদ্ধান্ত: স্টোরেজকে গোপন রাখুন এবং ক্লায়েন্টকে ডোমেইন-ভিত্তিক প্রশ্ন করতে দিন—যেমন “এক জন কাস্টমারের অর্ডার নির্দিষ্ট তারিখ সীমায়” বলেই বলুন, কিভাবে ডেটা স্টোর করা হয়েছে তা বলবেন না।
লিস্কভ প্রতিস্থাপন নীতি (LSP) বাস্তবে API-তে কী বোঝায়?
LSP মানে: যদি কোড কোনো ইন্টারফেসের সঙ্গে কাজ করে, সেটি অবশ্যই ইন্টারফেসের যেকোনো বৈধ ইমপ্লিমেন্টেশন বদলে দিলেও কাজ করবে—কোনো বিশেষ কেস ছাড়াই। API-র পরিপ্রেক্ষিতে: “কলারকে অবাক করে দিও না”।
প্রতিস্থাপনযোগ্য ইনপ্লিমেন্টেশন সহ করতে, স্ট্যান্ডার্ডাইজ করুন:
- বৈধ ইনপুটসমূহ (কোনো ইমপ্লিমেন্টেশন কঠোর প্রিকন্ডিশন যোগ না করবে)।
- আউটপুট নিশ্চিতকরণ (অর্ডারিং, কমপ্লিটনেস, ইউনিকনেস)।
- ফলফল আচরণ (একই ত্রুটি ও ‘not found’ অর্থ)।
একাধিক ইমপ্লিমেন্টেশন বা প্রোভাইডার থাকলে সাধারণ LSP লঙ্ঘনের উদাহরণগুলো কী?
মনোযোগ রাখুন:
- আক্সরুদ্ধ ইনপুট: নতুন ইমপ্লিমেন্টেশন এমন ইনপুট প্রত্যাখ্যান করে যা ইন্টারফেস আগে গ্রহণ করত।
- কমজোর আউটপুট: আইটেম ড্রপ করা, অরডার বদলানো, অথবা আংশিক ডেটা ফেরত দেওয়া।
- ভিন্ন ব্যর্থতার সেমান্টিক্স: একতরফায় "not found" দেয়, অন্যটাতে এক্সসেপশন।
যদি কোনো ইমপ্লিমেন্টেশনের কড়া শর্ত সত্যিই জরুরি হয়, আলাদা ইন্টারফেস বা স্পষ্ট ক্যাপাবিলিটি ফ্ল্যাগ দিন যেন ক্লায়েন্ট সচেতনভাবে সাইন-আপ করে।
কিভাবে আমি এমন একটি API ডিজাইন করব যা ছোট, সংহত এবং বোঝতে সহজ?
নির্ভরযোগ্য ইন্টারফেস ছোট, সংহত ও পড়তে সহজ হওয়া উচিত:
- একই অ্যাবস্ট্র্যাকশনের অপারেশনগুলো গ্রুপ করুন (অল্প ধারণা = কম ভুল ব্যবহার)।
options: anyবা অনেক বুলিয়ান প্যারামিটার এড়ান; অস্পষ্টতা বাড়ে।- নামাকরণে ক্রিয়াপদ ব্যবহার করুন যা প্রমাণ্য আচরণ বোঝায় (
reserve,release,list,validate)।
যদি ভিন্ন ভূমিকা বা ভিন্ন পরিবর্তন হারের অংশ থাকে, মডিউল আলাদা করুন (আরও পড়ুন: /blog/evolving-apis-without-breaking-users)।
আমি কীভাবে ত্রুটি পরিচালনা ডিজাইন করব যাতে ব্যর্থতা পূর্বানুমানযোগ্য ও টেস্টেবল হয়?
ভুল-উদ্দেশ্যভিত্তিক ব্যর্থতা (প্রোগ্রামার এরর) এবং রানটাইম ব্যর্থতা আলাদা করুন:
- প্রোগ্রামার এরর: কলার চুক্তি ভঙ্গ করেছে — এগুলোকে দ্রুত ও স্পষ্ট ভ্যালিডেশন ত্রুটিতে ধরুন।
- রানটাইম ব্যর্থতা: কলার চুক্তি মেনে চলেছে, কিন্তু বাইরের সিস্টেম ব্যর্থ — এগুলোকে উপস্থাপনযোগ্য ও পুনরায় চেষ্টা করা যায় এমনভাবে ডিজাইন করুন।
একই কনট্রাক্ট অনুযায়ী ব্যর্থির ধরন নির্ধারণ করুন: স্থিতিশীল ত্রুটি কোড ও স্ট্রাকচার্ড ফিল্ড দিন যেন টেস্টগুলো মেসেজ স্ট্রিংয়ের উপর নির্ভর না করে।