كنا ضائعين بين المونوليث والخدمات المصغرة: كيف أنقذنا ‘المونوليث النمطي’ (Modulith) من جحيم التعقيد؟

يا جماعة الخير، السلام عليكم ورحمة الله.

اسمحولي اليوم أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة فيها شوية وجع راس، وشوية حماس زيادة عن اللزوم، ودرس تعلمناه بالطريقة الصعبة. كنا وقتها شغالين على مشروع جديد، منصة تجارة إلكترونية طموحة أسميناها “دكانتي”. الفريق كان كله شباب وصبايا، شعلة نشاط، والكل متابع آخر صيحات التكنولوجيا.

في اجتماع البداية، واحد من الشباب المتحمسين قال: “يا أبو عمر، لازم نبني المشروع على معمارية الخدمات المصغرة (Microservices) من أول يوم! هيك الشركات الكبيرة بتشتغل، وهذا هو المستقبل!”. الحق يقال، الفكرة كانت براقة. تخيلنا حالنا بنبني خدمات مستقلة، كل فريق ماسك خدمة، وبنعمل deploy مية مرة باليوم بدون ما حدا يأثر على الثاني. الحلم كان كبير، والقهوة كانت طيبة، فقلنا “يلا، توكلنا على الله”.

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

هالجملة رنت في بالي. إحنا فعلاً كنا بنغرق. كنا بنطارد “الموضة” التقنية بدون ما نسأل حالنا: هل هذا هو الحل المناسب لمشكلتنا وحجمنا الحالي؟ هنا كانت نقطة التحول، لما قررنا نرجع خطوة للوراء ونبحث عن طريق ثالث، طريق يجمع أفضل ما في العالمين. وهذا الطريق كان اسمه “المونوليث النمطي” أو الـ Modulith.

ما هو المونوليث (Monolith)؟ ولماذا لم يعد “البعبع” الذي نخافه؟

قبل ما ندخل في تفاصيل المنقذ، خلينا نراجع الأساسيات. المونوليث، أو التطبيق المتراص، هو الأسلوب التقليدي في بناء البرمجيات. كل شي في مكان واحد: واجهة المستخدم، منطق العمل (business logic)، التعامل مع قاعدة البيانات… كله في كود واحد، يتم تجميعه ونشره كوحدة واحدة.

مزايا المونوليث

  • بساطة في البداية: سهل جداً تبدأ فيه. ما في تعقيدات شبكية ولا وجع راس الـ DevOps في المراحل الأولى.
  • سهولة التطوير: كل الكود في مكان واحد، التنقل بين أجزاء النظام سريع، والتعديلات مباشرة.
  • سهولة الاختبار: يمكنك تشغيل التطبيق بالكامل على جهازك واختبار كل شيء كوحدة واحدة.
  • عمليات نشر (Deployment) بسيطة: عندك ملف واحد (jar, war, exe…) بتنشره على السيرفر وخلصنا.

عيوب المونوليث (الـ “Big Ball of Mud”)

المشكلة تبدأ لما التطبيق يكبر. مع الوقت، بصير مثل “كومة القش” اللي ما إلها أول من آخر. الأجزاء المختلفة من الكود بتصير معتمدة على بعضها بشكل فوضوي، وتغيير بسيط في مكان ممكن يكسر عشر أماكن ثانية. هذا ما يسمى بالـ “Big Ball of Mud” أو “كرة الطين الكبيرة”.

  • صعوبة الصيانة: مع نمو الكود، يصبح فهمه وتعديله كابوساً.
  • صعوبة التوسع (Scaling): إذا كان عندك ضغط على جزء معين من التطبيق (مثلاً، تصفح المنتجات)، لازم تعمل scale للتطبيق كله، وهذا هدر للموارد.
  • الالتزام بتقنية واحدة: صعب جداً تدخل تقنية جديدة أو لغة برمجة مختلفة لجزء من النظام.

جاذبية الخدمات المصغرة (Microservices) القاتلة

وكرد فعل على مشاكل المونوليث، ظهرت معمارية الخدمات المصغرة. الفكرة هي تقسيم التطبيق الكبير إلى مجموعة من الخدمات الصغيرة المستقلة، كل خدمة مسؤولة عن جزء محدد من منطق العمل، ولها قاعدة بياناتها الخاصة، ويمكن تطويرها ونشرها بشكل مستقل.

الفكرة رائعة نظرياً، وهي الحل الأمثل لشركات بحجم Netflix أو Amazon. لكنها تأتي مع ثمن باهظ من التعقيد، وهذا اللي وقعنا فيه في مشروع “دكانتي”.

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

باختصار، قفزنا إلى حل لمشكلة لم تكن لدينا بعد. طبقنا حلاً مخصصاً للتعامل مع “الحجم الهائل” ونحن ما زلنا في حجم “الكوخ الصغير”.

المنقذ الذي لم نتوقعه: المونوليث النمطي (The Modulith)

هنا يأتي دور البطل الهادئ في قصتنا: المونوليث النمطي. الفكرة عبقرية في بساطتها: “ابنِ تطبيقك كوحدة واحدة (مونوليث)، لكن صممه داخلياً وكأنه مجموعة من الوحدات المستقلة (مثل الخدمات المصغرة).”

تخيلها معي كعمارة سكنية حديثة بدلاً من مجموعة فلل متفرقة.

  • الخدمات المصغرة (الفلل): كل فيلا مستقلة تماماً، لها سورها وبنيتها التحتية الخاصة. التواصل بين الفلل يتطلب الخروج للشارع (الشبكة)، وهذا بطيء ومُعرّض للمشاكل.
  • المونوليث النمطي (العمارة): هي مبنى واحد (نشر واحد). لكنها مقسمة إلى شقق واضحة المعالم (وحدات أو Modules). كل شقة (وحدة) لها بابها الخاص (واجهة برمجية API)، ومساحتها الداخلية الخاصة (تفاصيل التنفيذ). التواصل بين الشقق يتم عبر الممرات الداخلية (استدعاء دوال داخل الذاكرة In-Process)، وهذا سريع وموثوق.

في المونوليث النمطي، أنت تحصل على أفضل ما في العالمين: بساطة التطوير والنشر الخاصة بالمونوليث، مع التنظيم والحدود الواضحة الخاصة بالخدمات المصغرة.

كيف نبني مونوليث نمطي؟ (خطوات عملية وأمثلة)

طيب يا أبو عمر، حكي جميل، بس كيف نطبقه على أرض الواقع؟ بسيطة. الموضوع كله يتمحور حول فرض قيود وتنظيم داخل قاعدة الكود.

1. تعريف الحدود (Defining Boundaries)

أول وأهم خطوة هي تقسيم نظامك إلى وحدات منطقية واضحة. أفضل طريقة للقيام بذلك هي باستخدام مفاهيم من “التصميم الموجه بالمجال” (Domain-Driven Design – DDD)، وتحديداً مفهوم “السياقات المحدودة” (Bounded Contexts). كل سياق محدود يصبح “وحدة” (Module) في نظامك.

في مثال “دكانتي”، بدل ما تكون خدمات منفصلة، صارت وحدات داخل نفس التطبيق:

  • وحدة catalog (فهرس المنتجات)
  • وحدة ordering (الطلبات)
  • وحدة identity (إدارة المستخدمين)
  • وحدة payment (المدفوعات)

2. فرض العزل (Enforcing Encapsulation)

بعد تحديد الوحدات، يجب أن نفرض العزل بينها. القاعدة الذهبية هي: “الوحدات تتواصل مع بعضها فقط عبر واجهات برمجية عامة (Public APIs)، ويمنع منعاً باتاً الوصول إلى التفاصيل الداخلية لوحدة أخرى.”

هذا يعني أن وحدة ordering لا يمكنها مثلاً الوصول مباشرة إلى جداول قاعدة البيانات الخاصة بوحدة catalog. بل يجب أن تستدعي دالة عامة معرفة في الواجهة البرمجية لوحدة catalog.

كمثال، هيكل المشروع في لغة مثل Java باستخدام Maven/Gradle قد يبدو كالتالي:


dalkanti-app/
├── pom.xml (أو build.gradle)
└── src/
    └── main/
        └── java/
            └── com/dalkanti/
                ├── catalog/
                │   ├── api/      // الواجهات العامة والكائنات المشتركة (DTOs)
                │   │   └── CatalogService.java
                │   └── internal/ // التفاصيل الداخلية، مخفية عن باقي الوحدات
                │       └── DefaultCatalogService.java
                │       └── ProductRepository.java
                │
                ├── ordering/
                │   ├── api/
                │   │   └── OrderingService.java
                │   └── internal/
                │       └── DefaultOrderingService.java
                │       └── OrderRepository.java
                │       └── PlaceOrderUseCase.java
                │
                └── DalkantiApplication.java

نصيحة من خبرة أبو عمر: أهم إشي، خلي الـ internal زي أسرار البيت، ما بتطلع لبرا. أي تواصل بكون عبر الـ api الواضحة، زي الصالون اللي بتستقبل فيه الضيوف. يمكن استخدام أدوات مثل ArchUnit (في عالم Java) لفرض هذه القواعد برمجياً ومنع أي مطور من “الغش” والوصول للتفاصيل الداخلية.

3. التواصل بين الوحدات (Inter-Module Communication)

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

التواصل المتزامن (Synchronous)

يتم ببساطة عبر استدعاء الدوال (Method Calls) من خلال الواجهات البرمجية. وحدة ordering تحتاج لمعلومات عن منتج؟ ببساطة تحقن (inject) واجهة CatalogService وتستدعي الدالة المطلوبة.


// داخل وحدة الطلبات (ordering module)
@Service
public class DefaultOrderingService implements OrderingService {

    private final CatalogService catalogService; // حقن الواجهة من وحدة المنتجات

    // Constructor injection
    public DefaultOrderingService(CatalogService catalogService) {
        this.catalogService = catalogService;
    }

    public void createOrder(CreateOrderRequest request) {
        // استدعاء متزامن وسريع داخل الذاكرة
        ProductDto product = catalogService.findProductById(request.getProductId());
        if (product.isInStock()) {
            // ... أكمل عملية إنشاء الطلب
        }
    }
}

التواصل غير المتزامن (Asynchronous)

لتحقيق اقتران فضفاض (Loose Coupling) حقيقي، نستخدم نمط “الأحداث” (Events). عندما يحدث شيء مهم في وحدة ما (مثلاً، تم إنشاء طلب جديد)، تقوم هذه الوحدة بإصدار “حدث” (Event) دون أن تعرف من سيهتم به. الوحدات الأخرى يمكنها “الاستماع” لهذه الأحداث والتفاعل معها.

هذا يتم باستخدام “ناقل أحداث داخل الذاكرة” (In-Process Event Bus) توفره معظم أطر العمل الحديثة (مثل ApplicationEventPublisher في Spring).


// في وحدة الطلبات، بعد إنشاء الطلب بنجاح
public class DefaultOrderingService {
    private final ApplicationEventPublisher eventPublisher;
    // ...

    public Order placeOrder(OrderData data) {
        Order newOrder = //... حفظ الطلب في قاعدة البيانات
        
        // إصدار حدث للجميع
        eventPublisher.publishEvent(new OrderPlacedEvent(this, newOrder.getId()));
        
        return newOrder;
    }
}

// في وحدة أخرى تماماً، مثلاً وحدة الإشعارات (notifications module)
@Component
public class NotificationListener {

    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // تم استلام الحدث!
        // أرسل إيميل أو رسالة SMS للعميل
        System.out.println("Sending confirmation email for order: " + event.getOrderId());
    }
}

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

متى تختار المونوليث النمطي؟ ومتى تنتقل للخدمات المصغرة؟

المونوليث النمطي هو الخيار الأمثل في معظم الحالات، خصوصاً:

  • في بداية أي مشروع جديد (Startup).
  • للفرق الصغيرة والمتوسطة.
  • عندما لا يكون نطاق العمل (Domain) مفهوماً بالكامل بعد.
  • عندما تريد سرعة في التطوير مع الحفاظ على خيارات المستقبل مفتوحة.

والأجمل من هذا كله، أن المونوليث النمطي هو أفضل نقطة انطلاق نحو الخدمات المصغرة، إذا دعت الحاجة لذلك في المستقبل. عندما تكبر إحدى الوحدات (Modules) بشكل كبير، أو تحتاج إلى التوسع (scale) بشكل مستقل، يمكنك “استخلاصها” وتحويلها إلى خدمة مصغرة حقيقية. العملية ستكون أسهل بألف مرة لأن الحدود والواجهات البرمجية معرفة مسبقاً! كل ما عليك فعله هو تحويل استدعاءات الدوال إلى استدعاءات شبكية (REST/gRPC) وناقل الأحداث الداخلي إلى ناقل حقيقي (مثل RabbitMQ أو Kafka).

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

الخلاصة ونصيحة أبو عمر 💡

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

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

ابدأ بسيطاً، لكن ابدأ منظماً. اختاروا البساطة المنظمة على التعقيد المبهر. المونوليث النمطي هو خيار الحكماء اللي بفكروا لبعيد. ✅

أبو عمر

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

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

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

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

آخر المدونات

نصائح برمجية

كانت بياناتنا تتغير بغدر: كيف أنقذتنا ‘الكائنات غير القابلة للتغيير’ (Immutability) من جحيم الآثار الجانبية؟

أشارككم قصة حقيقية عن خطأ برمجي غامض كاد أن يصيب فريقنا بالجنون، وكيف كان الحل في مفهوم بسيط وقوي يسمى "اللامتغيرية" (Immutability). اكتشف كيف يمكن...

18 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

كانت إجابات نموذجنا من وحي الخيال: كيف أنقذنا البحث المعزز بالتوليد (RAG) من جحيم الهلوسة؟

أشارككم قصة حقيقية عن "هلوسة" نماذج الذكاء الاصطناعي وكيف تسببت في موقف محرج مع أحد العملاء. سنغوص في أعماق تقنية البحث المعزز بالتوليد (RAG)، ونشرحها...

18 مايو، 2026 قراءة المزيد
خوارزميات

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

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

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

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

أشارككم قصة حقيقية من قلب المعركة البرمجية، كيف انتقلنا من فوضى الواجهات والتصاميم المتضاربة إلى نظام متناغم وموحّد. هذه رحلتنا في بناء "نظام تصميم" (Design...

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

كانت تحديثات قاعدة البيانات كابوساً: كيف أنقذتنا أدوات الترحيل (Migrations) من جحيم التعديلات اليدوية؟

هل عانيت يوماً من تحديث مخطط قاعدة البيانات يدوياً بين فريقك؟ أبو عمر يشارككم قصة حقيقية حول كيف غيّرت أدوات الترحيل (Migrations) طريقة عمل فريقه،...

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

كانت خوادمنا تستجدي التحديثات: كيف أنقذتنا ‘خطاطيف الويب’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

تخيل خوادمك تلهث من كثرة الطلبات غير الضرورية. في هذه المقالة، أشارككم قصة حقيقية من الميدان حول كيفية انتقالنا من جحيم الاستقصاء المستمر (Polling) إلى...

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

كانت بنيتنا التحتية قصراً من رمال: كيف أنقذتنا “البنية التحتية ككود” (IaC) من جحيم البيئات المتضاربة؟

أشارككم قصة حقيقية عن ليلة كادت أن تنهار فيها شركتنا بسبب الفوضى في البنية التحتية، وكيف كانت "البنية التحتية ككود" (IaC) طوق النجاة الذي انتشلنا...

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

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

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

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