المشروع الشامل (الجزء 2)
الدرس 30: المشروع الشامل (الجزء 2)
تشبيه من الواقع
في الدرس السابق أكملنا تحليل المتطلبات، تصميم هيكل الدليل، نماذج البيانات، ومنطق الأعمال الأساسي لـ "نظام إدارة المهام TaskFlow" — مثل بناء منزل بالأساس موضوع والهيكل مُقام. في هذا الدرس، سنكمل التشطيب والتسليم:
- REST API = تركيب الأبواب والنوافذ حتى يتمكن الناس من الخارج من الدخول
- الوسائط = نظام التحكم في الوصول (المصادقة)، كاميرات الأمان (التسجيل)، تسجيل الزوار (CORS)
- تكامل قاعدة البيانات = استبدال الأثاث المؤقت بأثاث مخصص، متين وطويل الأمد
- اختبار التكامل = فحص القبول، ضمان عمل الماء والكهرباء والغاز بشكل صحيح
- توثيق API = دليل المستخدم
- Dockerfile = تغليف المنزل في حاوية، صالح للسكن أينما يُنقل
بعد هذا الدرس، سيكون لديك مشروع Go قابل للنشر، قابل للاختبار، موثق بالكامل.
مراجعة المشروع
في الدرس السابق بنينا الهيكل الأساسي لـ TaskFlow. هذا الدرس يكمل صقله. إذا لم تقرأ الدرس 29: المشروع الشامل (الجزء 1)، يُوصى بإكمال النصف أولاً.
هيكل المشروع النهائي:
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 تنفيذ المعالج الكامل
// 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 تسجيل المسارات وبدء تشغيل الخادم
// 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
}
net/http مطابقة الأسلوب ("GET /path") ومعاملات المسار ({id})، مما يتيح لك بناء واجهات RESTful بدون مكتبات توجيه طرف ثالث.
2. دمج الوسائط
2.1 نمط سلسلة الوسائط
الوسائط هي "عوائق" في خط معالجة طلبات HTTP التي يمكنها تنفيذ منطق مشترك قبل وبعد وصول الطلبات إلى المعالج:
الطلب → [CORS] → [التسجيل] → [المصادقة] → [المعالج] → الاستجابة
↓ ↓ ↓
التحكم تسجيل التحقق
عبر الأصل المدة من الهوية
2.2 البنية التحتية للوسائط
// 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
// 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 وسائط التسجيل
// 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 وسائط المصادقة
// 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 |
|---|---|---|
| التثبيت | بدون تكوين، ملف واحد | يتطلب خدمة منفصلة |
| الاستخدام | مشاريع صغيرة-متوسطة، مدمجة | بيئات إنتاج كبيرة |
| التزامن | قراءة متزامنة، كتابة متسلسلة | تزامن عالي |
| الترحيل | يتطلب تنفيذًا ذاتيًا | أدوات غنية |
3.2 تنفيذ تخزين SQLite
// 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 واجهة التخزين الموحدة
// 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 حقيقية:
// 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 تشغيل اختبارات التكامل
# تشغيل جميع الاختبارات
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:
# 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 تلقائيًا من تعليقات الكود:
// إضافة تعليقات فوق دوال المعالج
// 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) {
// ...
}
أمر الإنشاء:
# تثبيت أداة 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 الممتاز الأقسام التالية:
# 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
# إنشاء مهمة
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
هيكل المشروع
taskflow/
├── cmd/server/ # نقطة دخول البرنامج
├── internal/
│ ├── model/ # نماذج البيانات
│ ├── store/ # طبقة تخزين البيانات
│ ├── service/ # طبقة منطق الأعمال
│ ├── handler/ # طبقة معالج HTTP
│ └── middleware/ # الوسائط
├── docs/ # توثيق API
├── Dockerfile
└── README.md
الاختبار
# تشغيل جميع الاختبارات
go test ./... -v
# عرض التغיפוי
go test ./... -cover
# كشف الأحداث المتسابقة
go test -race ./...
النشر
نشر Docker
# بناء الصورة
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
# 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 البناء والتشغيل
# بناء صورة 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)
# تجميع ملف ثنائي لـ 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 قائمة التحقق من النشر
✅ فحوصات الأمان
□ API_KEY في الإنتاج تغير إلى كلمة مرور قوية
□ تكوين CORS يقيد نطاقات الأصل المسموح بها
□ السجلات لا تحتوي على معلومات حساسة
□ استخدام HTTPS (تكوين وكيل عكسي)
✅ فحوصات الأداء
□ تكوين بركة اتصال قاعدة البيانات
□ مهلات HTTP معينة (قراءة/كتابة/خمول)
□ حجم جسم الطلب محدود
□ الاستعلامات المصفحة لها حدود أقصى
✅ فحوصات الموثوقية
□ نقطة نهاية فحص الصحة متاحة
□ منطق الإغلاق السلس مُنفّذ
□ تنسيق السجل JSON منظم
□ فحص صحة Docker مُكوّن
8.2 استخدام 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 لديه قيود على الكتابات المتزامنة. تأكد من:
// 1. تعيين بركة الاتصال إلى 1 (اتصال واحد)
db.SetMaxOpenConns(1)
// 2. تفعيل وضع WAL (يسمح بتزامن القراءة/الكتابة)
db.Exec("PRAGMA journal_mode=WAL")
// 3. تعيين مهلة انتظار مزدحمة
// أضف ?_busy_timeout=5000 إلى سلسلة الاتصال
إذا كان التزامن عاليًا، فكّر في التبديل إلى PostgreSQL.
س2: هل ترتيب تنفيذ الوسائط مهم؟
ج: نعم. الترتيب الموصى به: CORS → التسجيل → المصادقة → معالج الأعمال. الأسباب:
- CORS يجب معالجته أولاً، الطلبات المسبقة (OPTIONS) لا تحمل معلومات مصادقة
- التسجيل قبل المصادقة يلتقط الطلبات غير المصادقة
- المصادقة قبل معالج الأعمال تمنع الطلبات غير الصالحة
// في Chain، الوسائط المُمررة أولاً تُنفّذ أولاً
handler := middleware.Chain(mux,
middleware.CORS(), // 1. تُنفّذ أولاً
middleware.Logging(), // 2. ثانياً
middleware.Auth(key), // 3. تُنفّذ أخيرًا
)
س3: كيف ندير مفاتيح API في الإنتاج؟
ج: لا تُشفر المفاتيح أبدًا أو تُدرجها في مستودع الكود. الأساليب الموصى بها:
# الطريقة 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 درسًا، لقد أتقنتَ:
✅ بنية الجملة الأساسية لنظام النوع في 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 | اتصال غير متزامن |
الكتب الموصى بها
- "لغة Go البرمجية" — Donovan & Kernighan
- "Go في العمل" — William Kennedy
- "التزامن في Go" — Katherine Cox-Buday
- "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 أبسط وأقوى مما تتخيل.
╔══════════════════════════════════════════════════╗
║ تهانينا على إكمال tutorial لغة Go ║
║ المكون من 30 درسًا! ║
║ لديك الآن الأساس لتطوير ║
║ تطبيقات Go جاهزة للإنتاج. ║
║ Go ابنِ شيئًا مذهلًا! ║
╚══════════════════════════════════════════════════╝
الدرس التالي
هذا هو الدرس الأخير. إذا أردت مراجعة نقطة بداية الدورة، عُد إلى الدرس 1: مقدمة Go.
📝 تمارين
- ⭐ أساسي: راجع المشروع بأكمله وحاول تشغيله محلياً.
- ⭐⭐ متقدم: أضف ميزة جديدة إلى المشروع (مثلاً: تصدير البيانات إلى CSV).
- ⭐⭐⭐ تحدّ: اكتب وثائق API باستخدام Swagger للمشروع.



