Files
paya-university-fault-system/docs/analysis/software-architecture.md
T

371 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# معماری نرم‌افزار (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<br/>SCR-01 تا SCR-12"]
end
subgraph A["لایهٔ برنامه (Application)"]
A1["سرویس‌های کاربردی<br/>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["برنامهٔ وب<br/>(رابط RTL فارسی)"]
end
subgraph Server["سرور برنامه"]
S1["API / لایهٔ برنامه"]
S2["ماژول‌های M1 تا M5"]
S3["کارگر پس‌زمینه<br/>(پردازش رویدادها)"]
end
subgraph Data["لایهٔ داده"]
DB[("پایگاه‌دادهٔ رابطه‌ای<br/>مرجع: PostgreSQL")]
FS[("ذخیره‌سازی فایل<br/>تصاویر اطلاعات تکمیلی")]
end
subgraph External["سرویس‌های بیرونی"]
AUTH["سرویس احراز هویت"]
NOTIF["درگاه اعلان<br/>(درون‌برنامه / پیامک / ایمیل)"]
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: اجباری: عنوان، توضیحات، محل، نوع تجهیز<br/>حداکثر عنوان: ۱۵۰ کاراکتر
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: ۱) کاربر = گزارش‌دهندهٔ همان گزارش؟<br/>۲) وضعیت = تعمیر انجام‌شده؟<br/>۳) آیا قبلاً بازخورد ثبت شده؟
alt یکی از شروط نقض شد
DOM-->>APP: رد درخواست
APP-->>R: پیام متناسب
Note over APP,R: «دسترسی مجاز نیست» یا<br/>«این گزارش در وضعیت قابل تأیید نیست»
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) – آماده بازبینی