যারা ড্রপশিপিং স্টোর খোলেন, তাদের বেশিরভাগই শর্টকাট বা সহজ উপায়ের সন্ধানে থাকেন। তারা উইনিং প্রোডাক্ট (winning products) খোঁজার জন্য ফোরাম স্ক্রল করেন, সস্তা ভার্চুয়াল অ্যাসিস্ট্যান্ট নিয়োগ করেন এবং আশা করেন যে অ্যালগরিদম তাদের রাতারাতি ধনী করে দেবে। এটি কখনোই আমাকে আকর্ষণ করেনি। আমি ড্রপশিপিংকে একটি ইঞ্জিনিয়ারিং সমস্যা হিসেবে দেখেছিলাম। আমি দ্রুত টাকার পেছনে ছুটছিলাম না। আমি ইনভেন্টরি সিঙ্কিং (inventory syncing) সমাধান করতে চেয়েছিলাম, বাজারের প্রকৃত পরিবর্তনের সাথে সামঞ্জস্যপূর্ণ প্রাইসিং অ্যালগরিদম তৈরি করতে চেয়েছিলাম এবং মানসিক ভারসাম্য না হারিয়ে সাপ্লায়ার API-গুলোর সাথে কাজ করতে চেয়েছিলাম। Node.js এবং PostgreSQL ব্যবহার করে আমি যে সিস্টেমটি তৈরি করেছিলাম, স্টোরটি ছিল তার একটি পার্শ্বপ্রতিক্রিয়া মাত্র।
স্টোরটিকে একটি ব্যাকএন্ড সার্ভিস হিসেবে বিবেচনা করুন
আপনি যখন ড্রপশিপিংকে একটি মার্কেটিং কৌশল হিসেবে ভাবা বন্ধ করে একটি ডিস্ট্রিবিউটেড সিস্টেম চ্যালেঞ্জ (distributed systems challenge) হিসেবে বিবেচনা করতে শুরু করবেন, তখনই সমস্যাগুলো আকর্ষণীয় হয়ে ওঠে। যখন তিনজন ভিন্ন সাপ্লায়ার আপনার স্টক নিয়ন্ত্রণ করে, তখন আপনি কীভাবে একটি স্টোরফ্রন্ট নির্ভুল রাখবেন? যখন সেই একই সাপ্লায়াররা আপনাকে না জানিয়েই দাম পরিবর্তন করে দেয়, তখন আপনি কীভাবে প্রতিযোগিতামূলক দাম নির্ধারণ করবেন? স্প্রেডশিটের সাগরে ডুবে না গিয়ে কীভাবে আপনি ৫০টি SKU থেকে ৫,০০০ SKU-তে বর্ধিত একটি ক্যাটালগ সামলাবেন?
এই প্রশ্নগুলোর উত্তর দেওয়ার জন্য আমি একটি পাইপলাইন তৈরি করেছিলাম। Node.js ইভেন্ট-ড্রিভেন আর্কিটেকচার (event-driven architecture) পরিচালনা করেছিল কারণ একসাথে একাধিক সাপ্লায়ার কানেকশন সামলানোর জন্য আমার নন-ব্লকিং I/O প্রয়োজন ছিল। PostgreSQL এখানে একটি নির্ভরযোগ্য 'সোর্স অফ ট্রুথ' (source of truth) হিসেবে কাজ করেছিল। আমি স্কিমা ডিজাইনের (schema design) ব্যাপারে অত্যন্ত যত্নশীল ছিলাম, কারণ একটি অগোছালো ইনভেন্টরি টেবিল প্রথমবার কোনো অস্তিত্বহীন পণ্য অতিরিক্ত বিক্রি (oversell) করার সাথে সাথেই দুঃস্বপ্নে পরিণত হতে পারে।
পাইপলাইন তৈরি করা
মূল কাজটি সহজ ছিল: সাপ্লায়ার API থেকে পণ্যের ডেটা সংগ্রহ করা। বাস্তবে এর অর্থ ছিল এমন সব এন্ডপয়েন্ট (endpoints) থেকে SKU, বিবরণ, ছবি, স্টকের পরিমাণ এবং দাম সংগ্রহ করা, যা একে অপরের সাথে কথা বলার জন্য তৈরি করা হয়নি। আমি Node.js-এ এমন কিছু পোলিং সার্ভিস (polling services) লিখেছিলাম যা নির্দিষ্ট বিরতিতে সাপ্লায়ার ফিডগুলোতে রিকোয়েস্ট পাঠাত। প্রতিটি ইনকামিং পেলোড (payload) আমাদের অভ্যন্তরীণ স্টোরফ্রন্ট ডেটাবেসে পৌঁছানোর আগে ভ্যালিডেশন এবং ম্যাপিং লেয়ারের মধ্য দিয়ে যেত।
আমি পণ্য, ভেরিয়েন্ট, প্রাইসিং হিস্ট্রি এবং সিঙ্ক লগের জন্য আলাদা আলাদা টেবিল দিয়ে PostgreSQL-এর গঠন তৈরি করেছিলাম। যখন কোনো সাপ্লায়ার নিঃশব্দে কোনো ফিল্ডের নাম পরিবর্তন করত বা যেখানে সংখ্যা থাকার কথা সেখানে 'null' পাঠাত, পাইপলাইনটি তা শনাক্ত করত এবং স্টোরফ্রন্টকে ক্ষতিগ্রস্ত করার পরিবর্তে একটি ফেইলর রেকর্ড (failure record) লিখে রাখত। আমি একটি লগ রো (log row) দেখে ঠিক বুঝতে পারতাম কোন এন্ডপয়েন্টটি কাজ করছে না, কখন এটি ঘটেছে এবং কোন ফিল্ডগুলো ত্রুটিপূর্ণ ছিল। এই অবজারভেবিলিটি (observability) আমাকে একাধিকবার রক্ষা করেছে, বিশেষ করে যখন কোনো সাপ্লায়ার সপ্তাহান্তের ছুটির সময় তাদের API "আপগ্রেড" করার সিদ্ধান্ত নেয়।
যা ভালো কাজ করেছে
অটোমেশন প্রচুর সময় বাঁচিয়েছে। শুরুর দিকে আমি ম্যানুয়াল পদ্ধতি চেষ্টা করেছিলাম: সাপ্লায়ারদের স্প্রেডশিট ডাউনলোড করা, হাতে সেগুলো পরিষ্কার করা, ছবি ফরম্যাট করা এবং স্টোরে CSV আপলোড করা। ক্যাটালগে কয়েক ডজন আইটেম পার হওয়ার সাথে সাথেই তা অসম্ভব হয়ে পড়ে। অটোমেটেড পাইপলাইনটি নতুন লিস্টিং, দামের আপডেট এবং স্টকের সমন্বয় এমনভাবে সামলে নিত যে আমাকে আর কোনো স্প্রেডশিট স্পর্শ করতে হতো না।
টেমপ্লেটের মাধ্যমে পণ্যের বিবরণ স্কেল করা সম্ভব হয়েছিল। প্রায় পাঁচশ প্রায় একই রকম পণ্যের জন্য আলাদা আলাদা বিবরণ লেখা টেকসই নয়। পরিবর্তে, আমি একটি টেমপ্লেটিং লেয়ার তৈরি করেছিলাম যা ম্যাটেরিয়াল, ডাইমেনশন বা রঙের মতো সাপ্লায়ার অ্যাট্রিবিউটগুলো গ্রহণ করত এবং সেগুলোকে একটি সুসংগঠিত ডেসক্রিপশন ব্লকে যুক্ত করত। এর আউটপুট ছিল কনভার্ট করার জন্য যথেষ্ট পরিচ্ছন্ন এবং এতটাই সামঞ্জস্যপূর্ণ যে হাজার হাজার নতুন SKU যোগ করার জন্য কোনো ম্যানুয়াল কপিরাইটিংয়ের প্রয়োজন হতো না।
প্রাইস মনিটরিং বা দাম পর্যবেক্ষণও আমার প্রত্যাশাকে ছাড়িয়ে গিয়েছিল। আমি একটি লাইটওয়েট মনিটরিং লেয়ার তৈরি করেছিলাম যা কিছু নির্দিষ্ট মূল পণ্যের ওপর প্রতিযোগীদের দাম ট্র্যাক করত। যখন এটি দামের পরিবর্তন শনাক্ত করত, সিস্টেমটি আমার কনফিগার করা গার্ডরেলের (guardrails) মধ্যে থেকে স্বয়ংক্রিয়ভাবে আমাদের মার্জিন সমন্বয় করে নিত। যদি কোনো সাপ্লায়ার পাইকারি দাম কমিয়ে দেয়, তবে লিস্টিং প্রাইস কয়েক দিনের বদলে কয়েক মিনিটের মধ্যেই সেই পরিবর্তন প্রতিফলিত করতে পারত। এই রেসপন্সিভনেস বা দ্রুত প্রতিক্রিয়া কম মার্জিনের পণ্যগুলোর ক্ষেত্রে উল্লেখযোগ্য পার্থক্য তৈরি করেছিল।
কী ভেঙেছিল এবং কেন
সাপ্লায়ার API-গুলোতে ধারাবাহিকতার অভাব রয়েছে। এটি কোনো অভিযোগ নয়; এটি একটি ধ্রুব সত্য। একজন পার্টনার প্রেডিক্টেবল প্যাগিনেশনসহ (predictable pagination) পরিষ্কার JSON প্রদান করে। অন্যজন সোমবার camelCase ট্যাগসহ XML পাঠায় আবার বুধবার snake_case পাঠায়। রেট লিমিট (rate limits) অনেক সময় বেশ উদার আবার কখনও অত্যন্ত কঠোর হয়। ডাউনটাইম সম্পর্কে জানানোর জন্য সঠিক স্ট্যাটাস কোডের পরিবর্তে HTML এরর পেজ ব্যবহার করা হয়। শেষ পর্যন্ত আপনাকে এমন সব এন্ডপয়েন্টের জন্য ডিফেন্সিভ পার্সার (defensive parsers) এবং রিট্রাই লজিক (retry logic) লিখতে হয়, যা দেখে মনে হয় যেন সেগুলো ২০০৩ সালে ডিজাইন করা হয়েছিল।
ইনভেন্টরি সিঙ্ক্রোনাইজেশনে (Inventory sync) এমন কিছু রেস কন্ডিশন (race conditions) ছিল যা আমার রাতের ঘুম কেড়ে নিয়েছিল। কল্পনা করুন: দুজন ক্রেতা মাত্র কয়েক সেকেন্ডের ব্যবধানে শেষ ইউনিটটি অর্ডার করে ফেললেন, অথবা একজন ক্রেতা ঠিক যখন চেকআউট বাটনে ক্লিক করছেন, তখনই একজন সাপ্লায়ারের ওয়েবহুক (webhook) জানিয়ে দিল যে স্টক শূন্য হয়ে গেছে। আমার প্রাথমিক 'read-then-update' লজিকটি মারাত্মকভাবে ব্যর্থ হয়েছিল। আমাকে হাই-ভেলোসিটি SKU-গুলোর জন্য অ্যাটমিক PostgreSQL ট্রানজ্যাকশন (atomic PostgreSQL transactions) এবং পেসিমিস্টিক লকিং (pessimistic locking) ব্যবহার করে সিঙ্ক লেয়ারটি নতুন করে লিখতে হয়েছিল। এটি কনকারেন্সি (concurrency) সম্পর্কে একটি যন্ত্রণাদায়ক ও বাস্তবমুখী শিক্ষা ছিল, যা কোনো টিউটোরিয়াল আপনাকে প্রকৃত অর্থের ঝুঁকির মুখে পড়ে শেখাতে পারে না।
আমার সবচেয়ে বড় ব্যর্থতা ছিল কাস্টমার সাপোর্ট অটোমেশনকে অবহেলা করা। আমি ডেটা পাইপলাইনের পেছনে অন্ধের মতো ছুটেছি এবং মানুষের ওপর এর প্রভাবকে গুরুত্বহীন হিসেবে বিবেচনা করেছি। অর্ডারগুলো দেরিতে আসছিল। সাপ্লায়াররা ভুল রঙের পণ্য পাঠিয়ে দিচ্ছিল। আমি যখন API টাইমআউট ডিবাগ করছিলাম, তখন গ্রাহকদের পাঠানো ইমেলগুলো ঘণ্টার পর ঘণ্টা আমার ইনবক্সে পড়ে থাকত। আমার কাছে কোনো টিকিট রাউটিং, কোনো স্বয়ংক্রিয় রেসপন্স বা চ্যাটবট হ্যান্ডঅফ ছিল না। টেকনিক্যাল ইনফ্রাস্ট্রাকচার মজবুত ছিল, কিন্তু হিউম্যান ইনফ্রাস্ট্রাকচার ছিল অনুপস্থিত; আর এই ঘাটতিটি একটি ত্রুটিপূর্ণ ওয়েবহুকের চেয়েও ব্যবসার বেশি ক্ষতি করেছিল।
একজন ইঞ্জিনিয়ারের মতো ইমেজ টেস্টিং
আমি প্রোডাক্ট ইমেজের ওপর একটি পার্শ্ববর্তী পরীক্ষা চালিয়েছিলাম। আমি সেশন-ভিত্তিক বাকেটিংয়ের (session-based bucketing) সাথে যুক্ত একটি সাধারণ URL প্যারামিটার রাউটিং ব্যবহার করে বিভিন্ন ব্যবহারকারীকে ভিন্ন ভিন্ন হিরো ইমেজ (hero images) দেখিয়েছিলাম। একটি ভেরিয়েন্টে পণ্যটিকে একটি সাধারণ সাদা ব্যাকগ্রাউন্ডে দেখানো হয়েছিল। অন্যটিতে একটি আসল ডেস্কের ওপর লাইফস্টাইল সেটিংয়ে দেখানো হয়েছিল। আমি সরাসরি অর্ডার ফ্লো-এর সাথে যুক্ত বেসিক ইভেন্ট লগিং ব্যবহার করে প্রতিটি বাকেটের কনভার্সন রেট ট্র্যাক করেছিলাম।
ছোট ছোট পরিবর্তনগুলো এনগেজমেন্ট বাড়িয়ে দিয়েছিল। লাইফস্টাইল শটগুলো সবসময় জয়ী হতো না, কিন্তু যখন সেগুলো জিতত, তখন সেই উন্নতি এতটাই উল্লেখযোগ্য ছিল যে তা আমার অগ্রাধিকার দেওয়ার পদ্ধতি বদলে দিয়েছিল।
