diff --git a/docs/analysis/software-architecture.md b/docs/analysis/software-architecture.md new file mode 100644 index 0000000..8188738 --- /dev/null +++ b/docs/analysis/software-architecture.md @@ -0,0 +1,369 @@ +# معماری نرم‌افزار (Software Architecture) – سامانه مدیریت و گزارش خرابی دانشگاهی + +**تهیه‌کننده:** امیرحسن عارف اصل +**بازبین:** رضا آدوسی +**آخرین به‌روزرسانی:** ۱۴۰۵/۰۸/۱۴ + +--- + +## مقدمه + +این سند معماری نرم‌افزاری سامانهٔ مدیریت و گزارش خرابی دانشگاهی را در سطح **منطقی و مفهومی** تعریف می‌کند. چون پروژه هنوز وارد فاز پیاده‌سازی نشده است (طبق `README.md`)، این سند **تعهد به فناوری خاصی ندارد** و انتخاب‌های فناورانه در بخش «پرسش‌های باز» علامت‌گذاری شده‌اند. + +هدف این سند پاسخ به سه پرسش است: + +1. سامانه از چه **مؤلفه‌هایی** تشکیل می‌شود و مرز مسئولیت هر کدام کجاست؟ +2. این مؤلفه‌ها چگونه با هم **همکاری** می‌کنند تا جریان‌های توصیف‌شده در `activity.md` محقق شوند؟ +3. کدام **نیروهای محرک معماری (Architectural Drivers)** این ساختار را توجیه می‌کنند؟ + +## ۱. نیروهای محرک معماری + +### ۱.۱. نیازمندی‌های غیرکارکردی + +| شناسه | نیازمندی | اثر معماری | +|---|---|---| +| `NFR-01` | امنیت اطلاعات | احراز هویت متمرکز، مجوزدهی سمت سرور، هش رمز، جلوگیری از دسترسی مستقیم به صفحات نقش دیگر | +| `NFR-02` | حفظ حریم خصوصی | فیلتر داده بر اساس نقش در لایهٔ برنامه (نه فقط در رابط کاربری) | +| `NFR-03` | کاربردپذیری | رابط RTL فارسی، اعتبارسنجی درجا، پیام خطای روشن | +| `NFR-04` | زمان پاسخ‌گویی | ایندکس‌گذاری هدفمند (طبق `data-model.md` §۷)، پرهیز از کوئری‌های سنگین در داشبورد | +| `NFR-05` | صحت و نگهداری اطلاعات | تاریخچهٔ append-only، قفل خوش‌بینانه، تراکنش‌های اتمیک | +| `NFR-06` | قابلیت نگهداری و توسعه | معماری لایه‌ای و ماژولار، جداسازی دامنه از زیرساخت | + +### ۱.۲. محدودیت‌ها + +| محدودیت | منبع | اثر | +|---|---|---| +| تیم سه‌نفره، پروژهٔ دانشگاهی | `README.md` | پرهیز از معماری‌های توزیع‌شده و پیچیده | +| هنوز فناوری انتخاب نشده | `README.md` («هنوز کدنویسی و پیاده‌سازی انجام نشده») | معماری باید فناوری‌مستقل بماند | +| فهرست وضعیت‌ها و اولویت‌ها نهایی نیست | پرسش‌های باز ۳ و ۴ در `activity.md` | این‌ها باید **داده** باشند، نه کد | +| دامنهٔ فضاها مشخص نیست | ابهام `README.md` | مدل محل خرابی باید قابل گسترش بماند | + +## ۲. سبک معماری انتخاب‌شده + +**سبک پیشنهادی: مونولیت ماژولار لایه‌ای (Modular Layered Monolith)** + +### ۲.۱. مقایسهٔ گزینه‌ها + +| گزینه | مزیت | عیب | تناسب با این پروژه | +|---|---|---|---| +| **مونولیت لایه‌ای** | سادگی استقرار، دیباگ آسان، مناسب تیم کوچک | مقیاس‌پذیری مستقل ماژول‌ها ندارد | ✅ **انتخاب شد** | +| میکروسرویس | استقلال استقرار، مقیاس‌پذیری انتخابی | هزینهٔ عملیاتی بالا، نیاز به DevOps، پیچیدگی تراکنش توزیع‌شده | ❌ برای تیم سه‌نفره و پروژهٔ درسی توجیه ندارد | +| بدون لایه (اسکریپت‌محور) | سرعت شروع | نقض `NFR-06`، عدم قابلیت نگهداری | ❌ | + +### ۲.۲. توجیه + +- `NFR-06` قابلیت نگهداری را الزام می‌کند؛ لایه‌بندی این را تضمین می‌کند. +- `NFR-05` صحت داده را الزام می‌کند؛ تراکنش در یک پایگاه‌دادهٔ واحد بسیار ساده‌تر از تراکنش توزیع‌شده است. +- مرزهای دامنه (بخش ۳) از `domain-model.md` گرفته شده‌اند تا در آیندهٔ دور اگر لازم شد، جداسازی به میکروسرویس **ممکن** باشد، بدون بازنویسی دامنه. + +## ۳. مرزهای ماژول (Bounded Contexts) + +مرزها از `domain-model.md` §۱ گرفته شده‌اند: + +| ماژول | مسئولیت | مفاهیم اصلی | نیازمندی‌ها | +|---|---|---|---| +| **M1 — هویت و دسترسی** | احراز هویت، تشخیص نقش، مجوزدهی | کاربر، نقش، نشست | `FR-01`، `FR-02`، `US-15` | +| **M2 — گزارش خرابی** | ثبت، پیگیری، اطلاعات تکمیلی، بازخورد گزارش‌دهنده | گزارش، اطلاعات تکمیلی، بازخورد | `FR-03`–`FR-05`، `FR-14`، `US-01`–`US-05` | +| **M3 — مدیریت تعمیر** | بررسی، اولویت‌بندی، تخصیص، ثبت نتیجه | اولویت، تخصیص، نتیجهٔ تعمیر | `FR-06`–`FR-13`، `US-06`–`US-14` | +| **M4 — اطلاع‌رسانی** | واکنش به رویدادهای دامنه و ارسال اعلان | اعلان، کانال | `FR-15`، `US-16` | +| **M5 — نظارت** | داشبورد و آمار تجمیعی | داشبورد (Read Model) | `FR-17`، `US-17` | + +> **نکته:** M2 و M3 در `data-model.md` روی یک جدول مرکزی (`fault_reports`) کار می‌کنند. جداسازی این دو **ماژول منطقی** است، نه فیزیکی. مزیت: هر ماژول فقط متدهای مجاز خودش را روی گزارش فراخوانی می‌کند و قانون «تغییر وضعیت فقط از مسیر مجاز» در یک نقطه کنترل می‌شود. + +## ۴. لایه‌های معماری + +```mermaid +flowchart TB + subgraph P["لایهٔ نمایش (Presentation)"] + P1["رابط وب RTL
SCR-01 تا SCR-12"] + end + subgraph A["لایهٔ برنامه (Application)"] + A1["سرویس‌های کاربردی
Use Cases"] + A2["اعتبارسنجی ورودی"] + A3["کنترل دسترسی"] + end + subgraph D["لایهٔ دامنه (Domain)"] + D1["موجودیت‌ها و Value Objectها"] + D2["سیاست‌های دامنه"] + D3["ماشین حالت گزارش"] + D4["رویدادهای دامنه"] + end + subgraph I["لایهٔ زیرساخت (Infrastructure)"] + I1["دسترسی به داده"] + I2["سرویس احراز هویت"] + I3["ارسال اعلان"] + I4["ذخیره‌سازی فایل"] + end + + P --> A + A --> D + A --> I + D -.->|"بدون وابستگی به زیرساخت"| I + + classDef pres fill:#e3f2fd,stroke:#1565c0 + classDef app fill:#fff3e0,stroke:#ef6c00 + classDef dom fill:#e8f5e9,stroke:#2e7d32 + classDef inf fill:#f3e5f5,stroke:#6a1b9a + class P1 pres + class A1,A2,A3 app + class D1,D2,D3,D4 dom + class I1,I2,I3,I4 inf +``` + +**قاعدهٔ وابستگی:** جهت وابستگی همیشه به سمت **دامنه** است. دامنه به هیچ لایهٔ دیگری وابسته نیست. این قاعده `NFR-06` را تضمین می‌کند و اجازه می‌دهد در آینده پایگاه‌داده یا سرویس احراز هویت عوض شود بدون تغییر منطق کسب‌وکار. + +## ۵. نمای ظرف (Container View) + +```mermaid +flowchart TB + subgraph Client["مرورگر کاربر"] + C1["برنامهٔ وب
(رابط RTL فارسی)"] + end + + subgraph Server["سرور برنامه"] + S1["API / لایهٔ برنامه"] + S2["ماژول‌های M1 تا M5"] + S3["کارگر پس‌زمینه
(پردازش رویدادها)"] + end + + subgraph Data["لایهٔ داده"] + DB[("پایگاه‌دادهٔ رابطه‌ای
مرجع: PostgreSQL")] + FS[("ذخیره‌سازی فایل
تصاویر اطلاعات تکمیلی")] + end + + subgraph External["سرویس‌های بیرونی"] + AUTH["سرویس احراز هویت"] + NOTIF["درگاه اعلان
(درون‌برنامه / پیامک / ایمیل)"] + end + + C1 -->|"HTTPS / JSON"| S1 + S1 --> S2 + S2 --> DB + S2 --> FS + S2 --> AUTH + S2 -.->|"انتشار رویداد"| S3 + S3 --> NOTIF + + classDef ext fill:#fff8e1,stroke:#f9a825,stroke-dasharray: 4 4 + class AUTH,NOTIF ext +``` + +> **توضیح:** پردازش اعلان (M4) به‌صورت **غیرهمگام** از طریق رویداد انجام می‌شود تا `NFR-04` حفظ شود: ثبت گزارش نباید منتظر ارسال پیامک بماند. کانال دقیق اعلان هنوز پرسش باز ۸ است؛ بنابراین `NOTIF` به‌عنوان یک **آداپتور قابل تعویض** مدل شده است. + +## ۶. جریان‌های کلیدی + +### ۶.۱. ورود به سامانه (AD-01، US-15) + +```mermaid +sequenceDiagram + actor U as کاربر + participant UI as رابط کاربری + participant APP as لایهٔ برنامه + participant AUTH as سرویس احراز هویت + participant DB as پایگاه‌داده + + U->>UI: نام کاربری و رمز عبور + UI->>UI: بررسی فیلد خالی + alt فیلد خالی + UI-->>U: «این فیلد الزامی است» + else کامل + UI->>APP: درخواست ورود + APP->>AUTH: اعتبارسنجی + alt سرویس در دسترس نیست + AUTH-->>APP: خطا + APP-->>UI: «خطا در ورود؛ لطفاً بعداً تلاش کنید» + else پاسخ دریافت شد + AUTH-->>APP: نتیجه + alt نامعتبر + APP-->>UI: «نام کاربری یا رمز عبور اشتباه است» + UI-->>U: خطای مبهم (بدون افشای فیلد اشتباه) + else معتبر + APP->>DB: ثبت رویداد ورود + APP-->>UI: نشست + نقش + UI-->>U: داشبورد متناسب با نقش + end + end + end +``` + +**الزامات امنیتی بازتاب‌یافته:** پیام خطا نباید مشخص کند کدام فیلد اشتباه است (`US-15.feature`). این قاعده در لایهٔ برنامه اعمال می‌شود، نه در رابط کاربری. + +### ۶.۲. ثبت گزارش خرابی (AD-02، US-01، US-02) + +```mermaid +sequenceDiagram + actor R as گزارش‌دهنده + participant UI as رابط کاربری + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + + R->>UI: پر کردن فرم + UI->>UI: اعتبارسنجی سمت کلاینت + Note over UI: اجباری: عنوان، توضیحات، محل، نوع تجهیز
حداکثر عنوان: ۱۵۰ کاراکتر + alt نامعتبر + UI-->>R: نمایش خطا کنار فیلد، حفظ مقادیر + else معتبر + UI->>APP: ارسال + APP->>APP: کنترل دسترسی (نقش مجاز) + APP->>DOM: اعتبارسنجی مجدد + alt نامعتبر + DOM-->>APP: نقض قاعده + APP-->>UI: خطای اعتبارسنجی + UI-->>R: نمایش خطا + else معتبر + APP->>DB: درج گزارش (وضعیت: ثبت‌شده) + APP->>DB: درج رویداد تاریخچه REPORT_SUBMITTED + APP-->>UI: شناسهٔ گزارش + UI-->>R: پیام موفقیت + شناسهٔ یکتا + APP-)APP: انتشار ReportSubmitted + end + end +``` + +**نکتهٔ مهم (الزام `US-01.feature`):** «اعتبارسنجی سمت کلاینت» فقط برای تجربهٔ کاربری است. اعتبارسنجی **معتبر** در لایهٔ برنامه و دامنه انجام می‌شود. اگر درخواست مستقیماً به API ارسال شود، همان قواعد باید اعمال شوند. + +### ۶.۳. تخصیص تعمیرکار (AD-03، US-08) + +```mermaid +sequenceDiagram + actor F as کارمند تأسیسات + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + participant N as ماژول اطلاع‌رسانی + + F->>APP: انتخاب گزارش + تعمیرکار + APP->>APP: کنترل دسترسی + APP->>DOM: درخواست تخصیص + DOM->>DOM: بررسی وضعیت مجاز + alt وضعیت مجاز نیست + DOM-->>APP: خطا + APP-->>F: پیام خطا + else مجاز + DOM->>DB: درج تخصیص + DOM->>DB: تغییر وضعیت به ارجاع‌شده + DOM->>DB: درج رویداد تاریخچه TECHNICIAN_ASSIGNED + DOM-)N: انتشار TechnicianAssigned + N->>N: ارسال اعلان (غیرهمگام) + APP-->>F: پیام موفقیت + end +``` + +### ۶.۴. تأیید یا رد تعمیر (AD-05، US-05) + +```mermaid +sequenceDiagram + actor R as گزارش‌دهنده + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + + R->>APP: درخواست تأیید/رد + APP->>APP: کنترل دسترسی + APP->>DB: خواندن گزارش با قفل خوش‌بینانه + APP->>DOM: بررسی قواعد تأیید + DOM->>DOM: ۱) کاربر = گزارش‌دهندهٔ همان گزارش؟
۲) وضعیت = تعمیر انجام‌شده؟
۳) آیا قبلاً بازخورد ثبت شده؟ + alt یکی از شروط نقض شد + DOM-->>APP: رد درخواست + APP-->>R: پیام متناسب + Note over APP,R: «دسترسی مجاز نیست» یا
«این گزارش در وضعیت قابل تأیید نیست» + else مجاز + alt رد بدون دلیل + APP-->>R: «ذکر دلیل رد الزامی است» + else + APP->>DB: درج بازخورد (UNIQUE روی نتیجهٔ تعمیر) + APP->>DB: درج رویداد تاریخچه + alt تأیید + APP->>DB: تغییر وضعیت به بسته‌شده + closed_at + end + APP-->>R: پیام موفقیت + end + end +``` + +**چرا قفل خوش‌بینانه؟** الزام ایدمپوتنسی در `US-05.feature`: «کلیک دوباره روی تأیید یک تغییر وضعیت بیشتر ایجاد نمی‌کند». ترکیب `UNIQUE (report_id, repair_result_id)` در `data-model.md` و بررسی `version` تضمین دوگانه می‌دهد. + +## ۷. دغدغه‌های عرضی (Cross-Cutting Concerns) + +| دغدغه | راهکار | لایه | مستند مرجع | +|---|---|---|---| +| **احراز هویت** | سرویس متمرکز؛ در صورت در دسترس نبودن، پیام «خطا در ورود؛ لطفاً بعداً تلاش کنید» و عدم ورود | برنامه | `FR-01`، `US-15.feature` | +| **مجوزدهی** | کنترل سمت سرور در **هر** درخواست؛ هرگز فقط مخفی‌کردن دکمه در رابط کاربری | برنامه | `FR-02`، `US-15.feature` | +| **اعتبارسنجی** | دو لایه: رابط کاربری (تجربهٔ کاربری) و دامنه (اعتبار واقعی) | نمایش + دامنه | `US-01.feature` | +| **ثبت تاریخچه** | هر تغییر معنادار یک ردیف در `report_history`؛ append-only | برنامه + داده | `FR-16`، `NFR-05` | +| **رویدادهای دامنه** | انتشار پس از commit موفق؛ مصرف غیرهمگام | دامنه → اطلاع‌رسانی | `FR-15` | +| **مدیریت خطا** | خطاهای قابل نمایش (اعتبارسنجی/مجوز) در برابر خطاهای فنی؛ در خطای فنی، پیام عمومی و ثبت جزئیات در لاگ | برنامه | `US-01.feature` | +| **زمان** | همهٔ زمان‌ها با منطقهٔ زمانی (`timestamptz`) | داده | `data-model.md` §۹ | +| **تراکنش** | تغییر وضعیت + درج تاریخچه در یک تراکنش | برنامه + داده | `NFR-05` | + +## ۸. تصمیم‌های معماری (ADR) + +| شناسه | تصمیم | جایگزین ردشده | دلیل | +|---|---|---|---| +| **ADR-01** | مونولیت ماژولار | میکروسرویس | تیم سه‌نفره، نیاز به تراکنش اتمیک، `NFR-06` | +| **ADR-02** | معماری لایه‌ای با وابستگی به سمت دامنه | لایه‌بندی بدون قاعده | جلوگیری از نشت منطق کسب‌وکار به رابط کاربری | +| **ADR-03** | مجوزدهی **سمت سرور** در هر درخواست | فقط مخفی‌کردن در رابط کاربری | `FR-02`، `US-15.feature` (سناریوی دسترسی مستقیم به آدرس) | +| **ADR-04** | اعلان بر پایهٔ **رویداد دامنه** و غیرهمگام | فراخوانی مستقیم ارسال اعلان در همان تراکنش | `NFR-04`، جداسازی M4 از M2/M3 | +| **ADR-05** | قفل خوش‌بینانه (`version`) | قفل بدبینانه | ایدمپوتنسی `US-05.feature` بدون بلوکه‌کردن | +| **ADR-06** | فهرست وضعیت‌ها و انتقال‌ها به‌صورت **داده** | `ENUM` یا `switch` در کد | پرسش‌های باز ۳ و ۴ هنوز بسته نشده‌اند | +| **ADR-07** | تاریخچهٔ append-only | لاگ فایل یا `audit` سطح پایگاه‌داده | قابل مشاهده برای کاربر (`FR-16`، `US-10`) | +| **ADR-08** | شناسه‌های فنی انگلیسی، متن رابط فارسی | همه‌چیز فارسی | تعارض با `CONTRIBUTING.md` ندارد؛ آن بند دربارهٔ متن مستندات است | + +## ۹. نمای استقرار (Deployment View) + +```mermaid +flowchart LR + subgraph Browser["دستگاه کاربر"] + B1["مرورگر"] + end + subgraph Host["میزبان (تک‌سرور یا Docker Compose)"] + W1["سرور برنامه"] + D1[("پایگاه‌داده")] + F1[("ذخیره‌سازی فایل")] + end + B1 -->|HTTPS| W1 + W1 --> D1 + W1 --> F1 + + classDef future fill:#f5f5f5,stroke:#9e9e9e,stroke-dasharray: 4 4 +``` + +**فرض:** برای یک پروژهٔ دانشگاهی، استقرار تک‌سروری (یا `docker compose` با سه سرویس) کافی است. جزئیات فناورانه در پرسش‌های باز آمده‌اند. + +## ۱۰. نگاشت نیازمندی‌های غیرکارکردی به راهکار معماری + +| NFR | راهکار معماری | محل تحقق | +|---|---|---| +| `NFR-01` امنیت | احراز هویت متمرکز، مجوزدهی سمت سرور، هش رمز، عدم افشای فیلد اشتباه | `M1`، لایهٔ برنامه | +| `NFR-02` حریم خصوصی | فیلتر نتایج بر اساس نقش در سرویس‌های کاربردی | لایهٔ برنامه | +| `NFR-03` کاربردپذیری | رابط RTL، اعتبارسنجی درجا، پیام‌های دقیق | لایهٔ نمایش (`ui-wireframes.md`) | +| `NFR-04` زمان پاسخ | ایندکس‌های `data-model.md` §۷، اعلان غیرهمگام، داشبورد به‌صورت نمای تجمیعی | داده + `M4` + `M5` | +| `NFR-05` صحت داده | تراکنش اتمیک، تاریخچهٔ append-only، قفل خوش‌بینانه، ممنوعیت حذف فیزیکی | دامنه + داده | +| `NFR-06` نگهداشت | لایه‌بندی، مرز ماژول، جداسازی دامنه از زیرساخت، ADRها | کل معماری | + +## ۱۱. ریسک‌های معماری + +| # | ریسک | اثر | کاهش | +|---|---|---|---| +| R1 | بسته‌نشدن پرسش باز ۹ (چند نقش برای کاربر) | تغییر در M1 و مدل داده و نمودار کلاس | تصمیم‌گیری پیش از پیاده‌سازی؛ در غیر این صورت تغییر سه سند | +| R2 | نهایی‌نشدن فهرست وضعیت‌ها (پرسش ۴) | تغییر ماشین حالت و انتقال‌های مجاز | ADR-06: انتقال‌ها به‌صورت داده | +| R3 | رشد دامنه به خوابگاه و فضاهای اداری | تأثیر بر مدل محل و مجوزها | مدل محل قابل گسترش (`data-model.md` D2) | +| R4 | انتخاب نشدن کانال اعلان (پرسش ۸) | تأخیر در `M4` | آداپتور قابل تعویض (بخش ۵) | +| R5 | انتخاب‌نشدن فناوری | عدم شروع پیاده‌سازی | پرسش باز ۱۶ | + +## ۱۲. نکات و پرسش‌های باز + +| # | پرسش | نیازمند تصمیم | +|---|---|---| +| ۱۶ | فناوری لایهٔ نمایش، برنامه و داده چیست؟ (زبان، فریم‌ورک، DBMS) | تیم — پیش از فاز پیاده‌سازی | +| ۱۷ | سرویس احراز هویت داخلی ساخته شود یا از راهکار آماده استفاده شود؟ | تیم | +| ۱۸ | ذخیره‌سازی تصاویر اطلاعات تکمیلی در فایل‌سیستم یا پایگاه‌داده؟ | تیم | +| ۱۹ | نیاز به API عمومی یا صرفاً مصرف داخلی رابط کاربری؟ | تیم | +| ۲۰ | سطح دسترسی دقیق مدیر (پرسش باز ۹ در `activity.md`) چه اثری بر M1 و M5 دارد؟ | تیم | +| ۲۱ | آیا نیاز به ثبت تلاش‌های ورود ناموفق و قفل حساب وجود دارد؟ (در مستندات نیامده) | تیم | +| ۲۲ | آیا رابط کاربری باید واکنش‌گرا (Responsive) باشد؟ (در `NFR-03` صریح نیست) | تیم | + +--- + +**وضعیت سند:** نسخه اولیه (Draft) – آماده بازبینی \ No newline at end of file