المشروع الشامل (الجزء 2)

الدرس 30: المشروع الشامل (الجزء 2)

تشبيه من الواقع

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

بعد هذا الدرس، سيكون لديك مشروع Go قابل للنشر، قابل للاختبار، موثق بالكامل.


مراجعة المشروع

في الدرس السابق بنينا الهيكل الأساسي لـ TaskFlow. هذا الدرس يكمل صقله. إذا لم تقرأ الدرس 29: المشروع الشامل (الجزء 1)، يُوصى بإكمال النصف أولاً.

هيكل المشروع النهائي:

TEXT
taskflow/
├── cmd/
│   └── server/
│       └── main.go            # نقطة دخول البرنامج
├── internal/
│   ├── model/
│   │   ├── task.go            # نموذج البيانات
│   │   └── task_test.go       # اختبارات النموذج
│   ├── store/
│   │   ├── store.go           # واجهة التخزين
│   │   ├── memory.go          # تنفيذ الذاكرة (الدرس السابق)
│   │   └── sqlite.go          # تنفيذ SQLite (هذا الدرس)
│   ├── service/
│   │   ├── task.go            # منطق الأعمال
│   │   └── task_test.go       # اختبارات الوحدة
│   ├── handler/
│   │   ├── task.go            # معالج HTTP
│   │   └── task_test.go       # اختبارات المعالج
│   └── middleware/
│       ├── auth.go            # وسائط المصادقة
│       ├── logging.go         # وسائط التسجيل
│       └── cors.go            # وسائط CORS
├── docs/
│   └── api.md                 # توثيق API
├── Dockerfile                 # تغليف الحاوية
├── docker-compose.yml         # تكوين التنسيق
├── go.mod
├── go.sum
└── README.md

1. إكمال طبقة REST API

1.1 تصميم المسارات

مسارات API مصممة وفقًا لاتفاقيات RESTful:

الأسلوب المسار الوصف
GET /api/tasks الحصول على قائمة المهام
GET /api/tasks/{id} الحصول على مهمة واحدة
POST /api/tasks إنشاء مهمة
PUT /api/tasks/{id} تحديث مهمة
DELETE /api/tasks/{id} حذف مهمة
GET /health فحص الصحة

1.2 تنفيذ المعالج الكامل

GO
// internal/handler/task.go
package handler

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"

	"taskflow/internal/model"
	"taskflow/internal/service"
)

// TaskHandler معالج HTTP للمهام
type TaskHandler struct {
	svc *service.TaskService
}

// NewTaskHandler يُنشئ مثيل معالج
func NewTaskHandler(svc *service.TaskService) *TaskHandler {
	return &TaskHandler{svc: svc}
}

// ErrorResponse تنسيق استجابة خطأ موحد
type ErrorResponse struct {
	Error   string `json:"error"`
	Code    int    `json:"code"`
	Message string `json:"message,omitempty"`
}

// SuccessResponse تنسيق استجابة نجاح موحد
type SuccessResponse struct {
	Data    interface{} `json:"data"`
	Message string      `json:"message,omitempty"`
}

// writeJSON يكتب استجابة JSON
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(data)
}

// writeError يكتب استجابة خطأ
func writeError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, ErrorResponse{
		Error: msg,
		Code:  status,
	})
}

// extractID يستخرج معلمة المعرّف من مسار URL
func extractID(r *http.Request) (int, error) {
	// تنسيق المسار: /api/tasks/{id}
	parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(parts) < 3 {
		return 0, errors.New("معلمة المعرّف مفقودة")
	}
	return strconv.Atoi(parts[2])
}

// ListTasks يعالج GET /api/tasks
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
	// تحليل معلمات الاستعلام
	query := r.URL.Query()
	filter := service.TaskFilter{
		Status: model.TaskStatus(query.Get("status")),
	}

	// تحليل معلمات التصفح
	page, _ := strconv.Atoi(query.Get("page"))
	pageSize, _ := strconv.Atoi(query.Get("page_size"))
	if page < 1 {
		page = 1
	}
	if pageSize < 1 || pageSize > 100 {
		pageSize = 20
	}

	tasks, total, err := h.svc.ListTasks(r.Context(), filter, page, pageSize)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "فشل الاستعلام: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"data":      tasks,
		"total":     total,
		"page":      page,
		"page_size": pageSize,
	})
}

// GetTask يعالج GET /api/tasks/{id}
func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "معرّف مهمة غير صالح")
		return
	}

	task, err := h.svc.GetTask(r.Context(), id)
	if err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "المهمة غير موجودة")
			return
		}
		writeError(w, http.StatusInternalServerError, "فشل الاستعلام")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Data: task})
}

// CreateTask يعالج POST /api/tasks
func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
	var req model.CreateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "تنسيق الطلب غير صالح")
		return
	}

	task, err := h.svc.CreateTask(r.Context(), &req)
	if err != nil {
		if errors.Is(err, service.ErrValidation) {
			writeError(w, http.StatusBadRequest, err.Error())
			return
		}
		writeError(w, http.StatusInternalServerError, "فشل الإنشاء")
		return
	}

	writeJSON(w, http.StatusCreated, SuccessResponse{
		Data:    task,
		Message: "تم إنشاء المهمة بنجاح",
	})
}

// UpdateTask يعالج PUT /api/tasks/{id}
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "معرّف مهمة غير صالح")
		return
	}

	var req model.UpdateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "تنسيق الطلب غير صالح")
		return
	}

	task, err := h.svc.UpdateTask(r.Context(), id, &req)
	if err != nil {
		switch {
		case errors.Is(err, service.ErrTaskNotFound):
			writeError(w, http.StatusNotFound, "المهمة غير موجودة")
		case errors.Is(err, service.ErrValidation):
			writeError(w, http.StatusBadRequest, err.Error())
		default:
			writeError(w, http.StatusInternalServerError, "فشل التحديث")
		}
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{
		Data:    task,
		Message: "تم تحديث المهمة بنجاح",
	})
}

// DeleteTask يعالج DELETE /api/tasks/{id}
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "معرّف مهمة غير صالح")
		return
	}

	if err := h.svc.DeleteTask(r.Context(), id); err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "المهمة غير موجودة")
			return
		}
		writeError(w, http.StatusInternalServerError, "فشل الحذف")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Message: "تم حذف المهمة بنجاح"})
}

// HealthCheck نقطة نهاية فحص الصحة
func (h *TaskHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]string{
		"status":  "ok",
		"service": "taskflow",
	})
}

1.3 تسجيل المسارات وبدء تشغيل الخادم

GO
// cmd/server/main.go
package main

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"taskflow/internal/handler"
	"taskflow/internal/middleware"
	"taskflow/internal/service"
	"taskflow/internal/store"
)

func main() {
	// تهيئة التسجيل
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	// تهيئة طبقة التخزين (الافتراضي SQLite)
	dbPath := getEnv("DB_PATH", "taskflow.db")
	st, err := store.NewSQLiteStore(dbPath)
	if err != nil {
		slog.Error("فشل تهيئة قاعدة البيانات", "error", err)
		os.Exit(1)
	}
	defer st.Close()

	// تهيئة طبقة الأعمال
	svc := service.NewTaskService(st)

	// تهيئة المعالجات
	h := handler.NewTaskHandler(svc)

	// تسجيل المسارات
	mux := http.NewServeMux()

	// فحص الصحة
	mux.HandleFunc("GET /health", h.HealthCheck)

	// مسارات API
	mux.HandleFunc("GET /api/tasks", h.ListTasks)
	mux.HandleFunc("POST /api/tasks", h.CreateTask)
	mux.HandleFunc("GET /api/tasks/{id}", h.GetTask)
	mux.HandleFunc("PUT /api/tasks/{id}", h.UpdateTask)
	mux.HandleFunc("DELETE /api/tasks/{id}", h.DeleteTask)

	// تجميع سلسلة الوسائط: CORS → التسجيل → المصادقة → معالج الأعمال
	apiKey := getEnv("API_KEY", "dev-secret-key")
	handlerChain := middleware.Chain(
		mux,
		middleware.CORS(),
		middleware.Logging(),
		middleware.Auth(apiKey),
	)

	// إنشاء خادم HTTP
	addr := ":" + getEnv("PORT", "8080")
	srv := &http.Server{
		Addr:         addr,
		Handler:      handlerChain,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// بدء الخادم (غير حاجب)
	go func() {
		slog.Info("بدأت خدمة TaskFlow", "addr", addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("خرج الخادم بشكل غير طبيعي", "error", err)
			os.Exit(1)
		}
	}()

	// الإغلاق السلس: الاستماع لإشارات النظام
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	slog.Info("تم استقبال إشارة الإغلاق، جاري الإغلاق السلس...")

	// إعطاء الطلبات قيد التنفيذ 5 ثوانٍ للإكمال
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		slog.Error("خطأ في إغلاق الخادم", "error", err)
	}

	slog.Info("أُوقفت خدمة TaskFlow")
}

// getEnv يحصل على متغير بيئة مع دعم القيمة الافتراضية
func getEnv(key, defaultVal string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defaultVal
}
💡 تحسين مسارات Go 1.22+: بدءًا من Go 1.22، يدعم net/http مطابقة الأسلوب ("GET /path") ومعاملات المسار ({id})، مما يتيح لك بناء واجهات RESTful بدون مكتبات توجيه طرف ثالث.


2. دمج الوسائط

2.1 نمط سلسلة الوسائط

الوسائط هي "عوائق" في خط معالجة طلبات HTTP التي يمكنها تنفيذ منطق مشترك قبل وبعد وصول الطلبات إلى المعالج:

TEXT
الطلب → [CORS] → [التسجيل] → [المصادقة] → [المعالج] → الاستجابة
           ↓         ↓          ↓
       التحكم    تسجيل     التحقق
       عبر الأصل  المدة     من الهوية

2.2 البنية التحتية للوسائط

GO
// internal/middleware/middleware.go
package middleware

import "net/http"

// Middleware تعريف النوع
type Middleware func(http.Handler) http.Handler

// Chain تجمع عدة وسائط في سلسلة، الأول المُمرر = الأول المُنفّذ
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
	// التفاف من الخلف إلى الأمام، لضمان ترتيب التنفيذ من اليسار إلى اليمين
	for i := len(middlewares) - 1; i >= 0; i-- {
		h = middlewares[i](h)
	}
	return h
}

2.3 وسائط CORS

GO
// internal/middleware/cors.go
package middleware

import "net/http"

// CORS يتعامل مع موارد التشارك عبر الأصل
func CORS() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// تعيين رؤوس استجابة CORS
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID")
			w.Header().Set("Access-Control-Max-Age", "86400") // ذاكرة التخزين المؤقت للطلب المسبق 24 ساعة

			// الطلب المسبق يُرجع مباشرة
			if r.Method == http.MethodOptions {
				w.WriteHeader(http.StatusNoContent)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

2.4 وسائط التسجيل

GO
// internal/middleware/logging.go
package middleware

import (
	"log/slog"
	"net/http"
	"time"
)

// responseWriter يلتف ResponseWriter لالتقاط رمز الحالة
type responseWriter struct {
	http.ResponseWriter
	statusCode int
	written    int64
}

func newResponseWriter(w http.ResponseWriter) *responseWriter {
	return &responseWriter{
		ResponseWriter: w,
		statusCode:     http.StatusOK,
	}
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	n, err := rw.ResponseWriter.Write(b)
	rw.written += int64(n)
	return n, err
}

// Logging يسجل سجلات المعالجة لكل طلب
func Logging() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()

			// التفاف ResponseWriter لالتقاط رمز الحالة
			rw := newResponseWriter(w)

			// استدعاء المعالج التالي
			next.ServeHTTP(rw, r)

			// تسجيل سجل الطلب
			duration := time.Since(start)
			slog.Info("طلب HTTP",
				"method", r.Method,
				"path", r.URL.Path,
				"status", rw.statusCode,
				"duration_ms", duration.Milliseconds(),
				"remote_addr", r.RemoteAddr,
				"user_agent", r.UserAgent(),
			)
		})
	}
}

2.5 وسائط المصادقة

GO
// internal/middleware/auth.go
package middleware

import (
	"net/http"
	"strings"
)

// Auth يتحقق من صحة مفتاح API
func Auth(apiKey string) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// نقطة نهاية فحص الصحة لا تحتاج مصادقة
			if r.URL.Path == "/health" {
				next.ServeHTTP(w, r)
				return
			}

			// استخراج الرمز من رأس الطلب
			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				w.Header().Set("WWW-Authenticate", `Bearer realm="taskflow"`)
				http.Error(w, `{"error":"مصادقة مفقودة","code":401}`, http.StatusUnauthorized)
				return
			}

			// التحقق من تنسيق Bearer
			parts := strings.SplitN(authHeader, " ", 2)
			if len(parts) != 2 || parts[0] != "Bearer" {
				http.Error(w, `{"error":"تنسيق مصادقة غير صالح، يجب أن يكون Bearer <token>","code":401}`, http.StatusUnauthorized)
				return
			}

			// التحقق من قيمة الرمز
			if parts[1] != apiKey {
				http.Error(w, `{"error":"فشلت المصادقة: مفتاح API غير صالح","code":403}`, http.StatusForbidden)
				return
			}

			// المصادقة نجحت، تابع المعالجة
			next.ServeHTTP(w, r)
		})
	}
}

3. تكامل قاعدة البيانات (SQLite)

3.1 لماذا SQLite؟

الميزة SQLite MySQL/PostgreSQL
التثبيت بدون تكوين، ملف واحد يتطلب خدمة منفصلة
الاستخدام مشاريع صغيرة-متوسطة، مدمجة بيئات إنتاج كبيرة
التزامن قراءة متزامنة، كتابة متسلسلة تزامن عالي
الترحيل يتطلب تنفيذًا ذاتيًا أدوات غنية
💡 للمشاريع التعليمية والتطبيقات الصغيرة-المتوسطة، SQLite هو أفضل خيار للبدء. عندما يزداد حركة المرور، يمكنك التبديل إلى PostgreSQL بسلاسة.

3.2 تنفيذ تخزين SQLite

GO
// internal/store/sqlite.go
package store

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/mattn/go-sqlite3" // تعريف تشغيل SQLite، الاستيراد المجهول يُفعّل التهيئة

	"taskflow/internal/model"
)

// SQLiteStore تنفيذ تخزين SQLite
type SQLiteStore struct {
	db *sql.DB
}

// NewSQLiteStore يُنشئ ويُهيئ تخزين SQLite
func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
	if err != nil {
		return nil, fmt.Errorf("فشل فتح قاعدة البيانات: %w", err)
	}

	// التحقق من الاتصال
	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("فشل الاتصال بقاعدة البيانات: %w", err)
	}

	// تكوين بركة الاتصالات
	db.SetMaxOpenConns(1) // SQLite كتابة واحدة، تحديد الكتابات المتزامنة
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(0) // الاتصالات لا تنتهي صلاحيتها

	// تنفيذ ترحيل الجدول
	if err := migrate(db); err != nil {
		return nil, fmt.Errorf("فشل ترحيل قاعدة البيانات: %w", err)
	}

	return &SQLiteStore{db: db}, nil
}

// migrate ينفذ ترحيل قاعدة البيانات
func migrate(db *sql.DB) error {
	query := `
	CREATE TABLE IF NOT EXISTS tasks (
		id          INTEGER PRIMARY KEY AUTOINCREMENT,
		title       TEXT    NOT NULL,
		description TEXT    NOT NULL DEFAULT '',
		status      TEXT    NOT NULL DEFAULT 'pending',
		priority    TEXT    NOT NULL DEFAULT 'medium',
		due_date    DATETIME,
		created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
		updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
	);

	CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
	CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
	CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date);
	`
	_, err := db.Exec(query)
	return err
}

// Close يُغلق اتصال قاعدة البيانات
func (s *SQLiteStore) Close() error {
	return s.db.Close()
}

// Create يُنشئ مهمة
func (s *SQLiteStore) Create(ctx context.Context, task *model.Task) error {
	query := `
		INSERT INTO tasks (title, description, status, priority, due_date, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`
	now := time.Now()
	result, err := s.db.ExecContext(ctx, query,
		task.Title, task.Description, task.Status, task.Priority,
		task.DueDate, now, now,
	)
	if err != nil {
		return fmt.Errorf("فشل إدراج المهمة: %w", err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return fmt.Errorf("فشل الحصول على معرّف الإدراج: %w", err)
	}

	task.ID = int(id)
	task.CreatedAt = now
	task.UpdatedAt = now
	return nil
}

// GetByID يُستعلم عن مهمة بالمعرّف
func (s *SQLiteStore) GetByID(ctx context.Context, id int) (*model.Task, error) {
	query := `
		SELECT id, title, description, status, priority, due_date, created_at, updated_at
		FROM tasks WHERE id = ?
	`
	var task model.Task
	var dueDate sql.NullTime

	err := s.db.QueryRowContext(ctx, query, id).Scan(
		&task.ID, &task.Title, &task.Description,
		&task.Status, &task.Priority, &dueDate,
		&task.CreatedAt, &task.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil // غير موجود يُرجع nil
	}
	if err != nil {
		return nil, fmt.Errorf("فشل استعلام المهمة: %w", err)
	}

	if dueDate.Valid {
		task.DueDate = &dueDate.Time
	}
	return &task, nil
}

// List يُستعلم عن قائمة المهام (يدعم التصفية والتصفح)
func (s *SQLiteStore) List(ctx context.Context, filter TaskFilter, page, pageSize int) ([]model.Task, int, error) {
	// بناء شروط الاستعلام
	where := "1=1"
	args := []interface{}{}

	if filter.Status != "" {
		where += " AND status = ?"
		args = append(args, filter.Status)
	}
	if filter.Priority != "" {
		where += " AND priority = ?"
		args = append(args, filter.Priority)
	}

	// استعلام العدد الكلي
	countQuery := fmt.Sprintf("SELECT COUNT(*) FROM tasks WHERE %s", where)
	var total int
	if err := s.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil {
		return nil, 0, fmt.Errorf("فشل استعلام المجموع: %w", err)
	}

	// استعلام مصفح
	offset := (page - 1) * pageSize
	query := fmt.Sprintf(`
		SELECT id, title, description, status, priority, due_date, created_at, updated_at
		FROM tasks WHERE %s
		ORDER BY created_at DESC
		LIMIT ? OFFSET ?
	`, where)
	args = append(args, pageSize, offset)

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, 0, fmt.Errorf("فشل استعلام القائمة: %w", err)
	}
	defer rows.Close()

	var tasks []model.Task
	for rows.Next() {
		var task model.Task
		var dueDate sql.NullTime
		if err := rows.Scan(
			&task.ID, &task.Title, &task.Description,
			&task.Status, &task.Priority, &dueDate,
			&task.CreatedAt, &task.UpdatedAt,
		); err != nil {
			return nil, 0, fmt.Errorf("فشل مسح الصف: %w", err)
		}
		if dueDate.Valid {
			task.DueDate = &dueDate.Time
		}
		tasks = append(tasks, task)
	}

	return tasks, total, nil
}

// Update يُحدّث مهمة
func (s *SQLiteStore) Update(ctx context.Context, task *model.Task) error {
	query := `
		UPDATE tasks
		SET title = ?, description = ?, status = ?, priority = ?, due_date = ?, updated_at = ?
		WHERE id = ?
	`
	result, err := s.db.ExecContext(ctx, query,
		task.Title, task.Description, task.Status, task.Priority,
		task.DueDate, time.Now(), task.ID,
	)
	if err != nil {
		return fmt.Errorf("فشل تحديث المهمة: %w", err)
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("المهمة غير موجودة")
	}
	return nil
}

// Delete يحذف مهمة
func (s *SQLiteStore) Delete(ctx context.Context, id int) error {
	query := "DELETE FROM tasks WHERE id = ?"
	result, err := s.db.ExecContext(ctx, query, id)
	if err != nil {
		return fmt.Errorf("فشل حذف المهمة: %w", err)
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("المهمة غير موجودة")
	}
	return nil
}

// TaskFilter شروط تصفية المهام
type TaskFilter struct {
	Status   model.TaskStatus
	Priority model.TaskPriority
}

3.3 واجهة التخزين الموحدة

GO
// internal/store/store.go
package store

import (
	"context"

	"taskflow/internal/model"
)

// TaskStore يحدد واجهة تخزين المهام
// جميع تنفيذات التخزين (الذاكرة، SQLite، PostgreSQL) يجب أن تنفذ هذه الواجهة
type TaskStore interface {
	Create(ctx context.Context, task *model.Task) error
	GetByID(ctx context.Context, id int) (*model.Task, error)
	List(ctx context.Context, filter TaskFilter, page, pageSize int) ([]model.Task, int, error)
	Update(ctx context.Context, task *model.Task) error
	Delete(ctx context.Context, id int) error
	Close() error
}
💡 حقن التبعيات: تعتمد طبقة الأعمال فقط على واجهة TaskStore، دون الاهتمام بما إذا كان التخزين الأساسي SQLite أم ذاكرة. احقن تقليد للاختبار، وتنفيذ حقيقي للإنتاج.


4. اختبار التكامل

4.1 اختبارات تكامل المعالج

تتحقق اختبارات التكامل مما إذا كانت عدة مكونات تعمل معًا بشكل صحيح، باستخدام httptest لمحاكاة طلبات HTTP حقيقية:

GO
// internal/handler/task_test.go
package handler_test

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"taskflow/internal/handler"
	"taskflow/internal/middleware"
	"taskflow/internal/model"
	"taskflow/internal/service"
	"taskflow/internal/store"
)

// setupTestServer يُنشئ خادم اختبار
func setupTestServer(t *testing.T) *httptest.Server {
	t.Helper()

	// استخدام التخزين في الذاكرة
	memStore := store.NewMemoryStore()
	svc := service.NewTaskService(memStore)
	h := handler.NewTaskHandler(svc)

	// تسجيل المسارات
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", h.HealthCheck)
	mux.HandleFunc("GET /api/tasks", h.ListTasks)
	mux.HandleFunc("POST /api/tasks", h.CreateTask)
	mux.HandleFunc("GET /api/tasks/{id}", h.GetTask)
	mux.HandleFunc("PUT /api/tasks/{id}", h.UpdateTask)
	mux.HandleFunc("DELETE /api/tasks/{id}", h.DeleteTask)

	// إضافة الوسائط
	apiKey := "test-key"
	handlerChain := middleware.Chain(
		mux,
		middleware.CORS(),
		middleware.Logging(),
		middleware.Auth(apiKey),
	)

	return httptest.NewServer(handlerChain)
}

// TestIntegration_CRUD اختبار تكامل تدفق CRUD كامل
func TestIntegration_CRUD(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()
	authHeader := "Bearer test-key"

	// 1. فحص الصحة
	t.Run("فحص الصحة", func(t *testing.T) {
		resp, err := client.Get(srv.URL + "/health")
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("رمز الحالة = %d، المتوقع 200", resp.StatusCode)
		}
	})

	// 2. إنشاء مهمة
	var createdTask model.Task
	t.Run("إنشاء مهمة", func(t *testing.T) {
		body := `{"title":"مهمة اختبار التكامل","description":"اختبار تدفق CRUD","priority":"high"}`
		req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/tasks", bytes.NewBufferString(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusCreated {
			t.Errorf("رمز الحالة = %d، المتوقع 201", resp.StatusCode)
		}

		var result struct {
			Data    model.Task `json:"data"`
			Message string     `json:"message"`
		}
		json.NewDecoder(resp.Body).Decode(&result)
		createdTask = result.Data

		if createdTask.Title != "مهمة اختبار التكامل" {
			t.Errorf("العنوان = %q، المتوقع %q", createdTask.Title, "مهمة اختبار التكامل")
		}
	})

	// 3. استعلام مهمة واحدة
	t.Run("استعلام مهمة", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("رمز الحالة = %d، المتوقع 200", resp.StatusCode)
		}
	})

	// 4. تحديث مهمة
	t.Run("تحديث مهمة", func(t *testing.T) {
		body := `{"title":"مهمة محدثة","status":"completed"}`
		req, _ := http.NewRequest(http.MethodPut, srv.URL+"/api/tasks/1", bytes.NewBufferString(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("رمز الحالة = %d، المتوقع 200", resp.StatusCode)
		}
	})

	// 5. استعلام القائمة
	t.Run("استعلام القائمة", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks?page=1&page_size=10", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("رمز الحالة = %d، المتوقع 200", resp.StatusCode)
		}

		var result struct {
			Data  []model.Task `json:"data"`
			Total int          `json:"total"`
		}
		json.NewDecoder(resp.Body).Decode(&result)

		if result.Total != 1 {
			t.Errorf("المجموع = %d، المتوقع 1", result.Total)
		}
	})

	// 6. حذف مهمة
	t.Run("حذف مهمة", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Errorf("رمز الحالة = %d، المتوقع 200", resp.StatusCode)
		}
	})

	// 7. التحقق من أن الاستعلام بعد الحذف يُرجع 404
	t.Run("الاستعلام بعد الحذف يجب أن يُرجع 404", func(t *testing.T) {
		req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks/1", nil)
		req.Header.Set("Authorization", authHeader)

		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("فشل الطلب: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusNotFound {
			t.Errorf("رمز الحالة = %d، المتوقع 404", resp.StatusCode)
		}
	})
}

// TestIntegration_Auth اختبار تكامل وسائط المصادقة
func TestIntegration_Auth(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()

	tests := []struct {
		name       string
		authHeader string
		wantCode   int
	}{
		{"بدون رأس مصادقة", "", http.StatusUnauthorized},
		{"تنسيق خاطئ", "Basic abc123", http.StatusUnauthorized},
		{"مفتاح خاطئ", "Bearer wrong-key", http.StatusForbidden},
		{"مصادقة صحيحة", "Bearer test-key", http.StatusOK},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, _ := http.NewRequest(http.MethodGet, srv.URL+"/api/tasks", nil)
			if tt.authHeader != "" {
				req.Header.Set("Authorization", tt.authHeader)
			}

			resp, err := client.Do(req)
			if err != nil {
				t.Fatalf("فشل الطلب: %v", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tt.wantCode {
				t.Errorf("رمز الحالة = %d، المتوقع %d", resp.StatusCode, tt.wantCode)
			}
		})
	}
}

// TestIntegration_CORS اختبار عبر الأصل
func TestIntegration_CORS(t *testing.T) {
	srv := setupTestServer(t)
	defer srv.Close()

	client := srv.Client()

	// طلب OPTIONS مسبق
	req, _ := http.NewRequest(http.MethodOptions, srv.URL+"/api/tasks", nil)
	req.Header.Set("Origin", "http://example.com")
	req.Header.Set("Access-Control-Request-Method", "POST")

	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("فشل الطلب: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("رمز الحالة = %d، المتوقع 204", resp.StatusCode)
	}

	if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
		t.Error("رأس استجابة CORS مفقود")
	}
}

4.2 تشغيل اختبارات التكامل

BASH
# تشغيل جميع الاختبارات
go test ./... -v

# تشغيل اختبارات التكامل فقط
go test -run TestIntegration -v

# عرض التغיפוי
go test ./... -cover -coverprofile=coverage.out

# إنشاء تقرير تغיפוי HTML
go tool cover -html=coverage.out -o coverage.html

# كشف الأحداث المتسابقة
go test -race ./...

5. توثيق API (نظرة عامة على Swagger/OpenAPI)

5.1 نظرة عامة على مواصفات OpenAPI

OpenAPI (سابقًا Swagger) هي المواصفات القياسية لوصف واجهات RESTful، تحدد الواجهات بتنسيق YAML/JSON:

YAML
# docs/api.yaml (نسخة مبسطة)
openapi: "3.0.3"
info:
  title: TaskFlow API
  description: واجهة برمجة RESTful لنظام إدارة المهام
  version: "1.0.0"
  contact:
    name: فريق التطوير
    email: dev@taskflow.example.com

servers:
  - url: http://localhost:8080
    description: بيئة التطوير المحلية

paths:
  /api/tasks:
    get:
      summary: الحصول على قائمة المهام
      operationId: listTasks
      tags: [إدارة المهام]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, in_progress, completed]
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: إرجاع قائمة المهام بنجاح
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
                  total:
                    type: integer
                  page:
                    type: integer

    post:
      summary: إنشاء مهمة جديدة
      operationId: createTask
      tags: [إدارة المهام]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTaskRequest"
      responses:
        "201":
          description: تم إنشاء المهمة بنجاح
        "400":
          description: معلمات الطلب غير صالحة

  /api/tasks/{id}:
    get:
      summary: الحصول على مهمة واحدة
      operationId: getTask
      tags: [إدارة المهام]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: نجاح
        "404":
          description: المهمة غير موجودة

    put:
      summary: تحديث مهمة
      operationId: updateTask
      tags: [إدارة المهام]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: تم التحديث بنجاح
        "404":
          description: المهمة غير موجودة

    delete:
      summary: حذف مهمة
      operationId: deleteTask
      tags: [إدارة المهام]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: تم الحذف بنجاح
        "404":
          description: المهمة غير موجودة

components:
  schemas:
    Task:
      type: object
      properties:
        id:
          type: integer
          example: 1
        title:
          type: string
          example: "تعلم Go"
        description:
          type: string
          example: "إكمال الدرس 30 المشروع الشامل"
        status:
          type: string
          enum: [pending, in_progress, completed]
        priority:
          type: string
          enum: [low, medium, high, urgent]
        due_date:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateTaskRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
        priority:
          type: string
          enum: [low, medium, high, urgent]
          default: medium
        due_date:
          type: string
          format: date-time
          nullable: true

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

security:
  - BearerAuth: []

5.2 إنشاء التوثيق تلقائيًا باستخدام Swaggo

لمشاريع Go، يُوصى باستخدام swaggo/swag لإنشاء توثيق Swagger تلقائيًا من تعليقات الكود:

GO
// إضافة تعليقات فوق دوال المعالج

// ListTasks godoc
// @Summary      الحصول على قائمة المهام
// @Description  يدعم التصفية حسب الحالة والتصفح
// @Tags         إدارة المهام
// @Accept       json
// @Produce      json
// @Param        status   query    string  false  "حالة المهمة"
// @Param        page     query    int     false  "رقم الصفحة"     default(1)
// @Param        page_size query   int     false  "العناصر لكل صفحة"  default(20)
// @Success      200  {object}  map[string]interface{}
// @Failure      500  {object}  ErrorResponse
// @Security     BearerAuth
// @Router       /api/tasks [get]
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
    // ...
}

أمر الإنشاء:

BASH
# تثبيت أداة swag
go install github.com/swaggo/swag/cmd/swag@latest

# إنشاء التوثيق في دليل جذر المشروع
swag init -g cmd/server/main.go

# الملفات المُنشأة في دليل docs/
# docs/swagger.json, docs/swagger.yaml, docs/docs.go

6. إرشادات كتابة README

يجب أن يتضمن README الممتاز الأقسام التالية:

MARKDOWN
# TaskFlow - نظام إدارة المهام

> خدمة RESTful API لإدارة المهام خفيفة الوزن مبنية بـ Go

## الميزات

- عمليات CRUD كاملة للمهام
- تصفية حسب الحالة والأولوية
- دعم التصفح
- مصادقة مفتاح API
- دعم CORS عبر الأصل
- تسجيل منظم
- تخزين SQLite مستمر
- نشر Docker بنقرة واحدة

## البدء السريع

### المتطلبات الأساسية

- Go 1.22+
- SQLite3 (بيئة التطوير)

### التثبيت والتشغيل

```bash
# استنساخ المشروع
git clone https://github.com/yourname/taskflow.git
cd taskflow

# تثبيت التبعيات
go mod tidy

# تشغيل الخدمة
go run cmd/server/main.go

# الخدمة تستمع على http://localhost:8080 افتراضيًا

متغيرات البيئة

المتغير الافتراضي الوصف
PORT 8080 منفذ الخدمة
DB_PATH taskflow.db مسار ملف قاعدة بيانات SQLite
API_KEY dev-secret-key مفتاح مصادقة API

أمثلة استخدام API

BASH
# إنشاء مهمة
curl -X POST http://localhost:8080/api/tasks \
  -H "Authorization: Bearer dev-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"title":"تعلم Go","priority":"high"}'

# استعلام القائمة
curl http://localhost:8080/api/tasks \
  -H "Authorization: Bearer dev-secret-key"

# فحص الصحة (لا يحتاج مصادقة)
curl http://localhost:8080/health

هيكل المشروع

TEXT
taskflow/
├── cmd/server/         # نقطة دخول البرنامج
├── internal/
│   ├── model/          # نماذج البيانات
│   ├── store/          # طبقة تخزين البيانات
│   ├── service/        # طبقة منطق الأعمال
│   ├── handler/        # طبقة معالج HTTP
│   └── middleware/     # الوسائط
├── docs/               # توثيق API
├── Dockerfile
└── README.md

الاختبار

BASH
# تشغيل جميع الاختبارات
go test ./... -v

# عرض التغיפוי
go test ./... -cover

# كشف الأحداث المتسابقة
go test -race ./...

النشر

نشر Docker

BASH
# بناء الصورة
docker build -t taskflow:latest .

# تشغيل الحاوية
docker run -d \
  -p 8080:8080 \
  -e API_KEY=your-production-key \
  -e DB_PATH=/data/taskflow.db \
  -v taskflow-data:/data \
  --name taskflow \
  taskflow:latest

الترخيص

MIT License


> 💡 مبدأ كتابة README: اجعل الآخرين يعملون في 30 ثانية أولاً، ثموسّع التفاصيل تدريجيًا.

---

## 7. تعبئة Dockerfile والإصدار

### 7.1 بناء متعدد المراحل

```dockerfile
# Dockerfile

# ========== المرحلة 1: البناء ==========
FROM golang:1.22-alpine AS builder

# تثبيت تبعيات تجميع SQLite
RUN apk add --no-cache gcc musl-dev

WORKDIR /app

# نسخ ملفات التبعية أولاً، الاستفادة من طبقات ذاكرة التخزين المؤقت Docker
COPY go.mod go.sum ./
RUN go mod download

# نسخ المصدر والتجميع
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build \
    -ldflags="-s -w" \
    -o /app/taskflow \
    ./cmd/server/main.go

# ========== المرحلة 2: التشغيل ==========
FROM alpine:3.19

# تثبيت تبعيات التشغيل
RUN apk add --no-cache ca-certificates tzdata

# إنشاء مستخدم غير جذري
RUN adduser -D -g '' appuser

WORKDIR /app

# نسخ الملف الثنائي من مرحلة البناء
COPY --from=builder /app/taskflow .

# التبديل إلى مستخدم غير جذري
USER appuser

# المنفذ المكشوف
EXPOSE 8080

# فحص الصحة
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

# أمر بدء التشغيل
ENTRYPOINT ["./taskflow"]

7.2 تكوين Docker Compose

YAML
# docker-compose.yml
version: "3.8"

services:
  taskflow:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DB_PATH=/data/taskflow.db
      - API_KEY=${API_KEY:-change-me-in-production}
      - TZ=Asia/Shanghai
    volumes:
      - taskflow-data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3

volumes:
  taskflow-data:

7.3 البناء والتشغيل

BASH
# بناء صورة Docker
docker build -t taskflow:latest .

# عرض حجم الصورة
docker images taskflow

# البدء باستخدام Docker Compose
export API_KEY=your-secret-key
docker compose up -d

# عرض السجلات
docker compose logs -f

# إيقاف الخدمة
docker compose down

# الإيقاف وحذف حجم البيانات
docker compose down -v

7.4 الترجمة المتقاطعة (بدون بيئة Docker)

BASH
# تجميع ملف ثنائي لـ Linux (على Windows/macOS)
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
    CC=x86_64-linux-musl-gcc \
    go build -ldflags="-s -w" -o taskflow-linux ./cmd/server/main.go

# تجميع نسخة بدون CGO (لا يدعم SQLite، يحتاج إلى تعريف قاعدة بيانات Go خالص)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w" -o taskflow-linux ./cmd/server/main.go

8. دليل النشر

8.1 قائمة التحقق من النشر

TEXT
✅ فحوصات الأمان
   □ API_KEY في الإنتاج تغير إلى كلمة مرور قوية
   □ تكوين CORS يقيد نطاقات الأصل المسموح بها
   □ السجلات لا تحتوي على معلومات حساسة
   □ استخدام HTTPS (تكوين وكيل عكسي)

✅ فحوصات الأداء
   □ تكوين بركة اتصال قاعدة البيانات
   □ مهلات HTTP معينة (قراءة/كتابة/خمول)
   □ حجم جسم الطلب محدود
   □ الاستعلامات المصفحة لها حدود أقصى

✅ فحوصات الموثوقية
   □ نقطة نهاية فحص الصحة متاحة
   □ منطق الإغلاق السلس مُنفّذ
   □ تنسيق السجل JSON منظم
   □ فحص صحة Docker مُكوّن

8.2 استخدام Nginx كوكيل عكسي

NGINX
# /etc/nginx/conf.d/taskflow.conf
server {
    listen 80;
    server_name api.taskflow.example.com;

    # إجبار HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.taskflow.example.com;

    ssl_certificate     /etc/ssl/certs/taskflow.crt;
    ssl_certificate_key /etc/ssl/private/taskflow.key;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # إعدادات المهلة
        proxy_connect_timeout 10s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
    }
}

❓ أسئلة شائعة

س1: SQLite يُبلغ "database is locked" في Docker؟

ج: SQLite لديه قيود على الكتابات المتزامنة. تأكد من:

GO
// 1. تعيين بركة الاتصال إلى 1 (اتصال واحد)
db.SetMaxOpenConns(1)

// 2. تفعيل وضع WAL (يسمح بتزامن القراءة/الكتابة)
db.Exec("PRAGMA journal_mode=WAL")

// 3. تعيين مهلة انتظار مزدحمة
// أضف ?_busy_timeout=5000 إلى سلسلة الاتصال

إذا كان التزامن عاليًا، فكّر في التبديل إلى PostgreSQL.

س2: هل ترتيب تنفيذ الوسائط مهم؟

ج: نعم. الترتيب الموصى به: CORS → التسجيل → المصادقة → معالج الأعمال. الأسباب:

GO
// في Chain، الوسائط المُمررة أولاً تُنفّذ أولاً
handler := middleware.Chain(mux,
    middleware.CORS(),    // 1. تُنفّذ أولاً
    middleware.Logging(), // 2. ثانياً
    middleware.Auth(key), // 3. تُنفّذ أخيرًا
)

س3: كيف ندير مفاتيح API في الإنتاج؟

ج: لا تُشفر المفاتيح أبدًا أو تُدرجها في مستودع الكود. الأساليب الموصى بها:

BASH
# الطريقة 1: متغيرات البيئة
export API_KEY=$(openssl rand -hex 32)

# الطريقة 2: ملف .env (أضفه إلى .gitignore)
echo "API_KEY=$(openssl rand -hex 32)" > .env

# الطريقة 3: Docker Secrets (وضع Swarm)
echo "my-secret-key" | docker secret create api_key -

س4: هل يجب استخدام SQLite أم PostgreSQL لمشروعي؟

ج: اختر بناءً على السيناريو:

السيناريو الموصى به السبب
التعلم/النمذجة الأولية SQLite بدون تكوين، ملف واحد
أدوات داخلية/حركة مرور منخفضة SQLite كافٍ وسهل الصيانة
عدة خدمات تشارك البيانات PostgreSQL وصول عبر الشبكة، إدارة مركزية
كتابات متزامنة عالية PostgreSQL كتابات SQLite متسلسلة
تحتاج استعلامات JSON PostgreSQL دعم JSONB قوي

📖 ملخص الدورة

تهانينا على إكمال جميع دروس tutorial Go الـ 30! دعنا نراجع نظام المعرفة بأكمله:

المرحلة 1 — أساسيات Go (الدروس 1-6)

الدرس النقاط الأساسية
مقدمة Go موضع Go: لغة مستوى النظام لعصر السحابة الأصلية
المتغيرات والأنواع متغيرات := المختصرة، آلية القيمة الصفرية، تعدادات iota
تدفق التحكم for هو الحلقة الوحيدة، defer التنفيذ المؤجل، switch بدون fallthrough
الدوال قيم الإرجاع المتعددة، الإغلاقات، دالة init، الدوال كمواطنين من الدرجة الأولى
المصفوفات والمراجع المراجع هي أنواع مرجعية، آلية توسيع append، المبادئ الأساسية
Map نمط comma ok، الطبيعة غير المرتبة، معايير الاختيار مع المراجعة

المرحلة 2 — الهياكل والواجهات (الدروس 7-12)

الدرس النقاط الأساسية
الهياكل أنواع القيم مقابل المؤشرات، علامات الهياكل، الحقول المجهولة
الطرق مستقبلات القيم مقابل مستقبلات المؤشرات، التركيب بدل الوراثة
الواجهات التنفيذ الضمني (نمط البط)، تأكيدات النوع، تركيب الواجهات
معالجة الأخطاء واجهة error، errors.Is/As، أخطاء مخصصة، فلسفة معالجة الأخطاء الصريحة
الحزم والوحدات go mod، قواعد التصدير، حزم internal
التطبيق استخدام شامل للهياكل + الواجهات + معالجة الأخطاء

المرحلة 3 — البرمجة المتزامنة (الدروس 13-18)

الدرس النقاط الأساسية
Goroutine مساعدات خفيفة الوزن، sync.WaitGroup، منع التسريب
Channel بدون مخزن مؤقت مقابل مع مخزن مؤقت، close/range، قيود الاتجاه
Select تعدد الإرسال، التحكم بالمهلة، أنماط الدمج/التفرع
حزمة sync Mutex/RWMutex، Once، sync.Map، كشف الأحداث المتسابقة
مُجدول المهام context.Context، التعاون المتزامن، إعادة المحاولة مع المهلة
زاحف الويب تحديد المعدل، إزالة التكرار، إعادة المحاولة عند الخطأ، الخروج السلس

المرحلة 4 — المكتبة القياسية والمهارات العملية (الدروس 19-24)

الدرس النقاط الأساسية
معالجة السلاسل النصية حزم strings/strconv، strings.Builder
IO الملفات حزمة os، bufio للقراءة/الكتابة الفعالة، تصفح الأدلة
معالجة JSON Marshal/Unmarshal، علامات الهياكل، المعالجة المتدفقة
برمجة HTTP خادم net/http، واجهة Handler، نمط الوسائط
الاختبار اختبارات موجهة بالجدول، httptest، المعايير المرجعية، التغיפוי
التعبيرات النمطية والتاريخ حزمة regexp، تنسيق time وتحليله، المؤقتات

المرحلة 5 — المشاريع الشاملة (الدروس 25-30)

الدرس النقاط الأساسية
أدوات CLI حزمة flag، مكتبة cobra، التحقق من المعاملات
REST API تصميم RESTful، معالجة JSON، سلسلة الوسائط
قاعدة البيانات database/sql، تعريف تشغيل SQLite، تغليف CRUD
النشر والتحسين الترجمة المتقاطعة، بناءات Docker، الإغلاق السلس
المشروع الشامل (الجزء 1) تصميم البنية، هيكل الدليل، منطق الأعمال، اختبارات الوحدة
المشروع الشامل (الجزء 2) إكمال API، دمج الوسائط، اختبار التكامل، إصدار مُعبأ بالحاوية

قائمة التحقق من الكفاءات الأساسية

بعد 30 درسًا، لقد أتقنتَ:

TEXT
✅ بنية الجملة الأساسية لنظام النوع في Go
✅ البرمجة الموجهة بالكائنات (الهياكل + الواجهات)
✅ البرمجة المتزامنة (Goroutine + Channel + sync)
✅ الحزم الأساسية في المكتبة القياسية (net/http, encoding/json, testing, إلخ)
✅ تصميم وتطوير واجهات RESTful
✅ عمليات قواعد البيانات (تعريفات تشغيل SQL، تغليف CRUD)
✅ التطوير الموجه بالاختبار (اختبارات الوحدة، اختبارات التكامل، المعايير المرجعية)
✅ هندسة المشاريع (هيكل الدليل، إدارة التبعيات، إدارة التكوين)
✅ النشر المُعبأ بالحاو (Docker، بناءات متعددة المراحل، فحوصات الصحة)

📝 اقتراحات للمزيد من التعلم

الاتجاهات المتقدمة

الاتجاه الموارد الموصى بها الوصف
إطار الويب Gin، Echo أطر HTTP جاهزة للإنتاج
ORM GORM، sqlx تبسيط عمليات قاعدة البيانات
الخدمات المصغرة go-kit، Kratos، go-zero أطر خدمات مصغرة
إدارة التكوين Viper قراءة تكوين متعددة التنسيقات
التسجيل zap، zerolog مكتبات تسجيل عالية الأداء
بوابة API Kong، Traefik إدارة حركة المرور
السحابة الأصلية Kubernetes، Docker Compose، Helm تنسيق الحاويات
قائمة الانتظار NATS، Kafka، RabbitMQ اتصال غير متزامن

الكتب الموصى بها

  1. "لغة Go البرمجية" — Donovan & Kernighan
  2. "Go في العمل" — William Kennedy
  3. "التزامن في Go" — Katherine Cox-Buday
  4. "100 خطأ في Go وكيفية تجنبها" — Teiva Harsanyi

الموارد عبر الإنترنت الموصى بها

اقتراحات ممارسة المشاريع

الصعوبة المشروع تركيز الممارسة
⭐⭐ مُختصر URL HTTP، JSON، SQLite
⭐⭐⭐ غرفة دردشة WebSocket، Goroutine، Channel
⭐⭐⭐ نظام مدونة شخصية محرك القوالب، الجلسة، رفع الملفات
⭐⭐⭐⭐ زاحف موزع تعاون متعدد العقد، قائمة انتظار الرسائل، إزالة التكرار
⭐⭐⭐⭐ بوابة API سلسلة الوسائط، تحميل التوازن، تحديد المعدل والدوائر الكهربائية

الخاتمة

"الأقل هو الأكثر" — فلسفة تصميم Go

30 درسًا، من fmt.Println("Hello, World!") إلى بناء خدمة RESTful API قابلة للنشر، لقد أكملت رحلة تعلم لغة Go بأكملها.

سحر Go يكمل في بساطته — لا يمنحك خيارات كثيرة، لكن كل خيار مدروس بعناية. عند مواجهة مشاكل التزامن، فكّر في goroutine وchannel؛ عند معالجة الأخطاء، فكّر في "الصريح بدل الضمني"؛ عند تصميم الواجهات، فكّر في "نمط البط" و"التركيب بدل الوراثة".

البرمجة هي حرفة، وأفضل طريقة للتعلم هي كتابة الكود. اكتب جميع أمثلة الدروس الـ 30، وأكمل جميع التمارين، ثم ابحث عن مشروع صغير حقيقي لممارسته — ستجد أن Go أبسط وأقوى مما تتخيل.

TEXT
  ╔══════════════════════════════════════════════════╗
  ║   تهانينا على إكمال tutorial لغة Go              ║
  ║   المكون من 30 درسًا!                             ║
  ║   لديك الآن الأساس لتطوير                       ║
  ║   تطبيقات Go جاهزة للإنتاج.                     ║
  ║   Go ابنِ شيئًا مذهلًا!                         ║
  ╚══════════════════════════════════════════════════╝

الدرس التالي

هذا هو الدرس الأخير. إذا أردت مراجعة نقطة بداية الدورة، عُد إلى الدرس 1: مقدمة Go.


📝 تمارين

  1. ⭐ أساسي: راجع المشروع بأكمله وحاول تشغيله محلياً.
  2. ⭐⭐ متقدم: أضف ميزة جديدة إلى المشروع (مثلاً: تصدير البيانات إلى CSV).
  3. ⭐⭐⭐ تحدّ: اكتب وثائق API باستخدام Swagger للمشروع.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%