إضافة Swagger إلى واجهتك الخاصة بـ Go
توليد وثائق OpenAPI تلقائيًا من التعليقات في الكود
توثيق API ضروري لأي تطبيق حديث، وعندما يتعلق الأمر بـ Go APIs Swagger (OpenAPI) فقد أصبح المعيار الصناعي.
للمطورين في Go، يوفر swaggo حلًا أنيقًا لإنشاء وثائق API شاملة مباشرة من التعليقات في الكود.
هذا الصورة الجميلة تُنتجها AI model Flux 1 dev.
لماذا يهم Swagger لـ Go APIs
عند بناء واجهات برمجة التطبيقات REST، تصبح الوثائق غالبًا قديمة مع تطور الكود. يحل Swagger هذا المشكل من خلال إنشاء وثائق من مصدر الكود الخاص بك، مما يضمن أنها تبقى متزامنة مع تنفيذك. واجهة Swagger التفاعلية تسمح للمطورين بإجراء اختبارات على نقاط النهاية مباشرة من المتصفح، مما يحسن بشكل كبير تجربة المطور.
للفرق التي تبني خدمات ميكرو أو واجهات برمجة تطبيقات عامة، تصبح وثائق Swagger ضرورية لـ:
- إنشاء مكتبات العملاء: إنشاء مكتبات العملاء تلقائيًا بلغات متعددة
- اختبار العقد: التحقق من طلبات وردود الفعل ضد المخططات المحددة
- التعاون بين الفرق: توفير مصدر واحد للحقيقة لعقود API
- التوظيف للمطورين الجدد: يمكن للموظفين الجدد استكشاف واجهات API تفاعليًا
البدء مع swaggo
مكتبة swaggo هي الأداة الأكثر شيوعًا لإضافة دعم Swagger لتطبيقات Go. تعمل من خلال تحليل التعليقات الخاصة في الكود الخاص بك وتحقيق ملفات تحديد OpenAPI 3.0.
التثبيت
أولًا، تثبيت أداة سواغ CLI:
go install github.com/swaggo/swag/cmd/swag@latest
ثم أضف حزمة الوسيط Swagger المناسبة لمنصةك. لـ Gin:
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
لـ Echo:
go get -u github.com/swaggo/echo-swagger
لـ Fiber:
go get -u github.com/gofiber/swagger
التكوين الأساسي
ابدأ بإضافة معلومات API العامة في ملف main.go. بطريقة مشابهة لكيفية ترتيبك لـ REST API في Go, يجب أن تكون التعليقات واضحة ووصفية:
// @title Product API
// @version 1.0
// @description API لإدارة المنتجات مع وثائق Swagger
// @termsOfService http://swagger.io/terms/
// @contact.name دعم API
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description أدخل "Bearer" متبوعًا بمسافة ورمز JWT.
func main() {
// كود تطبيقك هنا
}
تنفيذ مع إطارة Gin
لنقم بتنفيذ مثال كامل باستخدام Gin. أولاً، قم بتعريف نماذج البيانات مع علامات المخطط:
type Product struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"Laptop" binding:"required"`
Description string `json:"description" example:"High-performance laptop"`
Price float64 `json:"price" example:"999.99" binding:"required,gt=0"`
Stock int `json:"stock" example:"50"`
}
type ErrorResponse struct {
Error string `json:"error" example:"Invalid input"`
Message string `json:"message" example:"Product name is required"`
}
الآن أضف تعليقات وظائف المُعالجة. عند العمل مع عمليات قاعدة البيانات, هذه التعليقات تساعد في وثيق تدفق البيانات:
// GetProduct godoc
// @Summary الحصول على منتج حسب ID
// @Description استرداد منتج واحد حسب معرفه الفريد
// @Tags المنتجات
// @Accept json
// @Produce json
// @Param id path int true "معرف المنتج"
// @Success 200 {object} Product
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Router /products/{id} [get]
func GetProduct(c *gin.Context) {
id := c.Param("id")
// تنفيذ هنا
c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
// CreateProduct godoc
// @Summary إنشاء منتج جديد
// @Description إضافة منتج جديد إلى الكتالوج
// @Tags المنتجات
// @Accept json
// @Produce json
// @Param product body Product true "كائن المنتج"
// @Success 201 {object} Product
// @Failure 400 {object} ErrorResponse
// @Security Bearer
// @Router /products [post]
func CreateProduct(c *gin.Context) {
var product Product
if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(400, ErrorResponse{Error: "طلب خاطئ", Message: err.Error()})
return
}
// حفظ في قاعدة البيانات
c.JSON(201, product)
}
إنشاء الوثائق
بعد إضافة التعليقات إلى الكود، أنشئ وثائق Swagger:
swag init
هذا ينشئ مجلدًا يُسمى docs مع ملفات swagger.json و swagger.yaml وملفات Go. استورد وسجّل نقطة النهاية الخاصة بـ Swagger:
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "yourproject/docs" // استيراد الوثائق المُولدة
)
func main() {
r := gin.Default()
// مسارات API
v1 := r.Group("/api/v1")
{
v1.GET("/products/:id", GetProduct)
v1.POST("/products", CreateProduct)
}
// نقطة النهاية الخاصة بـ Swagger
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
الآن قم بزيارة وثائق API التفاعلية الخاصة بك على http://localhost:8080/swagger/index.html.
تنفيذ مع إطارة Echo
يستخدم مستخدمي Echo نمطًا مشابهًا لكنه يعتمد على الوسيط الخاص بـ Echo:
package main
import (
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
_ "yourproject/docs"
)
func main() {
e := echo.New()
// مسارات API
api := e.Group("/api/v1")
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
// نقطة النهاية الخاصة بـ Swagger
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Start(":8080")
}
تنفيذ مع إطارة Fiber
تنفيذ Fiber بسيط بنفس القدر:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "yourproject/docs"
)
func main() {
app := fiber.New()
// مسارات API
api := app.Group("/api/v1")
api.Get("/products/:id", getProduct)
api.Post("/products", createProduct)
// نقطة النهاية الخاصة بـ Swagger
app.Get("/swagger/*", swagger.HandlerDefault)
app.Listen(":8080")
}
التعليقات المُتقدمة لـ Swagger
وثيق جسم الطلب المعقد
للمستрукات المُستضمنة أو المصفوفات:
type CreateOrderRequest struct {
CustomerID int `json:"customer_id" example:"123" binding:"required"`
Items []OrderItem `json:"items" binding:"required,min=1"`
ShippingAddress Address `json:"shipping_address" binding:"required"`
}
type OrderItem struct {
ProductID int `json:"product_id" example:"1" binding:"required"`
Quantity int `json:"quantity" example:"2" binding:"required,min=1"`
}
type Address struct {
Street string `json:"street" example:"123 Main St" binding:"required"`
City string `json:"city" example:"New York" binding:"required"`
ZipCode string `json:"zip_code" example:"10001" binding:"required"`
}
// CreateOrder godoc
// @Summary إنشاء طلب جديد
// @Description إنشاء طلب مع عدة عناصر ومعلومات الشحن
// @Tags الطلبات
// @Accept json
// @Produce json
// @Param order body CreateOrderRequest true "تفاصيل الطلب"
// @Success 201 {object} Order
// @Failure 400 {object} ErrorResponse
// @Failure 422 {object} ErrorResponse
// @Security Bearer
// @Router /orders [post]
func CreateOrder(c *gin.Context) {
// تنفيذ هنا
}
وثيق تحميل الملفات
// UploadImage godoc
// @Summary تحميل صورة منتج
// @Description تحميل ملف صورة لمنتج
// @Tags المنتجات
// @Accept multipart/form-data
// @Produce json
// @Param id path int true "معرف المنتج"
// @Param file formData file true "ملف الصورة"
// @Success 200 {object} map[string]string
// @Failure 400 {object} ErrorResponse
// @Security Bearer
// @Router /products/{id}/image [post]
func UploadImage(c *gin.Context) {
file, _ := c.FormFile("file")
// معالجة التحميل
}
المعلمات الاستعلامية وتصفح البيانات
// ListProducts godoc
// @Summary قائمة المنتجات مع التصفح
// @Description الحصول على قائمة من المنتجات مع تصفح اختياري
// @Tags المنتجات
// @Accept json
// @Produce json
// @Param page query int false "رقم الصفحة" default(1)
// @Param page_size query int false "عدد العناصر لكل صفحة" default(10)
// @Param category query string false "فلترة حسب الفئة"
// @Param min_price query number false "السعر الأدنى"
// @Param max_price query number false "السعر الأقصى"
// @Success 200 {array} Product
// @Failure 400 {object} ErrorResponse
// @Router /products [get]
func ListProducts(c *gin.Context) {
// تنفيذ مع التصفح
}
المصادقة والأمان
وثق طرق المصادقة المختلفة في API الخاص بك. لـ تطبيقات متعددة المستأجرين, وثيقة المصادقة مناسبة للغاية:
مصادقة Bearer Token
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description أدخل "Bearer" متبوعًا بمسافة ورمز JWT.
مصادقة مفتاح API
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description مفتاح API للمصادقة
مصادقة OAuth2
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write يمنحك الوصول للكتابة
// @scope.admin يمنحك الوصول للقراءة والكتابة إلى المعلومات الإدارية
مصادقة Basic
// @securityDefinitions.basic BasicAuth
تطبيق المصادقة على نقاط النهاية المحددة:
// @Security Bearer
// @Security ApiKeyAuth
تخصيص واجهة Swagger UI
يمكنك تخصيص مظهر وسلوك واجهة Swagger UI:
// التكوين المخصص
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
// مع عنوان مخصص
r.GET("/swagger/*any", ginSwagger.WrapHandler(
swaggerFiles.Handler,
ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
ginSwagger.DefaultModelsExpandDepth(-1),
))
لتعطيل Swagger في الإنتاج:
if os.Getenv("ENV") != "production" {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
دمج مع CI/CD
تلقين إنشاء وثائق Swagger في أنبوب CI/CD الخاص بك:
# مثال GitHub Actions
name: Generate Swagger Docs
on: [push]
jobs:
swagger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install swag
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Generate Swagger docs
run: swag init
- name: Commit docs
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add docs/
git commit -m "Update Swagger documentation" || exit 0
git push
أفضل الممارسات
1. نمط تعليقات متسق
حافظ على التنسيق المتسق عبر جميع نقاط النهاية:
// HandlerName godoc
// @Summary وصف مختصر (أقل من 50 حرف)
// @Description وصف تفصيلي لما تفعله نقطة النهاية
// @Tags اسم المورد
// @Accept json
// @Produce json
// @Param name location type required "وصف"
// @Success 200 {object} ResponseType
// @Failure 400 {object} ErrorResponse
// @Router /path [method]
2. استخدام أمثلة وصفية
أضف أمثلة واقعية لمساعد API المستهلكين:
type User struct {
ID int `json:"id" example:"1"`
Email string `json:"email" example:"user@example.com"`
CreatedAt time.Time `json:"created_at" example:"2025-01-15T10:30:00Z"`
}
3. وثيق جميع رموز الاستجابة
شامل جميع رموز HTTP الممكنة:
// @Success 200 {object} Product
// @Success 201 {object} Product
// @Failure 400 {object} ErrorResponse "طلب خاطئ"
// @Failure 401 {object} ErrorResponse "غير مُخوّل"
// @Failure 403 {object} ErrorResponse "محظور"
// @Failure 404 {object} ErrorResponse "غير موجود"
// @Failure 422 {object} ErrorResponse "خطأ في التحقق"
// @Failure 500 {object} ErrorResponse "خطأ داخلي في الخادم"
4. إصدار API
استخدم الإصدار المناسب في مسار القاعدة:
// @BasePath /api/v1
وتنظيم الكود وفقًا لذلك:
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
5. تجميع نقاط النهاية المرتبطة
استخدم الوسوم لتجميع نقاط النهاية منطقيًا:
// @Tags products
// @Tags orders
// @Tags users
6. الحفاظ على تحديث الوثائق
تشغيل swag init قبل كل التزام أو دمجها في عملية البناء الخاصة بك:
#!/bin/bash
# hook التزام
swag init
git add docs/
اختبار وثائق Swagger
عند العمل مع هياكل خادم غير مركزي مثل AWS Lambda, يصبح اختبار وثائق API أكثر أهمية:
func TestSwaggerGeneration(t *testing.T) {
// تحقق من وجود swagger.json
_, err := os.Stat("./docs/swagger.json")
if err != nil {
t.Fatal("swagger.json غير موجود، قم بتشغيل 'swag init'")
}
// تحقق من نقطة النهاية الخاصة بـ swagger
r := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
المشاكل الشائعة والحلول
عدم تحديث الوثائق
إذا لم تظهر التغييرات، تأكد من إعادة إنشاء الوثائق:
swag init --parseDependency --parseInternal
العلم --parseDependency يحلل الاعتماديات الخارجية، والعلم --parseInternal يحلل الحزم الداخلية.
أنواع مخصصة غير مُعرفة
للمواد من الحزم الخارجية، استخدم علامة swaggertype:
type CustomTime struct {
time.Time
}
func (CustomTime) SwaggerDoc() map[string]string {
return map[string]string{
"time": "مقطع زمني RFC3339",
}
}
أو استخدم علامة swaggertype:
type Product struct {
ID int `json:"id"`
UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}
المصفوفات والقيم المحددة
وثق أنواع المصفوفات والقيم المحددة:
type Filter struct {
Status []string `json:"status" enums:"active,inactive,pending"`
Tags []string `json:"tags"`
}
// @Param status query string false "فلترة الحالة" Enums(active, inactive, pending)
طرق بديلة
بينما swaggo هو الخيار الأكثر شيوعًا، توجد طرق أخرى:
go-swagger
خيار أكثر تقدمًا ولكن معقد:
brew install go-swagger
swagger generate spec -o ./swagger.json
ملفات OpenAPI يدوية
للحصول على التحكم الكامل، اكتب تحديدات OpenAPI يدويًا في YAML:
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: قائمة المنتجات
responses:
'200':
description: نجاح
ثم قم بتقديمه مع:
r.StaticFile("/openapi.yaml", "./openapi.yaml")
دمج مع الذكاء الاصطناعي والLLMs
عند بناء واجهات برمجة التطبيقات التي تتكامل مع خدمات الذكاء الاصطناعي، تصبح الوثائق المناسبة ضرورية. على سبيل المثال، عند العمل مع مخرجات LLM المُهيكلة, تساعد وثائق Swagger في وثيق أنواع طلبات وردود المخططات المعقدة:
type LLMRequest struct {
Prompt string `json:"prompt" example:"قم بتلخيص هذا النص"`
Model string `json:"model" example:"qwen2.5:latest"`
Temperature float64 `json:"temperature" example:"0.7" minimum:"0" maximum:"2"`
MaxTokens int `json:"max_tokens" example:"1000" minimum:"1"`
Schema map[string]interface{} `json:"schema,omitempty"`
}
// GenerateStructured godoc
// @Summary إنشاء مخرجات LLM مُهيكلة
// @Description إنشاء نص مع مخطط مخرجات مُقيد
// @Tags llm
// @Accept json
// @Produce json
// @Param request body LLMRequest true "parameters LLM"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorResponse
// @Router /llm/generate [post]
func GenerateStructured(c *gin.Context) {
// تنفيذ هنا
}
اعتبارات الأداء
لدي وثائق Swagger تأثيرًا بسيطًا على الأداء:
- وقت البناء:
swag initيستغرق 1-3 ثوانٍ لمعظم المشاريع - الوقت أثناء التشغيل: تُحمل الوثائق مرة واحدة عند بدء التشغيل
- الذاكرة: تضيف عادةً 1-2 ميجابايت إلى حجم الملف
- وقت الاستجابة: لا يؤثر على نقاط النهاية الخاصة بالواجهات برمجة التطبيقات نفسها
للتطبيقات الكبيرة جدًا (100+ نقطة نهائية)، فكّر في:
- تقسيمها إلى ملفات Swagger متعددة
- تحميل Swagger UI الأصول بشكل متأخر
- تقديم الوثائق من خدمة منفصلة
اعتبارات الأمان
عند كشف وثائق Swagger:
- تعطيلها في الإنتاج (إذا كانت الواجهة برمجة التطبيقات داخليًا):
if os.Getenv("ENV") == "production" {
// لا تُسجّل نقطة النهاية الخاصة بـ Swagger
return
}
- إضافة المصادقة:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- تقييد الوصول إلى نقطة النهاية:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- لا تكشف تفاصيل داخلية أبدًا:
- لا توثق نقاط النهاية الداخلية
- تجنب كشف مخططات قواعد البيانات مباشرة
- تطهير رسائل الخطأ في الوثائق
الخاتمة
إضافة وثائق Swagger إلى واجهة برمجة تطبيقات Go تُحوّل تجربة المطور من التخمين إلى الاستكشاف المُوجه. تجعل مكتبة swaggo من هذا العملية بسيطة من خلال إنشاء وثائق OpenAPI الشاملة من تعليقات الكود الخاصة بك.
الملاحظات الرئيسية:
- ابدأ بتعليقات أساسية وتوسع تدريجيًا
- الحفاظ على تزامن الوثائق مع الكود عبر CI/CD
- استخدام واجهة Swagger التفاعلية أثناء التطوير
- وثيق المصادقة، الأخطاء، والحالات الحدودية بشكل شامل
- تفكير في اعتبارات الأمان عند كشف الوثائق
سواء كنت تبني خدمات ميكرو، واجهات برمجة تطبيقات عامة، أو أدوات داخلية، فإن وثائق Swagger تحقق فوائد في تقليل عبء الدعم، تسريع التوظيف، وتحسين تصميم الواجهات برمجة التطبيقات. الاستثمار الأولي في تعلم أسلوب التعليقات يصبح سريعًا روتينيًا، وتحقيق الوثائق تلقائيًا يضمن ألا تتخلف الوثائق عن تنفيذك.
للمطورين في Go، الجمع بين التипات القوية، إنشاء الكود، ونظام swaggo التعليقات يخلق تدفقًا قويًا يجعل وثائق API جزءًا طبيعيًا من عملية التطوير بدلًا من أن تكون بعدًا.
الروابط المفيدة
- Go Cheatsheet
- بناء واجهات برمجة التطبيقات REST في Go
- مقارنة ORMs في Go لـ PostgreSQL: GORM مقابل Ent مقابل Bun مقابل sqlc
- أنماط قواعد البيانات متعددة المستأجرين مع أمثلة في Go
- LLMs مع مخرجات مُهيكلة: Ollama، Qwen3 وPython أو Go
- أداء Lambda على AWS: JavaScript مقابل Python مقابل Go