تطوير واجهات REST API

نشر واجهات برمجة التطبيقات وإدارة العمليات

18 دقيقة الدرس 31 من 35

نشر واجهات برمجة التطبيقات وإدارة العمليات

يتطلب نشر واجهات برمجة التطبيقات إلى بيئة الإنتاج تخطيطًا دقيقًا وأتمتة ومراقبة. في هذا الدرس، سنستكشف ممارسات DevOps الحديثة لنشر واجهات برمجة التطبيقات، بما في ذلك الحاويات، وخطوط CI/CD، وتكوين البيئات، والنشر بدون توقف.

فهم تحديات نشر واجهات برمجة التطبيقات

تواجه عمليات نشر واجهات برمجة التطبيقات تحديات فريدة مقارنة بتطبيقات الويب التقليدية:

  • عدم التوقف: يجب أن تظل واجهات برمجة التطبيقات متاحة أثناء عمليات النشر حيث يعتمد عليها العملاء على مدار الساعة
  • التوافق العكسي: التغييرات الكبيرة يمكن أن تعطل التكاملات الحالية
  • تماثل البيئات: يجب أن تكون بيئات التطوير والتجريب والإنتاج متسقة
  • ترحيلات قواعد البيانات: يجب تنسيق تغييرات المخطط مع عمليات نشر الكود
  • إدارة التكوين: يجب أن تكون الأسرار والإعدادات الخاصة بالبيئة آمنة
فلسفة نشر واجهات برمجة التطبيقات: تعامل مع واجهة برمجة التطبيقات الخاصة بك كمنتج. يجب أن يكون كل نشر قابلاً للإلغاء وقابلاً للملاحظة وموثقًا. احتفظ دائمًا بخطة للرجوع.

الحاويات باستخدام Docker

يتيح Docker عمليات نشر متسقة عبر جميع البيئات. إليك Dockerfile جاهز للإنتاج لواجهة برمجة تطبيقات Laravel:

Dockerfile:
<!-- بناء متعدد المراحل للصور المحسنة -->
FROM php:8.2-fpm-alpine AS base

# تثبيت تبعيات النظام
RUN apk add --no-cache \
    nginx \
    postgresql-dev \
    redis \
    git \
    zip \
    unzip \
    curl

# تثبيت امتدادات PHP
RUN docker-php-ext-install pdo pdo_pgsql opcache

# تثبيت Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

# نسخ ملفات composer
COPY composer.json composer.lock ./

# تثبيت التبعيات (تخطي dev في الإنتاج)
RUN composer install --no-dev --optimize-autoloader --no-scripts

# نسخ كود التطبيق
COPY . .

# تعيين الأذونات
RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 755 /var/www/html/storage

# إنشاء autoloader محسّن
RUN composer dump-autoload --optimize

# ذاكرة التخزين المؤقت للتكوين
RUN php artisan config:cache \
    && php artisan route:cache \
    && php artisan view:cache

EXPOSE 9000

CMD ["php-fpm"]
docker-compose.yml للتطوير:
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/var/www/html
    environment:
      - APP_ENV=local
      - APP_DEBUG=true
    depends_on:
      - database
      - redis

  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: api_db
      POSTGRES_USER: api_user
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - .:/var/www/html
    ports:
      - "8000:80"
    depends_on:
      - app

volumes:
  db_data:

إدارة تكوين البيئة

لا تقم أبدًا بإرسال الأسرار إلى نظام التحكم في الإصدارات. استخدم متغيرات البيئة وأنظمة إدارة الأسرار:

قالب .env.example:
APP_NAME="My API"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://api.example.com

LOG_CHANNEL=stack
LOG_LEVEL=error

DB_CONNECTION=pgsql
DB_HOST=database.example.com
DB_PORT=5432
DB_DATABASE=api_production
DB_USERNAME=
DB_PASSWORD=

REDIS_HOST=redis.example.com
REDIS_PASSWORD=
REDIS_PORT=6379

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

JWT_SECRET=
JWT_TTL=60

MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

SENTRY_LARAVEL_DSN=
تحذير أمني: لا تستخدم أبدًا بيانات الاعتماد الافتراضية أو الأمثلة في الإنتاج. أنشئ أسرارًا قوية وفريدة لكل بيئة. استخدم أدوات إدارة الأسرار مثل AWS Secrets Manager أو HashiCorp Vault أو Azure Key Vault للبيانات الحساسة.

خط CI/CD باستخدام GitHub Actions

أتمتة الاختبار والبناء والنشر باستخدام خطوط CI/CD:

.github/workflows/deploy.yml:
name: Deploy API

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.2
          extensions: pdo, pdo_pgsql, redis
          coverage: xdebug

      - name: Install Dependencies
        run: composer install --prefer-dist --no-progress

      - name: Copy .env
        run: cp .env.ci .env

      - name: Generate Application Key
        run: php artisan key:generate

      - name: Run Migrations
        run: php artisan migrate --force

      - name: Run Tests
        run: php artisan test --coverage --min=80

      - name: Run Static Analysis
        run: ./vendor/bin/phpstan analyse

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to Production
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /var/www/api
            docker-compose pull
            docker-compose up -d --no-deps --build app
            docker-compose exec -T app php artisan migrate --force
            docker-compose exec -T app php artisan config:cache
            docker-compose exec -T app php artisan route:cache
            docker-compose exec -T app php artisan queue:restart

استراتيجيات النشر بدون توقف

قم بتطبيق استراتيجيات نشر تحافظ على توفر واجهة برمجة التطبيقات الخاصة بك أثناء التحديثات:

1. النشر الأزرق-الأخضر

سكريبت النشر الأزرق-الأخضر:
#!/bin/bash

# نشر أزرق-أخضر لواجهة برمجة التطبيقات
CURRENT=$(docker ps --filter "name=api-blue" -q)
if [ -z "$CURRENT" ]; then
    NEW_COLOR="blue"
    OLD_COLOR="green"
else
    NEW_COLOR="green"
    OLD_COLOR="blue"
fi

echo "جاري النشر إلى بيئة $NEW_COLOR..."

# بدء البيئة الجديدة
docker-compose -f docker-compose.$NEW_COLOR.yml up -d

# انتظار فحص الصحة
echo "انتظار فحص الصحة..."
for i in {1..30}; do
    if curl -f http://localhost:8001/health > /dev/null 2>&1; then
        echo "نجح فحص الصحة!"
        break
    fi
    sleep 2
done

# تبديل حركة المرور
echo "تبديل حركة المرور إلى $NEW_COLOR..."
# تحديث موازن التحميل أو تكوين الوكيل العكسي
nginx -s reload

# إيقاف البيئة القديمة
echo "إيقاف بيئة $OLD_COLOR..."
docker-compose -f docker-compose.$OLD_COLOR.yml down

echo "اكتمل النشر!"

2. النشر المتدحرج

تحديث Kubernetes المتدحرج:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # إضافة pod إضافي واحد أثناء التحديث
      maxUnavailable: 1  # السماح بعدم توفر pod واحد
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: ghcr.io/myorg/api:latest
        ports:
        - containerPort: 9000
        livenessProbe:
          httpGet:
            path: /health
            port: 9000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 9000
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

استراتيجيات ترحيل قواعد البيانات

تعامل مع تغييرات قاعدة البيانات بأمان أثناء عمليات النشر:

أفضل ممارسات الترحيل:
  • اجعل دائمًا الترحيلات متوافقة مع الإصدارات السابقة
  • استخدم ترحيلات متعددة المراحل للتغييرات الكبيرة
  • اختبر الترحيلات على بيانات شبيهة بالإنتاج
  • حافظ على سرعة الترحيلات (استخدم وظائف الخلفية لتغييرات البيانات الكبيرة)
  • احتفظ دائمًا بخطة للرجوع
نمط الترحيل الآمن:
<?php

// المرحلة 1: إضافة عمود جديد (قابل للإلغاء، متوافق مع الإصدارات السابقة)
Schema::table('users', function (Blueprint $table) {
    $table->string('new_email')->nullable();
});

// نشر الكود الذي يكتب إلى كل من الأعمدة القديمة والجديدة

// المرحلة 2: ملء البيانات (تشغيل كوظيفة خلفية)
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        $user->new_email = $user->email;
        $user->save();
    }
});

// المرحلة 3: جعل العمود الجديد غير قابل للإلغاء
Schema::table('users', function (Blueprint $table) {
    $table->string('new_email')->nullable(false)->change();
});

// نشر الكود الذي يستخدم العمود الجديد فقط

// المرحلة 4: حذف العمود القديم
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('email');
    $table->renameColumn('new_email', 'email');
});

المراقبة وفحوصات الصحة

قم بتطبيق فحوصات صحة شاملة للمراقبة التلقائية:

routes/api.php:
<?php

// فحص الحياة - هل التطبيق يعمل؟
Route::get('/health', function () {
    return response()->json([
        'status' => 'healthy',
        'timestamp' => now()->toIso8601String(),
    ]);
});

// فحص الجاهزية - هل يمكن للتطبيق خدمة حركة المرور؟
Route::get('/ready', function () {
    $checks = [
        'database' => false,
        'redis' => false,
        'storage' => false,
    ];

    try {
        DB::connection()->getPdo();
        $checks['database'] = true;
    } catch (\Exception $e) {
        Log::error('فشل فحص قاعدة البيانات: ' . $e->getMessage());
    }

    try {
        Redis::ping();
        $checks['redis'] = true;
    } catch (\Exception $e) {
        Log::error('فشل فحص Redis: ' . $e->getMessage());
    }

    try {
        Storage::disk('local')->exists('test');
        $checks['storage'] = true;
    } catch (\Exception $e) {
        Log::error('فشل فحص التخزين: ' . $e->getMessage());
    }

    $allHealthy = array_reduce($checks, fn($carry, $check) => $carry && $check, true);

    return response()->json([
        'status' => $allHealthy ? 'ready' : 'not_ready',
        'checks' => $checks,
        'timestamp' => now()->toIso8601String(),
    ], $allHealthy ? 200 : 503);
});

// نقطة نهاية الحالة التفصيلية (مصادق عليها)
Route::middleware(['auth:api', 'admin'])->get('/status', function () {
    return response()->json([
        'app' => [
            'name' => config('app.name'),
            'env' => config('app.env'),
            'version' => config('app.version'),
        ],
        'system' => [
            'php_version' => PHP_VERSION,
            'laravel_version' => app()->version(),
            'memory_usage' => memory_get_usage(true) / 1024 / 1024 . ' MB',
            'peak_memory' => memory_get_peak_usage(true) / 1024 / 1024 . ' MB',
        ],
        'cache' => [
            'driver' => config('cache.default'),
            'redis_connection' => Redis::connection()->ping(),
        ],
        'database' => [
            'connection' => DB::connection()->getName(),
            'version' => DB::select('SELECT version()')[0]->version,
        ],
        'queue' => [
            'connection' => config('queue.default'),
            'failed_jobs' => DB::table('failed_jobs')->count(),
        ],
    ]);
});

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

قائمة التحقق قبل النشر:
  • ✓ اجتياز جميع الاختبارات (الوحدة، التكامل، من البداية للنهاية)
  • ✓ الموافقة على مراجعة الكود
  • ✓ اكتمال فحص الأمان (لا توجد ثغرات حرجة)
  • ✓ اختبار ترحيلات قاعدة البيانات على التجريب
  • ✓ التحقق من التوافق العكسي
  • ✓ تحديث الوثائق
  • ✓ تكوين تنبيهات المراقبة
  • ✓ توثيق خطة الرجوع
  • ✓ إخطار أصحاب المصلحة
قائمة التحقق بعد النشر:
  • ✓ اجتياز فحوصات الصحة
  • ✓ تنفيذ اختبارات الدخان
  • ✓ معدلات الأخطاء ضمن النطاق الطبيعي
  • ✓ أوقات الاستجابة مقبولة
  • ✓ تحسين استعلامات قاعدة البيانات
  • ✓ تسخين ذاكرة التخزين المؤقت
  • ✓ تشغيل وظائف الخلفية
  • ✓ مراقبة السجلات للأخطاء
  • ✓ التحقق من تكاملات العملاء
  • ✓ توثيق النشر

إجراءات الرجوع

سكريبت الرجوع السريع:
#!/bin/bash

echo "بدء الرجوع..."

# الحصول على الإصدار السابق
PREVIOUS_VERSION=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1))

echo "الرجوع إلى الإصدار: $PREVIOUS_VERSION"

# الخروج إلى الإصدار السابق
git checkout $PREVIOUS_VERSION

# إعادة البناء والنشر
docker-compose build
docker-compose up -d

# الرجوع عن الترحيلات (إذا لزم الأمر)
# php artisan migrate:rollback --step=1

# مسح ذاكرات التخزين المؤقت
docker-compose exec app php artisan cache:clear
docker-compose exec app php artisan config:cache
docker-compose exec app php artisan route:cache

echo "اكتمل الرجوع!"
echo "يرجى التحقق من أن التطبيق يعمل بشكل صحيح."
تحذير الرجوع: يمكن أن تكون عمليات الرجوع عن قاعدة البيانات مدمرة. إذا حذف الترحيل الخاص بك بيانات، فلن يؤدي الرجوع إلى استعادتها. قم دائمًا بنسخ احتياطي لقاعدة البيانات قبل عمليات النشر واختبر إجراءات الرجوع في التجريب.

الملخص

يتطلب نشر واجهات برمجة التطبيقات وDevOps:

  • الحاويات للاتساق عبر البيئات
  • خطوط CI/CD تلقائية للاختبار والنشر
  • إدارة تكوين آمنة بدون أسرار مشفرة
  • استراتيجيات النشر بدون توقف (أزرق-أخضر، تحديثات متدحرجة)
  • أنماط ترحيل قاعدة بيانات آمنة مع خطط الرجوع
  • فحوصات صحة ومراقبة شاملة
  • إجراءات نشر ورجوع موثقة

في الدرس التالي، سنستكشف أنماط تصميم واجهات برمجة التطبيقات بما في ذلك نمط المستودع، وDTOs، وفئات الإجراءات لكود واجهة برمجة تطبيقات أكثر نظافة وقابلية للصيانة.