الأنماط المعمارية في Go: كيف لا تغرق في الغوروتينات

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا المعمارية ليست ترفًا بل ضرورة

\n\n

عندما تبدأ للتو في الكتابة بلغة Go، يبدو كل شيء بسيطًا: الغوروتينات تطير، القنوات تطن، والمُجمّع يسعد بالسرعة. لكن يمر شهر أو شهرين، ويتحول مشروعك الصغير إلى وحش يحتوي على package main بطول 3000 سطر ودعوات "ألا ينهار". هل هذا مألوف؟

\n\n

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

\n\n

في هذا المقال، سنشرح أربعة أنماط رئيسية من شأنها تبسيط حياتك حقًا: Repository وService Layer وWorker Pool وPipeline. سترى أمثلة على الكود، وستفهم متى تطبق كل نمط، وستتمكن من تطبيقها فورًا في مشاريعك. هيا بنا!

\n\n

1. نمط Repository: فصل منطق الأعمال عن قاعدة البيانات

\n\n

النمط الأول الذي يجب على كل مطور Go إتقانه هو Repository. جوهره بسيط: إنشاء طبقة وسيطة بين منطق الأعمال ومخزن البيانات (قاعدة بيانات، ملفات، واجهات برمجية خارجية).

\n\n

لماذا هذا ضروري؟

\n
    \n
  • قابلية الاختبار: يمكن بسهولة استبدال قاعدة البيانات الحقيقية بنموذج وهمي (mock).
  • \n
  • المرونة: الانتقال من PostgreSQL إلى MongoDB؟ نغير فقط الـ repository.
  • \n
  • نظافة الكود: الـ handler لا يعرف من أين تأتي البيانات - هذه مهمة الـ repository.
  • \n
\n\n

مثال على التنفيذ

\n

لنفترض أن لدينا نموذج مستخدم:

\n\n
type User struct {\n    ID    int64\n    Name  string\n    Email string\n}
\n\n

نحدد واجهة الـ repository:

\n\n
type UserRepository interface {\n    GetByID(ctx context.Context, id int64) (*User, error)\n    Create(ctx context.Context, user *User) error\n    Update(ctx context.Context, user *User) error\n    Delete(ctx context.Context, id int64) error\n}
\n\n

وتنفيذها لـ PostgreSQL:

\n\n
type PostgresUserRepo struct {\n    db *sql.DB\n}\n\nfunc (r *PostgresUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {\n    user := &User{}\n    err := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id).\n        Scan(&user.ID, &user.Name, &user.Email)\n    if err != nil {\n        return nil, fmt.Errorf("get user by id: %w", err)\n    }\n    return user, nil\n}
\n\n

الآن يعمل الـ handler أو الخدمة فقط مع واجهة UserRepository، وليس مع قاعدة بيانات محددة. هذا هو النمط المعماري Repository في Go عمليًا.

\n\n

2. Service Layer: منطق الأعمال تحت السيطرة

\n\n

إذا كان الـ Repository مسؤولاً عن البيانات، فإن Service Layer مسؤول عن قواعد العمل. يجمع هذا النمط العمليات المتفرقة في إجراءات ذات معنى.

\n\n

المشكلة بدون Service Layer

\n

غالبًا ما أرى كودًا حيث يقوم الـ handler بكل شيء: التحقق من صحة الطلب، التحقق من الصلاحيات، الكتابة في قاعدة البيانات، وتسجيل السجلات. هذا ينتهك مبدأ المسؤولية الواحدة (SRP).

\n\n

كيف نصلح هذا؟

\n

نقدم طبقة الخدمة:

\n\n
type UserService interface {\n    Register(ctx context.Context, name, email string) (*User, error)\n    GetProfile(ctx context.Context, userID int64) (*User, error)\n}\n\ntype userService struct {\n    repo UserRepository\n    mail MailSender\n}\n\nfunc (s *userService) Register(ctx context.Context, name, email string) (*User, error) {\n    // 1. التحقق من الصحة\n    if name == "" || email == "" {\n        return nil, ErrInvalidInput\n    }\n\n    // 2. منطق الأعمال: نتحقق مما إذا كان البريد الإلكتروني مشغولاً\n    existing, _ := s.repo.GetByEmail(ctx, email)\n    if existing != nil {\n        return nil, ErrEmailAlreadyExists\n    }\n\n    // 3. إنشاء المستخدم\n    user := &User{Name: name, Email: e                

المدونات

توصيات الكتب