هيكل مشروع Go: الممارسات والأنماط

نظم مشاريع Go الخاصة بك لتحقيق القدرة على التوسع والوضوح

Page content

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

شجرة المشروع

فهم فلسفات Go في تنظيم المشاريع

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

القاعدة الأساسية هي البساطة أولاً، والتعقيد فقط عند الحاجة. يقع العديد من المطورين في فخ التصميم المفرط للمشاريع في البداية، مما يؤدي إلى إنشاء أقسام مُستحقة وتعقيدات مبكرة. ابدأ بما تحتاجه اليوم، وقم بتحديثه عندما ينمو مشروعك.

متى يجب استخدام الدليل interne/ مقارنةً مع pkg/؟

يُستخدم الدليل internal/ لأغراض محددة في تصميم Go: يحتوي على حزم لا يمكن استيرادها من مشاريع خارجية. يفرض مترجم Go هذا القيود، مما يجعل internal/ مناسبًا تمامًا للمنطق الخاص بالتطبيقات، وقواعد العمل، والخدمات التي لا تُقصد استخدامها خارج مشروعك.

من ناحية أخرى، يشير الدليل pkg/ إلى أن الكود مُخصص للاستخدام الخارجي. ضع الكود فقط هنا إذا كنت تبني مكتبة أو مكونات قابلة لإعادة الاستخدام التي ترغب في أن يستخدمها الآخرون. لا تحتاج العديد من المشاريع إلى pkg/ على الإطلاق—if لا يتم استهلاك كودك خارجًا، فاحتفظ به في الجذر أو في internal/، وهو أوضح. عند بناء مكتبات قابلة لإعادة الاستخدام، فكّر في الاستفادة من النماذج العامة في Go لخلق مكونات قابلة لإعادة الاستخدام آمنة من حيث النوع.

الهيكل القياسي لمشروع Go

الأنمط الأكثر شيوعًا المعترف به هو golang-standards/project-layout، على الرغم من أنه من المهم ملاحظة أنه ليس معيارًا رسميًا. إليك كيف يبدو الهيكل النموذجي:

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

الدليل cmd/

يحتوي الدليل cmd/ على نقاط دخول تطبيقك. يمثل كل فرع دليل دليلًا منفصلًا لبرنامج قابل للتنفيذ. على سبيل المثال، cmd/api/main.go يبني خادم API الخاص بك، بينما cmd/worker/main.go قد يبني معالج وظائف خلفية.

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

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    
    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

الدليل internal/

هذا هو المكان الذي يعيش فيه كود تطبيقك الخاص. يمنع مترجم Go أي مشروع خارجي من استيراد الحزم داخل internal/، مما يجعله مثاليًا لـ:

  • منطق العمل ونماذج النطاق
  • خدمات التطبيق
  • واجهات وخدمات داخلية
  • مستودعات قاعدة البيانات (للمزيد حول اختيار ORM المناسب، راجع مقارنتنا بين ORMs لـ PostgreSQL في Go)
  • منطق المصادقة والتفويض

نظم internal/ حسب النطاق أو الميزة، لا حسب الطبقات التقنية. بدلًا من internal/handlers/، internal/services/، internal/repositories/، فضل internal/user/، internal/order/، internal/payment/ حيث يحتوي كل حزمة على مُعالجاتها، خدماتها، ومستودعاتها.

هل يجب أن أبدأ بهيكل دليل معقد؟

بالتأكيد لا. إذا كنت تبني أداة صغيرة أو نموذجًا أوليًا، ابدأ بـ:

myproject/
├── main.go
├── go.mod
└── go.sum

مع تطور مشروعك وتحديد المجموعات المنطقية، أضف الدليلات. قد تضيف حزمة db/ عندما يصبح منطق قاعدة البيانات كبيرًا، أو حزمة api/ عندما تزيد عدد مُعالجات HTTP. دع الهيكل يظهر بشكل طبيعي بدلًا من فرضه مسبقًا.

الهيكل المسطح مقابل الهيكل المُستحاق: العثور على التوازن

أحد أكثر الأخطاء شيوعًا في تنظيم مشاريع Go هو الاستخدام المفرط للهيكل المُستحاق. تفضل Go الهيكل المسطح—عادة مستوى واحد أو اثنين فقط. يزيد الاستخدام المفرط للهيكل المُستحاق من عبء التفكير ويُعقد الاستيراد.

ما هي الأخطاء الشائعة؟

الهيكل المُستحاق المفرط: تجنب الهياكل مثل internal/services/user/handlers/http/v1/. هذا يخلق تعقيدًا غير ضروري في التنقل. بدلًا من ذلك، استخدم internal/user/handler.go.

أسماء الحزم العامة: الأسماء مثل utils، helpers، common، أو base هي مؤشرات على وجود مشاكل في الكود. لا تنقل وظيفة محددة، وغالبًا ما تصبح أماكن لتخزين كود غير مرتبطة. استخدم أسماء وصفية: validator، auth، storage، cache.

الاعتماديات الدائرية: عندما تستورد الحزمة A الحزمة B، وتستورد B الحزمة A، فإنك تواجه اعتمادية دائرية—وهي خطأ في التجميع في Go. هذا يشير عادةً إلى فصل سيء للمسؤوليات. أدخل واجهات أو استخرج الأنواع المشتركة إلى حزمة منفصلة.

دمج المهام: حافظ على تركيز مُعالجات HTTP على مهام HTTP فقط، ومستودعات قاعدة البيانات على الوصول إلى البيانات، ووضع منطق العمل في حزم الخدمات. وضع قواعد العمل في مُعالجات يجعل الاختبار صعبًا ويُربط منطق النطاق مع HTTP.

تصميم النطاق والهيكل السداسي

للمشاريع الكبيرة، خاصةً الميكرو خدمة، يوفر تصميم النطاق (DDD) مع الهيكل السداسي فصلًا واضحًا للمهام.

كيف أُنظم خدمة ميكرو خدمة وفقًا لتصميم النطاق؟

يُنظم الهيكل السداسي الكود في طبقات مركبة مع تدفق الاعتماديات نحو الداخل:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # النماذج والنماذج القيمية للنطاق
│       ├── repository.go    # واجهة المستودع (المنفذ)
│       └── service.go       # خدمات النطاق
├── application/
│   └── user/
│       ├── create_user.go   # حالة الاستخدام: إنشاء مستخدم
│       ├── get_user.go      # حالة الاستخدام: استرداد المستخدم
│       └── service.go       # تنسيق خدمة التطبيق
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # مُعالج HTTP (نهايات REST)
│   ├── postgres/
│   │   └── user_repo.go     # مُستودع قاعدة البيانات (يُنفذ واجهة المستودع)
│   └── redis/
│       └── cache.go         # مُستودع التخزين المؤقت
└── api/
    └── http/
        └── router.go        # تكوين المسارات والمتوسطات

طبقة النطاق (domain/): منطق العمل الأساسي، والنماذج، والنماذج القيمية، وواجهات خدمات النطاق. لا تحتوي هذه الطبقة على أي اعتماديات خارجية—لا HTTP، ولا استيراد قاعدة البيانات. تحدد واجهات المستودع (المنافذ) التي ينفذها المُستودعات.

طبقة التطبيق (application/): حالات الاستخدام التي تنظم كائنات النطاق. كل حالة استخدام (مثل “إنشاء مستخدم”، “معالجة الدفع”) هي ملف منفصل أو حزمة. هذه الطبقة تنظم كائنات النطاق ولكن لا تحتوي على أي قواعد عمل.

طبقة المُستودع (adapter/): تنفذ الواجهات المُعرفة من طبقات داخلية. تحول مُعالجات HTTP الطلبات إلى كائنات النطاق، وتحصل مُستودعات قاعدة البيانات على التخزين، وتنظم قوائم الرسائل التواصلية المُرسلة. تحتوي هذه الطبقة على جميع الكود المُرتبط بالمنصات والبنية التحتية.

طبقة API (api/): المسارات، المتوسطات، DTOs (أوامر نقل البيانات)، تحديد إصدارات API، ومواصفات OpenAPI.

يضمن هذا الهيكل أن منطق العمل الأساسي الخاص بك يظل قابلًا للاختبار ومستقلًا عن المنصات، والقواعد، أو الخدمات الخارجية. يمكنك تبديل PostgreSQL إلى MongoDB، أو REST إلى gRPC دون لمس كود النطاق.

الأنماط العملية للمشاريع المختلفة

أداة سطر الأوامر الصغيرة

لتطبيقات سطر الأوامر، يجب أن يكون الهيكل داعمًا لعدد كبير من الأوامر والفرعيات. فكّر في استخدام إطارات مثل Cobra لتنظيم الأوامر وViper لإدارة التكوين. يغطي دليلنا حول بناء تطبيقات سطر الأوامر في Go مع Cobra وViper هذا النمط بالتفصيل.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

خدمة API REST

عند بناء خدمة API REST في Go، يفصل هذا الهيكل المهام بشكل نظيف: تُعالج المُعالجات الأمور المتعلقة بـ HTTP، وتحتوي الخدمات على منطق العمل، وتحصل المستودعات على الوصول إلى البيانات. لدليل شامل يغطي النهج القائم على المكتبة القياسية، والإطارات، والمصادقة، والأنماط الخاصة بالاختبار، والممارسات المثالية للاستخدام في الإنتاج، راجع دليلنا حول دليل كامل لبناء APIs REST في Go.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

مستودع واحد مع عدة خدمات

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # الحزم الداخلية المشتركة
│   ├── api/          # الكود الخاص بـ API
│   ├── worker/       # الكود الخاص بالعميل
│   └── scheduler/    # الكود الخاص بالجدول
├── pkg/              # المكتبات المشتركة
├── go.work           # ملف Go workspace
└── README.md

الاختبار والتوثيق

ضع ملفات الاختبار بجانب الكود الذي تستثمره باستخدام لاحقة _test.go:

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

هذا الاتفاقية تُبقي الاختبارات قريبة من التنفيذ، مما يجعلها سهلة العثور عليها والحفاظ عليها. للاختبارات المتكاملة التي تتعامل مع عدة حزم، أنشئ دليلًا منفصلًا test/ في جذر المشروع. لدليل شامل حول كتابة اختبارات وحدة فعّالة، بما في ذلك اختبارات قائمة على الجداول، والتمثيلات، والتحليل، والممارسات المثالية، راجع دليلنا حول دليل اختبارات الوحدة في Go.

يجب أن تكون الوثائق في:

  • README.md: مقدمة المشروع، تعليمات التثبيت، الاستخدام الأساسي
  • docs/: وثائق مفصلة، قرارات التصميم، مراجع API
  • api/: مواصفات OpenAPI/Swagger، تعريفات protobuf

لـ APIs REST، توليد وتقديم وثائق OpenAPI مع Swagger ضروري للكشف عن API وتجربة المطور. يغطي دليلنا حول إضافة Swagger إلى API في Go التكامل مع الإطارات الشائعة والممارسات المثالية.

إدارة الاعتماديات باستخدام Go Modules

يجب أن يستخدم كل مشروع Go Go Modules لإدارة الاعتماديات. لمرجع شامل حول الأوامر في Go وإدارة الموديولات، راجع دليل Go. ابدأ بـ:

go mod init github.com/yourusername/myproject

هذا ينشئ go.mod (الاعتماديات والنسخ) وgo.sum (المؤشرات للتحقق). احتفظ بهذه الملفات في التحكم في الإصدار لبناء مُعاد.

تحديث الاعتماديات بانتظام:

go get -u ./...          # تحديث جميع الاعتماديات
go mod tidy              # إزالة الاعتماديات غير المستخدمة
go mod verify            # التحقق من المؤشرات

النقاط الرئيسية

  1. ابدأ ببساطة، وطور بشكل طبيعي: لا تُفرط في التصميم في البداية. أضف الدليلات والحزم عندما تطلبها التعقيد.

  2. تفضّل الهيكل المسطح: قلل من التحديد إلى مستوى واحد أو اثنين. يُحسّن الهيكل المسطح لـ Go من قابلية القراءة.

  3. استخدم أسماء الحزم الوصفية: تجنب الأسماء العامة مثل utils. اختر أسماء تُظهر ما تفعله الحزمة: auth، storage، validator.

  4. فصل المهام بوضوح: حافظ على تركيز مُعالجات HTTP على HTTP فقط، ومستودعات قاعدة البيانات على الوصول إلى البيانات، ومنطق العمل في حزم الخدمات.

  5. استخدم internal/ للخصوصية: استخدمه للكود الذي لا يجب استيراده خارجًا. معظم كود التطبيق ينتمي هنا.

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

  7. دع Go توجهك: اتبع عادات Go بدلًا من استيراد أنماط من لغات أخرى. Go لديها فلسفتها الخاصة حول البساطة والتنظيم.

روابط مفيدة

مقالات متعلقة