added software-architecture

This commit is contained in:
2026-11-05 20:19:55 +03:30
parent 45ee92efb0
commit 969d2fa188
+369
View File
@@ -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<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) – آماده بازبینی