# معماری نرم‌افزار (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) – آماده بازبینی