منطق البرمجي السجين: كيف حررتني المعمارية السداسية (Hexagonal Architecture) من قيود أطر العمل؟

يا أهلاً وسهلاً فيكم، معكم أخوكم أبو عمر.

قبل عدة سنوات، كنت غارقًا حتى أذني في مشروع لأحد العملاء. كان تطبيقًا واعدًا لإدارة الطلبات، وبدأنا العمل بحماس باستخدام إطار عمل شهير للويب. في البداية، كانت الأمور تسير كالحلاوة. الصفحات تظهر، البيانات تُحفظ في قاعدة البيانات، والعميل مبسوط. كنت أظن أني “مسيطر” على الوضع، وأن الكود نظيف ومرتب داخل مجلدات إطار العمل.

بعد ستة أشهر، بدأت الكوابيس. قال العميل: “أبو عمر، بدنا نضيف تطبيق موبايل للسائقين يتكلم مع نفس النظام”. بسيطة، فكرت في نفسي، نعمل API. ثم أضاف: “وبدنا شريك جديد يبعثلنا طلبات عبر بروتوكول مختلف تمامًا، مش HTTP”. هنا بدأت أشعر بالورطة. وبعدها أتت القشة التي قصمت ظهر البعير: “قررنا نغير قاعدة البيانات من MySQL لشيء أكثر قوة للبيانات الجغرافية”.

وقتها نظرت إلى الكود الذي كتبته… يا ويلي! منطق العمل الأساسي (حساب التكاليف، تعيين الطلبات، تتبع المسارات) كان متناثرًا ومختلطًا بشكل مباشر مع كود إطار العمل. دوال الـ Controller تستدعي الـ Models الخاصة بالـ ORM مباشرة، والـ Models نفسها تحتوي على منطق برمجي حساس. كل شيء يعتمد على كل شيء. قلت في نفسي: “يا زلمة، كيف بدنا نعمل كل هاد والشغل كله ملزّق ببعضه؟”. شعرت أن منطقي البرمجي، جوهر التطبيق، أصبح سجينًا داخل إطار العمل. أي محاولة لتغيير جزء كانت تهدد بانهيار أجزاء أخرى. كان جحيمًا من التبعيات.

هذه التجربة القاسية كانت أفضل درس تعلمته في مسيرتي. لقد أجبرتني على البحث عن مخرج، عن طريقة لبناء برمجيات لا تخاف من التغيير. وكان المخرج هو ما يعرف بـ “المعمارية السداسية” (Hexagonal Architecture). اليوم، سأشارككم كيف أنقذتني هذه الفلسفة، وكيف يمكنها أن تنقذكم أيضًا.

السجن الذي كنت فيه: تبعية المنطق البرمجي لإطار العمل

قبل أن نتحدث عن الحل، دعونا نفهم طبيعة السجن. في معمارية الطبقات التقليدية (Traditional Layered Architecture)، غالبًا ما نرى تدفقًا للتبعية في اتجاه واحد: واجهة المستخدم تعتمد على منطق العمل، ومنطق العمل يعتمد على طبقة الوصول للبيانات، وطبقة الوصول للبيانات تعتمد على قاعدة البيانات وإطار العمل.

المشكلة هي أن “منطق العمل” (Domain/Business Logic)، وهو الجزء الأهم والأكثر قيمة في تطبيقك، يصبح معتمدًا على تفاصيل تقنية مثل قاعدة البيانات أو إطار عمل الويب. هذا هو الانقلاب الخطير الذي يسبب كل المشاكل.

أعراض السجن: كيف تعرف أنك في ورطة؟

اسأل نفسك هذه الأسئلة. إذا كانت الإجابة “نعم” على معظمها، فأنت على الأغلب في نفس السجن الذي كنت فيه:

  • هل تحتاج إلى تشغيل خادم الويب وقاعدة البيانات بالكامل فقط لتختبر دالة صغيرة في منطق العمل (مثلاً: دالة حساب الخصم)؟
  • إذا قررت تغيير الـ ORM (مثلاً من Eloquent إلى Doctrine)، هل ستحتاج إلى إعادة كتابة أجزاء كبيرة من منطق العمل؟
  • هل الكود الخاص بمنطق العمل مليء بكائنات خاصة بإطار العمل (مثل HttpRequest, JsonResponse, أو نماذج الـ ORM)؟
  • هل فكرة إضافة واجهة جديدة (مثل واجهة سطر الأوامر CLI أو مستهلك رسائل RabbitMQ) تبدو كمشروع ضخم ومعقد؟

إذا كانت هذه الأعراض مألوفة، فلا تقلق. هناك مفتاح للخروج.

المعمارية السداسية: مفتاح الخروج من السجن

المعمارية السداسية، التي أطلق عليها مبتكرها أليستير كوبيرن اسم “المنافذ والمحولات” (Ports and Adapters)، هي نمط معماري يهدف إلى تحقيق هدف واحد نبيل: عزل منطق التطبيق والعمل الأساسي عن العوامل الخارجية مثل واجهات المستخدم، وقواعد البيانات، والخدمات الخارجية.

تخيل أن تطبيقك هو “قلعة” أو “سداسي” في المنتصف. هذا السداسي هو جوهر النظام، منطق العمل النقي. هذا الجوهر لا يعرف شيئًا عن العالم الخارجي. لا يعرف هل يتحدث معه مستخدم عبر الويب، أو تطبيق موبايل، أو نظام آخر. لا يعرف هل يحفظ بياناته في ملف نصي، أو قاعدة بيانات SQL، أو قاعدة بيانات NoSQL.

المكونات الأساسية: الداخل، الخارج، المنافذ، والمحولات

لفهم الفكرة، دعنا نقسمها إلى أجزاء بسيطة:

  1. الداخل (Inside / The Hexagon): هذا هو قلب تطبيقك. يحتوي على:
    • كيانات المجال (Domain Entities): كائنات تمثل مفاهيم العمل الأساسية (مثل User, Order, Product). هذه كائنات بسيطة لا تحتوي على أي كود يتعلق بالبنية التحتية.
    • حالات الاستخدام (Use Cases): هي العمليات التي يمكن للنظام القيام بها (مثل RegisterUser, PlaceOrder). هي التي تنسق عمل الكيانات وتحتوي على منطق التطبيق.
  2. المنافذ (Ports): هي بوابات القلعة. هي مجرد واجهات (Interfaces) يُعرّفها “الداخل” ليحدد كيف يريد التحدث مع العالم الخارجي. المنافذ لا تحتوي على أي كود تنفيذي. هي مجرد عقد (Contract).
    • منافذ الإدخال (Driving/Input Ports): تصف كيف يمكن للعوامل الخارجية (مثل واجهة المستخدم) أن تتفاعل مع التطبيق. عادة ما تكون واجهة بسيطة تنفذها “حالة الاستخدام”.
    • منافذ الإخراج (Driven/Output Ports): تصف الخدمات التي يحتاجها التطبيق من العالم الخارجي (مثل “أحتاج طريقة لحفظ مستخدم” أو “أحتاج طريقة لإرسال بريد إلكتروني”).
  3. المحولات (Adapters): هي الجسور التي تربط العالم الخارجي بـ “المنافذ”. هي التنفيذ الفعلي للواجهات.
    • محولات الإدخال (Driving Adapters): هي التي “تقود” التطبيق. مثال: وحدة تحكم API (API Controller) تستقبل طلب HTTP، تستدعي “حالة الاستخدام” المناسبة عبر منفذ الإدخال، ثم تحول النتيجة إلى استجابة HTTP.
    • محولات الإخراج (Driven Adapters): هي التي “يقودها” التطبيق. مثال: كلاس ينفذ واجهة “مستودع المستخدمين” (UserRepository Port) باستخدام مكتبة PostgreSQL. التطبيق يطلب “احفظ هذا المستخدم”، والمحول يترجم هذا الطلب إلى استعلامات SQL.

القاعدة الذهبية هنا هي قاعدة التبعية (Dependency Rule): كل التبعيات تشير إلى الداخل. المحولات تعتمد على المنافذ الموجودة في الداخل، لكن الداخل لا يعتمد أبدًا على أي محول.

مثال عملي: خلينا نشوف كود يا جماعة

لنأخذ مثال تسجيل مستخدم جديد. سأستخدم TypeScript لتوضيح الواجهات بشكل جيد، لكن المبدأ يطبق على أي لغة.

1. الداخل (Domain & Application Layers)

هذا هو الكود النقي الذي لا يعتمد على أي إطار عمل.

// src/domain/user.entity.ts
// كيان بسيط جداً (POJO - Plain Old JavaScript Object)
export class User {
  id: string;
  name: string;
  email: string;
  passwordHash: string; // كلمة المرور تكون مشفرة بالفعل هنا
}

// src/application/ports/user.repository.port.ts
// "منفذ" يصف ما نحتاجه من العالم الخارجي لحفظ واسترجاع المستخدمين
export interface IUserRepository {
  findByEmail(email: string): Promise<User | null>;
  save(user: User): Promise<void>;
}

// src/application/use-cases/register-user.use-case.ts
// "حالة استخدام" تحتوي على منطق العمل لتسجيل المستخدم
import { User } from '../../domain/user.entity';
import { IUserRepository } from '../ports/user.repository.port';

export class RegisterUserUseCase {
  // نعتمد على "المنفذ" (الواجهة)، وليس على التنفيذ الفعلي
  constructor(private readonly userRepository: IUserRepository) {}

  async execute(input: { name: string; email: string; rawPassword: string }): Promise<void> {
    const existingUser = await this.userRepository.findByEmail(input.email);
    if (existingUser) {
      throw new Error('مستخدم بهذا البريد الإلكتروني موجود بالفعل.');
    }

    // ... هنا يكون منطق العمل الحقيقي ...
    // مثلاً: التحقق من قوة كلمة المرور، تشفيرها، إنشاء كائن المستخدم
    const passwordHash = await this.hashPassword(input.rawPassword);
    
    const user = new User();
    user.id = crypto.randomUUID(); // مثال
    user.name = input.name;
    user.email = input.email;
    user.passwordHash = passwordHash;
    
    await this.userRepository.save(user);
  }

  private async hashPassword(password: string): Promise<string> {
    // في تطبيق حقيقي، ستستخدم مكتبة مثل bcrypt
    return `hashed_${password}`;
  }
}

لاحظ جمال هذا الكود. يمكنك اختباره بالكامل دون الحاجة إلى قاعدة بيانات أو خادم ويب. يمكنك ببساطة إنشاء تنفيذ وهمي (Mock) للواجهة IUserRepository.

2. الخارج (Infrastructure Layer – Adapters)

هنا يأتي دور العالم الخارجي والتفاصيل التقنية.

// src/infrastructure/db/postgres-user.repository.adapter.ts
// "محول" لقاعدة البيانات. هذا هو التنفيذ الفعلي للمنفذ
import { IUserRepository } from '../../application/ports/user.repository.port';
import { User } from '../../domain/user.entity';
import { PrismaClient } from '@prisma/client'; // مثال باستخدام Prisma ORM

export class PostgresUserRepositoryAdapter implements IUserRepository {
  private prisma = new PrismaClient();

  async findByEmail(email: string): Promise<User | null> {
    const dbUser = await this.prisma.user.findUnique({ where: { email } });
    if (!dbUser) return null;
    // تحويل من نموذج قاعدة البيانات إلى كيان المجال
    return this.toDomain(dbUser);
  }

  async save(user: User): Promise<void> {
    // تحويل من كيان المجال إلى نموذج قاعدة البيانات
    const dbUser = this.toPersistence(user);
    await this.prisma.user.create({ data: dbUser });
  }

  // دوال التحويل مهمة لفصل العالمين
  private toDomain(dbUser: any): User {
      const user = new User();
      user.id = dbUser.id;
      user.name = dbUser.name;
      // ...الخ
      return user;
  }

  private toPersistence(user: User): any {
      return {
          id: user.id,
          name: user.name,
          email: user.email,
          password_hash: user.passwordHash,
      };
  }
}

// src/infrastructure/web/express.controller.ts
// "محول" آخر، هذه المرة لواجهة الويب (Express.js)
import { Request, Response } from 'express';
import { RegisterUserUseCase } from '../../application/use-cases/register-user.use-case';
import { PostgresUserRepositoryAdapter } from '../db/postgres-user.repository.adapter';

// عملية "تجميع" التبعيات (Dependency Injection)
const userRepository = new PostgresUserRepositoryAdapter();
const registerUserUseCase = new RegisterUserUseCase(userRepository);

export async function registerUserController(req: Request, res: Response) {
  try {
    const { name, email, password } = req.body;
    await registerUserUseCase.execute({ name, email, rawPassword: password });
    res.status(201).send({ message: 'تم تسجيل المستخدم بنجاح' });
  } catch (error: any) {
    res.status(400).json({ message: error.message });
  }
}

الآن، إذا أردنا تغيير قاعدة البيانات إلى MongoDB، ماذا نفعل؟ بسيطة. نكتب محولاً جديدًا MongoUserRepositoryAdapter ينفذ نفس الواجهة IUserRepository، ثم نقوم بتبديله في ملف الـ Controller. منطق العمل في RegisterUserUseCase لن يتغير منه حرف واحد!

طعم الحرية: لماذا ستعشق هذه المعمارية؟

عندما تبدأ بتطبيق هذه المبادئ، ستشعر بالحرية التي شعرت بها. أهم الفوائد هي:

  • قابلية الاختبار الفائقة: يمكنك اختبار 90% من منطقك البرمجي في ثوانٍ، بمعزل عن أي تفاصيل بطيئة وغير موثوقة مثل الشبكة أو قاعدة البيانات.
  • استقلالية التكنولوجيا: “بدي أغير من Express لـ Fastify؟ بسيطة!”. “بدنا نستخدم RabbitMQ بدلًا من API؟ بسيطة!”. طالما أنك تبني محولًا جديدًا، فالجوهر يبقى آمنًا ومستقرًا.
  • تطور وتوسع مرن: يمكنك تطوير منطق العمل وتطوير البنية التحتية بشكل منفصل وعلى التوازي. فريق يمكنه العمل على تحسين أداء قاعدة البيانات، وفريق آخر يضيف ميزات جديدة لمنطق العمل، دون أن يعيق أحدهما الآخر.
  • منطق عمل واضح: عندما تنظر إلى مجلدات “الداخل” (Domain/Application)، فأنت ترى قصة تطبيقك الحقيقية، خالية من ضوضاء التفاصيل التقنية.

نصائح من شايب في الكار

بعد سنوات من استخدام هذا النمط، تعلمت بعض الدروس التي أود مشاركتها معكم:

متى تستخدمها ومتى لا؟

هذه المعمارية ليست حلاً لكل المشاكل. إذا كنت تبني موقعًا بسيطًا جدًا، أو تطبيق CRUD مباشر، أو نموذجًا أوليًا سريعًا لرميه لاحقًا، فقد تكون هذه المعمارية overkill أو مبالغًا فيها. استخدمها في المشاريع التي تتوقع أن تعيش طويلًا، والتي يمثل فيها منطق العمل المعقد القيمة الحقيقية للمنتج.

لا تفرط في التجريد

ليس عليك إنشاء منفذ ومحول لكل شيء صغير. ابدأ بالأماكن الأكثر أهمية والأكثر عرضة للتغيير: قاعدة البيانات، خدمات الدفع، خدمات الإشعارات، إلخ. يمكنك دائمًا زيادة التجريد لاحقًا عند الحاجة (Refactoring).

هيكلية المجلدات مهمة

نظّم مشروعك بطريقة تعكس هذه المعمارية. هيكلية شائعة وفعالة هي الفصل حسب الطبقة المعمارية:

/src
  /domain
    /entities
    /value-objects
  /application
    /ports
    /use-cases
  /infrastructure
    /db         (محولات قاعدة البيانات)
    /web        (محولات الويب: Controllers)
    /messaging  (محولات أنظمة الرسائل)

الخلاصة: حرّر منطقك البرمجي! 🔑

المعمارية السداسية ليست مجرد مجموعة من القواعد، بل هي فلسفة. هي تذكير دائم بأن منطق العمل هو الجوهرة الثمينة في تطبيقك، ويجب حمايتها من التفاصيل العابرة والمتغيرة للتكنولوجيا. أطر العمل، قواعد البيانات، المكتبات… كلها أدوات تأتي وتذهب. أما منطق عملك، فهو القيمة الدائمة التي تبني عليها مشروعك.

في المرة القادمة التي تبدأ فيها مشروعًا جديدًا، لا تجعل منطقك البرمجي سجينًا. امنحه الحرية التي يستحقها، وسيكافئك بنظام مرن وقوي وقابل للصيانة لسنوات وسنوات.

أتمنى لكم كل التوفيق في رحلتكم البرمجية. سلام.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

أتمتة العمليات

تنبيهاتي كانت تضيع في بحر الإيميلات: كيف أنقذني ChatOps من فوضى إدارة الحوادث والنشر؟

في هذه المقالة، أشارككم قصة حقيقية عن كيفية تحولي من مطاردة التنبيهات في صندوق بريد إلكتروني فوضوي إلى إدارة مركزية وفعالة للحوادث وعمليات النشر. سأغوص...

3 أبريل، 2026 قراءة المزيد
خوارزميات

خوارزمية A*: كيف أنقذتني من جحيم المسارات الغبية وشخصياتي التي تصطدم بالجدران

أشارككم تجربتي الشخصية مع خوارزميات إيجاد المسار، وكيف انتقلت من شخصيات ألعاب غبية تصطدم بالجدران إلى مسارات ذكية وفعالة باستخدام خوارزمية A*. دليل شامل للمبتدئين...

3 أبريل، 2026 قراءة المزيد
صورة المقال
تسويق رقمي

محتواي كان يضيع في الزحام: كيف بنيت آلة لتوليد آلاف الصفحات المستهدفة باستخدام SEO البرمجي (Programmatic SEO)؟

في هذه المقالة، أشاركك تجربتي كـ "أبو عمر"، مطور فلسطيني، في الانتقال من كتابة المحتوى يدوياً إلى بناء نظام مؤتمت لتوليد آلاف الصفحات باستخدام الـ...

3 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

تطبيقي كان يطرد المستخدمين دون قصد: كيف أنقذتني ‘إمكانية الوصول’ من جحيم التصميم الإقصائي؟

أشارككم قصتي يا جماعة الخير، كيف أن تطبيقي الذي بنيته بجهد وتعب كان يقصي فئة كبيرة من المستخدمين دون علمي. هذه المقالة هي رحلتي من...

3 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

بياناتي كانت تتضارب في سباق محموم: كيف أنقذتني ‘معاملات قاعدة البيانات’ (Transactions) من جحيم الفوضى؟

أشارككم قصة حقيقية من بداياتي في البرمجة، حين كادت طلبات العملاء المتزامنة أن تدمر مخزون متجري الإلكتروني. اكتشفوا معي كيف أنقذتني "معاملات قاعدة البيانات" (Transactions)...

3 أبريل، 2026 قراءة المزيد
الشبكات والـ APIs

واجهاتي كانت تغرق في بيانات لا تحتاجها: كيف أنقذني GraphQL من جحيم الطلبات المتعددة والإفراط في جلب البيانات؟

أشارككم قصتي مع واجهات برمجة التطبيقات (APIs) وكيف عانيت من بطء الأداء بسبب طلبات REST المتعددة والبيانات الزائدة. سأشرح لكم كيف كانت تقنية GraphQL هي...

3 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

مستقبلي كان مرهونًا بمزود سحابي واحد: كيف أنقذتني ‘استراتيجية السحابة المتعددة’ من جحيم الاحتكار؟

أشارككم قصتي مع "الاحتكار السحابي" وكيف كاد أن يدمر مشروعي التقني. سأشرح لكم بالتفصيل، ومن خلال تجربتي العملية كـ"أبو عمر"، كيف كانت استراتيجية السحابة المتعددة...

3 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

مقابلاتي التقنية كانت اختبارات صامتة: كيف أنقذني ‘التفكير بصوت عالٍ’ من جحيم الرفض رغم معرفتي بالحل؟

أشاركك قصتي مع مقابلات العمل التقنية التي فشلت فيها رغم معرفتي بالحل الصحيح. اكتشف معي استراتيجية "التفكير بصوت عالٍ" التي حولت مساري المهني، وكيف يمكنك...

3 أبريل، 2026 قراءة المزيد
البودكاست