→ همه‌ی نوشته‌ها

با دید حل مسئله پل ساختم

۲۹ شهریور ۱۴۰۵ · ۸ دقیقه مطالعه

طراح محصول برنامه نویس

این مسئله فقط اتصال دو API نبود، ساختن یک فرایند همگام‌سازی قابل‌مشاهده، تکرارپذیر، کنترل‌شده و قابل‌بازیابی بود.

موضوع از جایی شروع شد که یکی از مشتریام یه مشکل رو واسم مطرح کرد که راهنماییش کنم. از بهینه کردن یک فرایند تکراری به پیشنهاد سیستم حسابداری رسیدیم و در نهایت به حل یک مسئله دیگه و نامرتبط و اتصال دو سرویس که هردو API ارائه میدادن ولی هیچکدوم ساختار خواندن و ارتباط با API دیگری رو نداشتن.

واسه خودم جالب بود که حلش کنم، یک مسئله که واسه من ساده بود ولی تجربه عمیقی روش نداشتم و ذهن کنجکاوم دوست داشت انجامش بده چون ازون چالش هایی بود که وایب کد نویسی رو در من زنده میکرد.

خلاصه داستان

این پروژه با یک درخواست ظاهراً ساده شروع شد: اطلاعات محصولات از نرم‌افزار حسابداری محک به یک فروشگاه مبتنی بر میکسین منتقل شود.

اما اتصال مستقیم دو API کافی نبود. مدل داده‌ها با یکدیگر هم‌خوانی نداشت، رفتار واقعی بعضی endpointها با مستندات متفاوت بود، واحد قیمت میان دو سیستم فرق می‌کرد و یک خطای کوچک می‌توانست محصول تکراری، قیمت اشتباه یا از دست‌رفتن وضعیت همگام‌سازی ایجاد کند.

راه‌حل، یک Bridge سبک و مستقل بود که داده‌های محک را دریافت، نرمال‌سازی و اعتبارسنجی می‌کند و سپس با guardهای کنترلی به میکسین می‌فرستد. این سرویس با PHP و SQLite ساخته شد تا روی زیرساخت موجود و کم‌منبع اجرا شود و برای عملیات روزمره به سرویس‌های سنگین دیگری وابسته نباشد.

نتیجه پایلوت، انتقال و تأیید چهار محصول واقعی همراه با نام، قیمت، موجودی، دسته‌بندی و تصویر بود. اجرای عمومی بعدی هر چهار محصول را بدون ساخت رکورد تکراری به‌روزرسانی کرد و اجرای افزایشی پس از آن، به‌درستی هیچ داده بدون تغییری را دوباره پردازش نکرد.

تعریف مسئله

محک و میکسین دو مدل متفاوت از «محصول» داشتند. برای رسیدن از داده خام حسابداری به محصول قابل‌نمایش در فروشگاه باید چند موجودیت محک به یکدیگر متصل می‌شدند:

  • Product و ProductDetail برای هویت و مشخصات کالا
  • VisitorProduct برای قیمت و موجودی مرتبط با سایت
  • ProductCategory برای طبقه‌بندی
  • PhotoGallery و Picture برای تصویر اصلی

در کنار این تفاوت ساختاری، چند ابهام پرریسک هم وجود داشت:

  • مستندات محک endpointهای V2 را نشان می‌دادند، اما پشتیبانی توصیه کرد از نسخه‌های بدون V2 استفاده شود.
  • تا زمانی که کالاها از داخل فرایند «بازارا» برای سایت ارسال نمی‌شدند، API مجموعه محصولات را خالی برمی‌گرداند.
  • قیمت VisitorProduct در داده واقعی صفر بود و قیمت معتبر باید از ProductDetail.Price1 خوانده می‌شد.
  • قیمت‌های محک در محیط آزمایش‌شده ریال و قیمت‌های میکسین تومان بودند.
  • دسته‌بندی در OpenAPI میکسین ظاهراً اختیاری بود، اما API واقعی ایجاد محصول بدون دسته را با خطای ۴۰۰ رد کرد.
  • retry کردن کورکورانه درخواست‌های ایجاد محصول یا تصویر می‌توانست duplicate بسازد.

بنابراین مسئله اصلی فقط «انتقال داده» نبود؛ باید فرایندی ساخته می‌شد که پیش از نوشتن قابل بررسی باشد، اجرای تکراری آن رکورد اضافه نسازد و در صورت شکست، وضعیت داده‌ها را گم نکند.

نقشی که برای خودم تعریف کردم

من صاحب مسئله و تصمیم‌گیر فنی پروژه بودم و استقرار و کنترل کیفیت عملی را نیز انجام دادم. مسئولیت‌های من شامل این موارد بود:

  • تعریف scope، محدودیت‌های زیرساخت و سطح قابل‌قبول ریسک
  • بررسی مستندات و پیگیری ابهام endpointها با پشتیبانی محک
  • انتخاب معماری سبک PHP و SQLite
  • آماده‌سازی محیط سرور، تنظیمات، دسترسی‌ها و استقرار
  • اجرای diagnosticها و تست‌ها روی APIهای واقعی
  • مقایسه قیمت، موجودی، دسته‌بندی و تصویر با رابط‌های واقعی دو سیستم
  • کنترل مرحله‌ای مجوزهای نوشتن و تهیه backup قبل از اجرای عمومی
  • تأیید تجربه و اطلاعات موردنیاز داشبورد فارسی
  • بازبینی مستندات و حذف اطلاعات خصوصی پیش از انتشار عمومی

برای تحلیل، پیاده‌سازی و مستندسازی از Codex استفاده کردم. Codex ساختار کد، clientها، mapperها، guardها، تست‌ها و داشبورد را تحت هدایت من تولید و اصلاح کرد؛ تصمیم‌های کسب‌وکاری، اجرای production و تأیید نهایی خروجی‌ها بر عهده من بود.

رویکرد

۱. ابتدا مشاهده، سپس نوشتن

سیستم به‌صورت پیش‌فرض در حالت dry-run طراحی شد. هر payload پیش از ایجاد تغییر در فروشگاه قابل مشاهده بود و نوشتن تک‌محصولی تنها با یک مجوز موقت جداگانه فعال می‌شد.

این رویکرد امکان داد قیمت، موجودی، نام و دسته‌بندی را ابتدا با داده واقعی مقایسه کنیم و دامنه هر آزمایش را به یک رکورد محدود نگه داریم.

۲. همگام‌سازی افزایشی به‌جای دریافت کامل

محک برای موجودیت‌های مختلف RowVersion مستقل ارائه می‌کرد. Bridge آخرین checkpoint موفق هر موجودیت را در SQLite نگه می‌دارد و فقط تغییرات جدید را درخواست می‌کند.

checkpoint تنها پس از پایان موفق اجرا جلو می‌رود. در نتیجه، شکست میانی باعث نمی‌شود تغییرات دریافت‌نشده در اجرای بعدی ناپدید شوند.

۳. snapshot محلی برای join قابل‌اعتماد

چون ممکن بود فقط یکی از موجودیت‌های مرتبط تغییر کند، تکیه بر پاسخ همان اجرای API کافی نبود. snapshot آخرین نسخه هر موجودیت در SQLite ذخیره شد تا join محصول، جزئیات، قیمت، موجودی و تصویر حتی در اجرای افزایشی کامل باقی بماند.

۴. نگاشت پایدار شناسه‌ها

هر ProductDetail محک به شناسه محصول ساخته‌شده در میکسین نگاشت شد. Bridge با کمک این mapping تشخیص می‌دهد که باید رکورد جدید بسازد یا محصول قبلی را به‌روزرسانی کند.

این تصمیم، وابستگی خطرناک به جست‌وجوی نام محصول را حذف و اجرای تکراری را idempotent کرد.

۵. نوشتن و rollback محافظت‌شده

برای smoke test، محصولی قابل‌شناسایی با موجودی صفر و وضعیت غیرفعال ساخته شد. حذف تنها زمانی مجاز بود که marker و مالکیت رکورد تأیید شوند. ایجاد موفق بود، رکورد دوباره خوانده شد و سپس با موفقیت حذف شد.

برای محصولات واقعی نیز preview، apply و انتقال تصویر از یکدیگر جدا شدند. درخواست‌های امن در خطاهای موقت retry می‌شوند، اما create محصول و تصویر کورکورانه تکرار نمی‌شوند؛ چون timeout ممکن است پس از ایجاد موفق اما پیش از دریافت پاسخ رخ داده باشد.

۶. داشبورد کم‌ریسک برای عملیات

یک داشبورد فارسی و read-only ساخته شد تا وضعیت Bridge، اتصال APIها، تعداد mappingها، checkpointها و تاریخچه اجرا را نمایش دهد.

داشبورد عمداً امکان اجرای sync یا تغییر تنظیمات ندارد. وضعیت APIها نیز فقط با refresh دستی بررسی می‌شود تا بازشدن صفحه باعث ایجاد درخواست غیرضروری به سرویس‌های خارجی نشود.

مسیر آزمون و تکرار

فرایند به‌جای یک انتشار بزرگ، در چند مرحله محدود پیش رفت:

  1. تأیید اتصال و authentication هر دو API
  2. اجرای diagnostic فقط‌خواندنی روی داده‌های محک
  3. اصلاح endpointها، casing شناسه‌ها و منطق fallback قیمت
  4. اجرای dry-run برای چهار محصول
  5. ایجاد، خواندن و حذف یک محصول آزمایشی محافظت‌شده در میکسین
  6. انتقال اولین محصول واقعی و بررسی دستی قیمت و موجودی
  7. انتقال تصویر همان محصول و آزمایش duplicate guard
  8. انتقال سه محصول و تصویر باقی‌مانده
  9. تهیه backup و اجرای sync عمومی
  10. اجرای مجدد dry-run برای تأیید checkpoint و جلوگیری از پردازش تکراری

تست‌های خودکار مواردی مانند تبدیل ریال به تومان، fallback قیمت، موجودی صفر، پاسخ‌های PascalCase، شناسه‌های عددی، نرمال‌سازی نام فارسی، تشخیص فرمت تصویر، persistence در SQLite و ایمنی محصول آزمایشی را پوشش دادند.

نتیجه ثبت‌شده روی سرور:

10 tests · 0 skipped · 0 failures

نتیجه

در پایلوت production:

  • چهار محصول واقعی به میکسین منتقل شدند.
  • چهار تصویر اصلی بدون duplicate ثبت شدند.
  • نام، قیمت، موجودی، دسته‌بندی و شناسه‌های مرجع کنترل شدند.
  • اولین اجرای عمومی، هر چهار mapping موجود را update کرد و محصول جدید تکراری نساخت.
  • اجرای افزایشی بعدی صفر رکورد جدید دریافت کرد؛ یعنی checkpoint تغییرات بدون تکرار عمل کرد.
Production run: 4 received · 0 created · 4 updated · 0 skipped
Next incremental dry-run: 0 received · 0 created · 0 updated

این پروژه هنوز مدرکی برای ادعای افزایش فروش یا صرفه‌جویی زمانی ندارد. موفقیت مرحله فعلی با معیارهای فنی سنجیده شد: صحت داده، جلوگیری از duplicate، قابلیت مشاهده، کنترل نوشتن و امکان بازیابی.

محدودیت‌ها و ادامه مسیر

نسخه فعلی بر همگام‌سازی محصول از محک به میکسین تمرکز دارد. انتقال مشتری، آدرس، سفارش و پرداخت هنوز فعال نشده است. همچنین قواعد variant، حذف یا غیرفعال‌سازی محصول، reconcile کاتالوگ قبلی و alerting اجرای زمان‌بندی‌شده به تصمیم و تست بیشتر نیاز دارند.

گام بعدی منطقی، تثبیت عملیات production است: تأیید scheduler و alerting، کامل‌کردن نگاشت دسته‌ها، طراحی reconcile بر پایه external ID و سپس تعریف قواعد کسب‌وکاری سفارش و مشتری در یک محیط staging.

آموخته‌ها

مستندات API نقطه شروع‌اند، نه حقیقت نهایی

رفتار runtime، داده واقعی و پاسخ پشتیبانی در چند نقطه با برداشت اولیه از OpenAPI متفاوت بود. هر فرض مهم باید با یک آزمایش کوچک و قابل‌بازگشت تأیید شود.

idempotency فقط retry نیست

برای جلوگیری واقعی از duplicate به mapping پایدار، checkpoint، snapshot، lock، ownership marker و سیاست retry مناسب نیاز بود.

نقش انسان در توسعه AI-assisted، کنترل تصمیم و ریسک است

سرعت تولید کد با AI بالا بود، اما اعتمادپذیری از اجرای مرحله‌ای، مقایسه با رابط واقعی، محدودکردن مجوزها و بازبینی نتایج به دست آمد. مهم‌ترین خروجی من فقط کد نبود؛ ساختن یک فرایند تصمیم‌گیری و اعتبارسنجی بود که اجازه نمی‌داد سرعت، جای ایمنی را بگیرد.

لینک ریپوی گیت هاب همین پروژه ( سورس و مستندات کامل )