مقدمة: لماذا المعمارية ليست ترفًا بل ضرورة
\n\nعندما تبدأ للتو في الكتابة بلغة Go، يبدو كل شيء بسيطًا: الغوروتينات تطير، القنوات تطن، والمُجمّع يسعد بالسرعة. لكن يمر شهر أو شهرين، ويتحول مشروعك الصغير إلى وحش يحتوي على package main بطول 3000 سطر ودعوات "ألا ينهار". هل هذا مألوف؟
توفر Go أدوات قوية، لكنها لا تفرض أطرًا صارمة. هذه ميزة وعيب في نفس الوقت. بدون معمارية واضحة، ستتشوش بسرعة في غوروتيناتك الخاصة. وهنا تأتي الأنماط المعمارية في Go لإنقاذك - حلول مثبتة للمشاكل التصميمية النمطية.
\n\nفي هذا المقال، سنشرح أربعة أنماط رئيسية من شأنها تبسيط حياتك حقًا: Repository وService Layer وWorker Pool وPipeline. سترى أمثلة على الكود، وستفهم متى تطبق كل نمط، وستتمكن من تطبيقها فورًا في مشاريعك. هيا بنا!
\n\n1. نمط Repository: فصل منطق الأعمال عن قاعدة البيانات
\n\nالنمط الأول الذي يجب على كل مطور Go إتقانه هو Repository. جوهره بسيط: إنشاء طبقة وسيطة بين منطق الأعمال ومخزن البيانات (قاعدة بيانات، ملفات، واجهات برمجية خارجية).
\n\nلماذا هذا ضروري؟
\n- \n
- قابلية الاختبار: يمكن بسهولة استبدال قاعدة البيانات الحقيقية بنموذج وهمي (mock). \n
- المرونة: الانتقال من PostgreSQL إلى MongoDB؟ نغير فقط الـ repository. \n
- نظافة الكود: الـ handler لا يعرف من أين تأتي البيانات - هذه مهمة الـ repository. \n
مثال على التنفيذ
\nلنفترض أن لدينا نموذج مستخدم:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n}\n\nنحدد واجهة الـ repository:
\n\ntype 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\ntype 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 عمليًا.
2. Service Layer: منطق الأعمال تحت السيطرة
\n\nإذا كان الـ Repository مسؤولاً عن البيانات، فإن Service Layer مسؤول عن قواعد العمل. يجمع هذا النمط العمليات المتفرقة في إجراءات ذات معنى.
\n\nالمشكلة بدون Service Layer
\nغالبًا ما أرى كودًا حيث يقوم الـ handler بكل شيء: التحقق من صحة الطلب، التحقق من الصلاحيات، الكتابة في قاعدة البيانات، وتسجيل السجلات. هذا ينتهك مبدأ المسؤولية الواحدة (SRP).
\n\nكيف نصلح هذا؟
\nنقدم طبقة الخدمة:
\n\ntype 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