Files
Backend/prm.md
T
2026-04-05 00:57:25 +03:30

13 KiB

گزارش بررسی معماری و پیاده سازی پروژه CropLogic

دامنه بررسی

  • این بررسی فقط روی کد موجود در ریپازیتوری انجام شد.
  • طبق درخواست شما هیچ تغییری در کد اپلیکیشن داده نشد.
  • برای این گزارش تست اجرایی سراسری اجرا نشد؛ جمع بندی بر پایه بازبینی ساختار، تنظیمات، مدل ها، ویوها، سرویس ها و تست های موجود است.

جمع بندی سریع

پروژه از نظر تفکیک دامنه ها به چند اپ Django ساختار قابل فهمی دارد، اما چند ضعف جدی در مرزهای معماری، امنیت APIها، پایداری در کار با سرویس های خارجی، و یکپارچگی داده ها دیده می شود. مهم ترین ریسک ها این ها هستند:

  1. سیستم نوتیفیکیشن از نظر مجوزدهی ناامن است و کاربر احراز هویت شده می تواند به کانال دلخواه subscribe/publish کند.
  2. ویرایش سنسورهای مزرعه باعث حذف و بازسازی کامل سنسورها می شود و به خاطر CASCADE شدن، تاریخچه داده های سنسور هم از بین می رود.
  3. ساخت مزرعه و زون بندی، dispatch تسک های async را قبل از commit نهایی تراکنش بالادستی شروع می کند و مستعد race condition است.
  4. چند API وابسته به سرویس خارجی، خطاهای شبکه/پیکربندی را handle نمی کنند و به احتمال زیاد با 500 خام fail می شوند.

3) dispatch تسک های زون بندی داخل جریان تراکنش بالادستی انجام می شود

شدت: بالا

شواهد:

  • در farm_hub/services.py:19 تا farm_hub/services.py:23 ساخت مزرعه داخل transaction.atomic() انجام می شود.
  • همانجا dispatch_farm_zoning صدا زده می شود.
  • در crop_zoning/services.py:897 تا crop_zoning/services.py:935 بعد از ساخت CropArea و CropZoneها، تابع dispatch_zone_processing_tasks اجرا می شود.

اثر:

  • create_zones_and_dispatch داخل یک atomic داخلی صدا زده می شود، اما هنوز transaction بیرونی create_farm_with_zoning commit نشده است.
  • worker ممکن است قبل از commit نهایی شروع شود و داده های وابسته را نبیند یا وضعیت ناقص ببیند.
  • این مشکل در محیط های واقعی، intermittent و سخت برای دیباگ خواهد بود.

پیشنهاد:

  • dispatch تسک ها باید با transaction.on_commit(...) انجام شود.
  • ساخت entityها و شروع پردازش async باید کاملا از هم جدا شوند.
  • بهتر است workflow ایجاد مزرعه -> commit -> enqueue zoning -> poll status به صورت صریح طراحی شود.

4) وابستگی به سرویس خارجی در چند endpoint بدون fail-safe مناسب است

شدت: بالا

شواهد:

  • dashboard/views.py:126 تا dashboard/views.py:134
  • irrigation_recommendation/views.py:63 تا irrigation_recommendation/views.py:79
  • irrigation_recommendation/views.py:125 تا irrigation_recommendation/views.py:136
  • fertilization_recommendation/views.py:63 تا fertilization_recommendation/views.py:80
  • fertilization_recommendation/views.py:94 تا fertilization_recommendation/views.py:105
  • در external_api_adapter/adapter.py:50 و external_api_adapter/adapter.py:70 خطاهای config/network به ExternalAPIRequestError تبدیل می شوند، اما endpoint های بالا آن را catch نمی کنند.

اثر:

  • اگر base_url تنظیم نشده باشد یا شبکه/سرویس خارجی down باشد، پاسخ API داخلی احتمالا 500 خام می شود.
  • رفتار endpoint ها ناهمگون است: مثلا farm_ai_assistant این خطا را handle می کند، اما dashboard/irrigation/fertilization نه.
  • این ناهمگونی نگهداری و مانیتورینگ را سخت می کند.

پیشنهاد:

  • یک لایه مشترک برای map کردن خطاهای external dependency به 502/503 ایجاد شود.
  • همه endpoint های adapter-based رفتار یکسان داشته باشند.
  • timeout، retry policy، circuit breaker و logging ساختاریافته برای این لایه لازم است.

5) GET مربوط به crop zoning دارای side effect است

شدت: متوسط رو به بالا

شواهد:

  • در crop_zoning/views.py:57 تا crop_zoning/views.py:68 endpoint GET /area/ فقط read نیست.
  • در crop_zoning/services.py:869 تا crop_zoning/services.py:892 این GET می تواند:
    • area جدید بسازد
    • zone بسازد
    • rule-based data تولید کند
    • task جدید dispatch کند

اثر:

  • semantics REST شکسته می شود؛ GET دیگر safe/read-only نیست.
  • cache کردن، replay، tracing و حتی load-testing این endpoint رفتار غیرمنتظره پیدا می کند.
  • اگر frontend چند بار polling کند، endpoint read عملا orchestration engine می شود.

پیشنهاد:

  • endpoint creation/recompute باید POST باشد.
  • GET فقط باید status/result را برگرداند.
  • orchestration crop zoning بهتر است state machine یا task-oriented API شفاف داشته باشد.

7) خطای یکتایی email در update پروفایل می تواند 500 بدهد

شدت: متوسط

شواهد:

  • در account/views.py:53 تا account/views.py:60 فیلدهای کاربر مستقیم set و save می شوند.
  • مدل User در account/models.py ایمیل unique دارد.
  • در این مسیر هیچ IntegrityErrorی handle نشده است.

اثر:

  • اگر کاربر ایمیلی را بگذارد که قبلا ثبت شده، به جای خطای business-level، احتمالا 500 برمی گردد.
  • تجربه API ناهمگون می شود، چون در RegisterView برای uniqueness handling وجود دارد ولی در profile update نه.

پیشنهاد:

  • validation uniqueness باید در serializer انجام شود.
  • save() هم باید با handling مناسب برای race condition همراه باشد.

8) ingestion سنسور به availability سرویس نوتیفیکیشن گره خورده است

شدت: متوسط

شواهد:

  • در external_sensor_api/views.py:53 تا external_sensor_api/views.py:80 ابتدا reading در DB ذخیره می شود و بعد publish_notification صدا زده می شود.
  • در notifications/services.py:24 تا notifications/services.py:25 publish بدون try/except انجام می شود.

اثر:

  • اگر Redis قطع باشد، ingest ممکن است بعد از ذخیره شدن داده با exception fail کند.
  • نتیجه می تواند پاسخ 500 به sender باشد، در حالی که reading واقعا ثبت شده است.
  • این باعث رفتار non-idempotent و retry خطرناک می شود: فرستنده دوباره retry می کند و رکورد duplicate می سازد.

پیشنهاد:

  • publish notification باید best-effort و جدا از مسیر اصلی ingestion باشد.
  • برای sensor ingest بهتر است notification async شود.
  • اگر notification شکست خورد، ثبت reading نباید rollback منطقی API را مخدوش کند.

9) طراحی authentication برای OTP در مقیاس چند پردازه/چند نود پایدار نیست

شدت: متوسط

شواهد:

  • در config/settings.py:110 تا config/settings.py:115 cache از نوع LocMemCache است.
  • منطق OTP در auth/views.py از cache برای نگهداری کد استفاده می کند.
  • هرچند routeهای OTP در auth/urls.py:8 تا auth/urls.py:9 فعلا کامنت شده اند، طراحی فعلی برای production-ready بودن کافی نیست.

اثر:

  • در deployment چند worker یا چند instance، درخواست request-otp و verify-otp ممکن است به نودهای مختلف بخورند و verify شکست بخورد.
  • این یعنی طراحی احراز هویت به process-local memory وابسته است.

پیشنهاد:

  • cache/shared store مثل Redis برای OTP استفاده شود.
  • rate limit، lockout، replay protection و audit logging هم به این flow اضافه شود.

10) نشانه های واضحی از ناتمام بودن مرزهای API و drift بین کد و قرارداد وجود دارد

شدت: متوسط

شواهد:

  • routeهای مهمی کامنت شده اند: auth/urls.py:8 تا auth/urls.py:9، account/urls.py:7 تا account/urls.py:8، crop_zoning/urls.py:16 تا crop_zoning/urls.py:31، farm_ai_assistant/urls.py:15
  • کلاس ها و viewهای مرتبط هنوز در کد حضور دارند، اما publicly exposed نیستند.

اثر:

  • فهمیدن وضعیت واقعی featureها برای توسعه دهنده جدید سخت می شود.
  • schema و مستندات ممکن است با قابلیت واقعی محصول همگام نباشد.
  • این وضعیت معمولا نشانه این است که API lifecycle و deprecation strategy شفاف نیست.

پیشنهاد:

  • featureهای غیرفعال یا باید حذف شوند، یا با feature flag و مستندات واضح مدیریت شوند.
  • برای endpointهای deprecated یا موقت، policy مشخص publish/unpublish لازم است.

ضعف های معماری کلان

1) تکرار زیاد منطق دسترسی به مزرعه

در چند اپ مختلف mixin مشابه برای resolve کردن farm_uuid و ownership تکرار شده:

  • dashboard/views.py:20
  • irrigation_recommendation/views.py:24
  • fertilization_recommendation/views.py:24
  • farm_ai_assistant/views.py:28
  • farm_hub/views.py:16
  • access_control/views.py:15

اثر:

  • رفتار خطاها یکدست نیست.
  • هر اپ interpretation متفاوتی از “farm not found / access denied / validation error” دارد.
  • تغییر policy ownership یا prefetching باید در چند نقطه تکرار شود.

پیشنهاد:

  • یک service/mixin/shared permission مشترک برای farm-scoped access ساخته شود.

2) مرز بین دامنه های business و integration واضح نیست

نمونه ها:

  • viewها مستقیم با external_api_request(...) کار می کنند.
  • persistence، orchestration، و response-shaping در یک متد جمع شده است.
  • در crop zoning هم business logic سنگین، persistence و async dispatch در یک service بزرگ متمرکز شده است.

اثر:

  • تست واحد سخت تر می شود.
  • وابستگی به provider خارجی به لایه API نشت کرده است.
  • refactor یا جایگزینی provider بیرونی پرهزینه تر می شود.

پیشنهاد:

  • use-case/service layer مشخص برای هر دامنه تعریف شود.
  • adapter خارجی فقط در لایه integration بماند.
  • view صرفا orchestration نهایی HTTP را انجام دهد.

وضعیت تست ها

تست برای بعضی اپ ها وجود دارد، اما پوشش پروژه ناهمگن است. بر اساس ساختار فعلی، این اپ ها فاقد فایل تست قابل مشاهده هستند:

  • access_control
  • account
  • auth
  • external_api_adapter
  • fertilization_recommendation
  • irrigation_recommendation
  • plant_simulator
  • pest_detection
  • sensor_ingest

این شکاف مخصوصا برای بخش های زیر نگران کننده است:

  • مجوزدهی و access control
  • authentication
  • integration با سرویس خارجی
  • رفتار خطا و fallback
  • data integrity سنسورها و readingها

اولویت پیشنهادی برای اصلاح

فوری

  1. بستن ضعف امنیتی notification stream/publish
  2. جلوگیری از delete شدن سنسورها در update و حفظ تاریخچه reading
  3. انتقال dispatch تسک ها به transaction.on_commit
  4. یکسان سازی خطاهای external service و جلوگیری از 500 خام

بعدی

  1. جدا کردن endpointهای mock از API اصلی
  2. حذف side effect از GETهای crop zoning
  3. یکپارچه سازی farm access logic
  4. افزودن تست برای auth, access_control, recommendations, integrations

نتیجه نهایی

پروژه از نظر تقسیم اپ ها و شفاف بودن domain names شروع خوبی دارد، اما هنوز چند بخش مهم آن بیشتر شبیه نسخه integration-heavy و نیمه محصولی است تا یک backend production-hardened. بزرگ ترین ریسک های فعلی مربوط به:

  • امنیت notificationها
  • از دست رفتن داده در lifecycle سنسورها
  • race condition در zoning async flow
  • ناپایداری در برابر خطاهای سرویس های خارجی

اگر بخواهم فقط یک جمع بندی کوتاه بدهم: مشکل اصلی پروژه نه در syntax یا ساختار فایل ها، بلکه در مرزبندی responsibilityها، lifecycle داده ها، و رفتار fail-safe سیستم است.