262 lines
13 KiB
Markdown
262 lines
13 KiB
Markdown
# گزارش بررسی معماری و پیاده سازی پروژه 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 سیستم است.
|