UPDATE
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# گزارش بررسی معماری و پیاده سازی پروژه 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 سیستم است.
|
||||
Reference in New Issue
Block a user