الدرس 1 من 12 · التصميم
مشروع Souk Atlas: الاحتياجات، النماذج الأولية، البنية ومستودع Git
الهدف
وضع أسس المشروع المحوري لهذا التكوين: Souk Atlas، سوق إلكترونية (marketplace) يبيع فيها حرفيون مغاربة الزرابي والفخار والجلد والحُليّ، ويدفع فيها الزبائن عبر التحويل البنكي. في نهاية الدرس، ستعرف ما يجب أن يفعله التطبيق، وكيف يبدو، وكيف تتواصل مكوّناته الثلاثة، وسيكون لديك مستودع Git نظيف لا يحتوي على أيّ سرّ.
أهم النقاط
- يبدأ مشروع fullstack (التطوير الشامل) على الورق: قصص المستخدم، والنماذج الأولية، وقائمة ما هو خارج النطاق.
- يتكوّن Souk Atlas من ثلاثة مكوّنات: واجهة أمامية React (المتصفّح)، وAPI Node.js/Express (قواعد العمل)، وقاعدة بيانات PostgreSQL (مرجع حقيقة البيانات).
- لا يتحدّث المتصفّح أبدًا مباشرة مع قاعدة البيانات: كل شيء يمرّ عبر الـ API، التي تتحقّق وتعيد الحساب.
- مستودع Git واحد (« monorepo ») يضمّ
api/وweb/وdocs/يكفي تمامًا لفريق صغير. - الأسرار (
.env) لا توضع أبدًا في Git: نضع مكانها تحت التحكم في الإصدارات قالبًا.env.example.
1. ما الذي يجمعه هذا التكوين
لقد تعلّمت المكوّنات واحدًا تلو الآخر في تكوينات MasterLinux الأخرى:
| المكوّن | أين تعلّمته؟ | ما الذي نفعله به هنا |
|---|---|---|
HTML، CSS، JavaScript، fetch |
تكوين Web (الدروس 2.x و3.2) | الواجهة والاستدعاءات إلى الـ API |
| Node.js، Express | تكوين Node.js | الـ API REST للسوق الإلكترونية |
| React | تكوين React | الواجهة الأمامية: الكتالوج، السلة، فضاء البائع |
| SQL، PostgreSQL | تكوين SQL | المخطّط (schema) والاستعلامات |
| Docker، Compose | تكوين Docker (الدروس 1.4 و2.4 و2.5 و3.3) | التغليف والنشر |
| GitHub Actions | تكوين CI/CD (الدروس 1.2 و2.1 و2.2) | الاختبارات والبناء الآليّان |
لا يعيد هذا التكوين شرح هذه الأساسيات: بل يُبيّن كيف نجعلها تعمل معًا على منتج حقيقي، مع القرارات التي يتّخذها مطوّر fullstack كل يوم. عندما يكون مفهوم ما قد سبق شرحه، نحيلك إلى الدرس المعنيّ.
2. الزبون وحاجته
الزبون: تعاونية خيالية، « Souk Atlas SARL »، مقرّها في فاس. تريد مساعدة نحو خمسين حرفيًا من فاس وآسفي ومراكش والصويرة على البيع عبر الإنترنت، دون الاعتماد على منصة أجنبية تأخذ عمولة كبيرة.
ما نعرفه عن مستخدميها:
- المشترون موجودون أساسًا في المغرب، ويستعملون الهاتف، وكثير منهم لا يملك بطاقة بنكية صالحة للدفع عبر الإنترنت؛
- الحرفيون ليسوا متخصّصين في المعلوميات: يجب أن تكون شاشتهم بسيطة؛
- تتسلّم التعاونية التحويلات في حسابها، ثم تعيد توزيعها على الحرفيين.
ومن هنا القرار الأكثر تأثيرًا في بنية المشروع: يتمّ الدفع عبر التحويل البنكي. يقدّم المشتري طلبيته، فيتلقّى RIB التعاونية ومرجعًا ينسخه في سبب (motif) التحويل؛ ثم يطابق مسؤول (administrateur) التحويلات المستلمة ويضع على الطلبيات علامة « مدفوعة ». أما الدفع بالبطاقة فسيأتي في الإصدار 2.
3. قصص المستخدم
تصف قصة المستخدم (user story) حاجة من وجهة نظر صاحبها، في جملة واحدة: « بصفتي دورًا، أقوم بشيء ما من أجل الحصول على فائدة ». ولا تقول كيف نبرمجها.
| الدور | القصة | الأولوية |
|---|---|---|
| مشترٍ | بصفتي مشتريًا، أتصفّح الكتالوج حسب الفئة وأبحث عن منتج باسمه | ضروري |
| مشترٍ | بصفتي مشتريًا، أملأ سلة وأرى المجموع بالدرهم، شاملًا التوصيل | ضروري |
| مشترٍ | بصفتي مشتريًا، أقدّم طلبيتي وأتلقّى المرجع الذي أكتبه في تحويلي | ضروري |
| مشترٍ | بصفتي مشتريًا، أتابع حالة طلبيتي (في الانتظار، مدفوعة، مُرسَلة) | ضروري |
| بائع | بصفتي بائعًا، أضيف منتجاتي وأعدّلها وأسحبها | ضروري |
| بائع | بصفتي بائعًا، أرى الطلبيات المدفوعة وأضع عليها علامة « مُرسَلة » | ضروري |
| مسؤول | بصفتي مسؤولًا، أضع على الطلبية علامة « مدفوعة » عندما يصل التحويل | ضروري |
| مشترٍ | بصفتي مشتريًا، أترك رأيًا حول منتج | لاحقًا |
كتابة عمود الأولوية لا تقلّ أهمية عن الباقي: فهو يحدّد المنتج الأدنى القابل للاستمرار (MVP)، أي ما نسلّمه أولًا.
أما قائمة خارج النطاق، المكتوبة بوضوح تام، فتجنّب النقاشات التي لا تنتهي: الدفع بالبطاقة، والتطبيق المحمول الأصلي (native)، والمراسلة بين المشتري والبائع، وتدبير المرتجعات. ليس المقصود « أبدًا »، بل « ليس في هذا الإصدار ».
4. النماذج الأولية
قبل البرمجة، نرسم الشاشات. يكفي قلم وورقة، أو أداة مثل Figma أو Penpot. إليك الشاشات الخمس للإصدار 1، في شكل نموذج « هيكل سلكي » (wireframe):
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ SOUK ATLAS [Rechercher… ] 🛒 2 │ │ ← Catalogue │
├─────────────────────────────────────┤ │ ┌──────────┐ Bol en céramique Safi │
│ Tapis · Poterie · Cuir · Bijoux │ │ │ photo │ 150,00 DH │
│ ┌──────┐ ┌──────┐ ┌──────┐ │ │ └──────────┘ Atelier Belkhayat │
│ │photo │ │photo │ │photo │ │ │ Quantité [1] [Ajouter au panier] │
│ │Tapis │ │Bol │ │Babou-│ │ │ Description… │
│ │4500DH│ │150 DH│ │120 DH│ │ └─────────────────────────────────────┘
│ └──────┘ └──────┘ └──────┘ │ 2. Fiche produit
│ ‹ 1 2 3 › │
└─────────────────────────────────────┘
1. Catalogue
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ Panier │ │ Commande SA-2026-000123-77 │
│ Bol de Safi x2 300,00 DH │ │ À virer : 450,00 DH │
│ Babouches x1 120,00 DH │ │ RIB : 011 780 0000123456789012 34 │
│ Livraison 30,00 DH │ │ Motif : SA-2026-000123-77 │
│ Total (dont TVA 70 DH) 450,00 DH │ │ Statut : en attente du virement │
│ [Nom] [Téléphone] [Adresse] [Ville] │ └─────────────────────────────────────┘
│ [Valider la commande] │ 4. Confirmation
└─────────────────────────────────────┘
3. Panier et livraison 5. Espace vendeur : mes produits, mes commandes
(المصطلحات: Catalogue = الكتالوج، Fiche produit = بطاقة المنتج، Panier et livraison = السلة والتوصيل، Confirmation = التأكيد، Espace vendeur = فضاء البائع، Rechercher = بحث، Ajouter au panier = أضف إلى السلة، Quantité = الكمية، Livraison = التوصيل، Valider la commande = تأكيد الطلبية، À virer = المبلغ الواجب تحويله، Motif = سبب التحويل، Statut : en attente du virement = الحالة: في انتظار التحويل.)
تكشف النماذج الأولية أسئلة يخفيها النص: هل يلزم حساب لتقديم طلبية؟ (نعم، لمتابعة الطلبية)؛ هل التوصيل مجاني؟ (فوق 500 درهم)؛ هل الأسعار شاملة للضريبة (TTC)؟ (نعم، كما في كل تجارة بالمغرب؛ فالضريبة على القيمة المضافة بنسبة 20 % مُدرَجة ومعروضة للعلم).
دوّن هذه القرارات في docs/: فبعد ثلاثة أشهر، لن يتذكّرها أحد.
5. البنية
Navigateur (téléphone, PC)
┌────────────────────────┐ HTTPS + JSON ┌──────────────────────────┐ SQL ┌──────────────┐
│ Front React (Vite) │ ────────────────▶ │ API Node.js / Express │ ─────────▶ │ PostgreSQL │
│ catalogue, panier, │ ◀──────────────── │ validation, prix, stock │ ◀───────── │ tables, │
│ espace vendeur │ cookie session │ sessions, commandes │ │ contraintes │
└────────────────────────┘ └──────────────────────────┘ └──────────────┘
fichiers statiques servis par nginx port 3000 (local) port 5432 (local)
(المصطلحات: Navigateur (téléphone, PC) = المتصفّح (هاتف، حاسوب)، catalogue, panier, espace vendeur = الكتالوج، السلة، فضاء البائع، validation, prix, stock = التحقّق، الأسعار، المخزون، sessions, commandes = الجلسات، الطلبيات، tables, contraintes = الجداول، القيود، cookie session = كعكة (cookie) الجلسة، fichiers statiques servis par nginx = ملفات ثابتة يقدّمها nginx.)
لكل مكوّن مسؤولية واحدة:
- الواجهة الأمامية تعرض وتجمع المعطيات. يمكنها حساب مجموع « للعلم »، لكنها لا تملك أيّ سلطة: فالمستخدم الماكر يستطيع تعديل أيّ حساب يُجرى في متصفّحه.
- الـ API تطبّق القواعد: تعيد قراءة الأسعار من قاعدة البيانات، وتتحقّق من المخزون، وتراقب من له الحق في فعل ماذا، وترفض الباقي برمز خطأ واضح.
- قاعدة البيانات تحفظ المعطيات وتحمي اتّساقها بواسطة قيود (constraints) (لا يمكن أن يكون المخزون سالبًا، والطلبية تنتمي حتمًا إلى مشترٍ موجود).
لماذا هذه التقنيات؟ ليس اتّباعًا للموضة: React يملك منظومة ضخمة ومطوّرين متوفّرين في المغرب؛ وExpress هو خادم Node.js الأكثر انتشارًا والأوفر توثيقًا؛ وPostgreSQL يوفّر معاملات (transactions) متينة، لا غنى عنها بمجرّد التعامل مع المال والمخزون. كان بإمكان فريق آخر أن يختار شيئًا آخر؛ المهمّ هو أن تعرف كيف تبرّر اختيارك.
يقترح موقع توثيق React طريقة لتقسيم واجهة إلى مكوّنات (components) انطلاقًا من نموذج أولي (« Penser en React »): وهذا بالضبط ما سنفعله في المستوى 3.
6. مستودع Git
مستودع واحد للمشروع كله:
souk-atlas/
├── README.md ← ce qu'est le projet, comment le lancer
├── .gitignore ← node_modules/, .env, dist/…
├── .env.example ← les variables attendues, SANS valeurs secrètes
├── compose.yaml ← (niveau 4) la pile complète
├── docs/
│ ├── BESOINS.md ← récits utilisateur, hors périmètre, décisions
│ ├── API.md ← (leçon 1.3) le contrat de l'API
│ └── maquettes/
├── api/ ← le back-end Node.js
│ ├── package.json
│ ├── migrations/ ← (leçon 1.2) le schéma SQL, versionné
│ ├── src/
│ └── test/
└── web/ ← le front React
├── package.json
└── src/
(المصطلحات: README.md = ما هو المشروع وكيف نشغّله؛ .gitignore = node_modules/ و.env وdist/…؛ .env.example = المتغيّرات المنتظرة، دون قيم سرّية؛ compose.yaml = (المستوى 4) المنظومة الكاملة؛ BESOINS.md = قصص المستخدم، خارج النطاق، القرارات؛ API.md = (الدرس 1.3) عقد الـ API؛ maquettes = النماذج الأولية؛ api/ = الواجهة الخلفية (back-end) بـ Node.js؛ migrations/ = (الدرس 1.2) مخطّط SQL تحت التحكم في الإصدارات؛ web/ = الواجهة الأمامية React.)
أنشئه:
mkdir -p souk-atlas/{api/src,web/src,docs}
cd souk-atlas
touch api/src/.gitkeep web/src/.gitkeep # Git لا يتتبّع المجلدات الفارغة
printf '%s\n' 'node_modules/' '.env' 'dist/' 'coverage/' '*.log' > .gitignore
git init -b main
git add .
git commit -m "Initialisation du projet Souk Atlas"
الأسرار: .env و.env.example
ستحتاج الـ API إلى عنوان قاعدة بيانات وإلى سرّ للجلسة. تعيش هذه القيم في ملف .env، مختلف على كل جهاز، ولا يُوضع أبدًا في commit:
# .env.example (تحت التحكم في الإصدارات: يوثّق ما يجب ملؤه)
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://souk:souk@localhost:5432/souk_atlas
SESSION_SECRET=
ينسخ كل مطوّر القالب: cp .env.example .env، ثم يملأ قيمه. تحقّق من أنه متجاهَل فعلًا:
git status --short # يجب ألّا يظهر .env
git check-ignore -v .env # يعرض قاعدة .gitignore التي تتجاهله
كلمة مرور دُفعت (push) إلى GitHub، ولو لدقيقة واحدة، يجب اعتبارها مُخترَقة: يجب تغييرها. فتاريخ Git يحتفظ بكل شيء.
طريقة العمل
بالنسبة لفريق صغير: الفرع main قابل للتسليم دائمًا؛ وكل خاصية (feature) تُطوَّر على فرع (feature/panier)، ثم تصل إلى main عبر pull request تُراجَع وتُختبَر آليًا (الدرس 4.2). رسائل commit واضحة (« Ajoute le calcul de la livraison ») أفضل من اصطلاح معقّد لا يتبعه أحد.
7. خطة التكوين
| المستوى | الدروس | ما تُنتجه |
|---|---|---|
| 1 التصميم | 1.1 إلى 1.3 | الاحتياجات، مخطّط SQL، عقد الـ API |
| 2 الواجهة الخلفية (Back-end) | 2.1 إلى 2.4 | الـ API: الهيكل، الكتالوج، الحسابات، الطلبيات |
| 3 الواجهة الأمامية (Front-end) | 3.1 و3.2 | الواجهة الأمامية React كاملة |
| 4 الإطلاق في الإنتاج | 4.1 إلى 4.3 | الاختبارات، الحاويات، CI، النشر على VPS |
تُنجَز التمارين داخل حاوية Node.js دون اتصال بالإنترنت: من المستحيل تثبيت Express أو React أو PostgreSQL فيها. لذلك ستكتب فيها مكوّنات العمل (business logic) للمشروع (التحقّق، حساب السلة، مرجع التحويل، موجّه HTTP، مخطّط SQL…) باستعمال الوحدات المدمجة في Node.js وحدها، وكل عملية تحقّق تنفّذ شيفرتك. هذه المكوّنات هي بالضبط تلك التي ستربطها بعد ذلك بـ Express وReact على جهازك الخاص.
أخطاء متكررة
- البرمجة قبل معرفة ما يجب برمجته: دون قصص ولا نماذج أولية، نكتشف الأسئلة الحقيقية (TTC أم HT؟ هل الحساب إجباري؟) في منتصف التطوير، فنعيد العمل.
- نسيان ما هو خارج النطاق: كل اجتماع يضيف « مجرّد وظيفة صغيرة »، ولا يخرج المشروع أبدًا.
- وضع
.envفي commit: أضف.envإلى.gitignoreقبل أولgit add .، وليس بعده. - الثقة في الواجهة الأمامية: المجموع أو السعر أو الدور الذي يرسله المتصفّح يمكن لأيّ شخص تعديله بأدوات المطوّر.
التطبيق العملي (Lab)
ستُنشئ هيكل مستودع Souk Atlas في /root/souk-atlas: الشجرة api/ وweb/ وdocs/ وملف README؛ ووثيقة الاحتياجات مع قصص المستخدم وما هو خارج النطاق؛ وحماية الأسرار (.gitignore و.env.example)؛ وأخيرًا مستودع Git على الفرع main مع أول commit، مع التحقّق من أن ملفًا .env حقيقيًا يبقى خارج Git.
اختبار قصير
- لماذا يجب ألّا يرسل المتصفّح سعر المنتج إلى الـ API لحظة تقديم الطلبية؟
- ما الفرق بين
.envو.env.example؟ - ما فائدة قائمة « خارج النطاق » في وثيقة الاحتياجات؟
- لماذا يستعمل Souk Atlas مرجعًا للطلبية في سبب التحويل؟
- دفع زميل كلمة المرور الحقيقية لقاعدة البيانات في commit، ثم حذفها في الـ commit الموالي. هل حُلّت المشكلة؟
الأجوبة
- لأن كل ما يأتي من المتصفّح يمكن للمستخدم تعديله. لا تتلقّى الـ API إلا معرّفات المنتجات والكميات، ثم تعيد قراءة الأسعار من قاعدة البيانات.
- يحتوي
.envعلى القيم الحقيقية (كلمة المرور، السرّ) الخاصة بـ هذا الجهاز ولا يوضع أبدًا تحت التحكم في الإصدارات؛ أما.env.exampleفيوضع تحت التحكم في الإصدارات، ويسرد المتغيّرات المنتظرة ولا يحتوي على أيّ قيمة سرّية. - لتحديد ما لن يُنجَز في هذا الإصدار، من أجل حماية الآجال وتجنّب الإضافات المتواصلة.
- لكي يتمكّن المسؤول من مطابقة كل تحويل مستلم مع الطلبية الصحيحة؛ كما أن مفتاح التحقّق (الدرس 2.4) يكشف أخطاء الكتابة.
- لا: فتاريخ Git يحتفظ بالـ commit القديم. يجب اعتبار كلمة المرور مُخترَقة وتغييرها، ثم تنظيف التاريخ إن اقتضى الأمر.