الألم الصامت عند نشر التطبيقات

الألم الصامت عند نشر التطبيقات

M. Zakyuddin Munziri

M. Zakyuddin Munziri

@zakiego

كتب أصلاً بـ English.

أستمر في رؤية نفس قصة النشر (deployment) تتكرر عبر الفرق والتقنيات. تبدو صغيرة في البداية. لا شيء يفشل أثناء النشر. فحوصات الصحة خضراء. الـ CI سعيد. الخادم يعمل.

ثم لاحقاً، ينكسر شيء ما في الإنتاج (production) ويصبح التحقيق مطاردة بطيئة وقبيحة. السبب الجذري غالباً ما يكون نفسه. متغيرات بيئة (environment variables) مفقودة أو خاطئة. التطبيق عمل لساعات أو أيام قبل أن تصبح القيمة المفقودة مهمة أخيراً. بحلول ذلك الوقت يبدو الفشل غامضاً. بحلول ذلك الوقت يكون الناس متوترين.

هذا النمط شائع جداً لدرجة أنه يجب التعامل معه كعيوب في التصميم، وليس إهمالاً من المطور. متغيرات البيئة ليست بيانات وصفية اختيارية. إنها إعدادات حرجة لوقت التشغيل (runtime). عاملها هكذا.


القصة المألوفة

مطور يطلب من آخر المساعدة في شحن تطبيق. الـ CI ينجح. النشر يمر. عند الاختبار، تبدو الأمور جيدة. ثم تتصرف ميزة بشكل غير متوقع. أو تعيد نقطة نهاية (endpoint) بصمت الحمولة الخطأ.

نحفر في السجلات. لا شيء واضح. لا يوجد تتبع مكدس (stack trace) يشير مباشرة إلى المشكلة. تمر الساعات. أخيراً يجدها شخص ما: متغير بيئة مطلوب لم يصل أبداً إلى الإنتاج. محلياً كان يعمل لأن شخصاً ما أضافه على جهازه ونسي تحديث الإعدادات المركزية.

هذه ليست حالة حادة (edge case). إنها واحدة من أكثر أنماط الفشل إرهاقاً في التطبيقات الحديثة. تجنبها رخيص، وإصلاحها تحت النار مكلف.


المشكلة الحقيقية

هذا ليس عن لوم الناس. إنه عن الأنظمة التي تسمح لأخطاء الإعدادات الكامنة بالعيش طويلاً جداً.

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

نحن بحاجة لمعاملة الإعدادات كدرجة أولى. افشل مبكراً. افشل بوضوح. هذه هي الهندسة المسؤولة.


كيف يجب أن يتصرف

على الأقل، قاعدتان يجب فرضهما في كل خدمة:

  1. يرفض التطبيق البدء إذا كانت متغيرات البيئة المطلوبة مفقودة. لا تنتظر وقت التشغيل للكشف عن المشكلة. تحطم (Crash) عند بدء التشغيل واجعل المشكلة فورية وواضحة.

  2. الأخطاء يجب أن تكون صريحة ومقروءة للبشر. إذا كان API_KEY مفقوداً، يجب أن يقول خطأ وقت التشغيل ذلك بالضبط. لا تخمين. لا استحضار أرواح في السجلات.

هاتان القاعدتان تزيلان معظم العبء العاطفي في الاستجابة للحوادث. توقفان المطاردة البطيئة وتعيدان توجيه الجهد حيث يهم: إصلاح عقد الإعدادات بين النشر ووقت التشغيل.


التحقق من متغيرات البيئة

عامل متغيرات البيئة كمدخلات. تحقق منها. في عالم JavaScript هذا سهل ومنخفض المخاطر.

أنا عادة أوصي بالتحقق من المخطط (schema validation) باستخدام Zod أو استخدام تجريد صغير مثل @t3-oss/env-nextjs. النمط بسيط:

  • عرف العقد
  • تحقق عند بدء التشغيل أو وقت البناء
  • افشل بصوت عالٍ (fail loud) إذا كان هناك خطأ ما

هذا يعطيك أمان النوع (type safety) ويزيل فخ "يعمل على جهازي".

مثال مع Zod

env.ts

import { z } from "zod";

const envSchema = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]),
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
});

export const ENV = envSchema.parse(process.env);

إذا كانت قيمة مطلوبة مفقودة أو مشوهة، تتحطم العملية عند بدء التشغيل مع خطأ واضح. هذا هو بالضبط السلوك الذي تريده.

لماذا أصدر ENV كأحرف كبيرة

أنا أصدر القيم المتحقق منها كثابت بأحرف كبيرة:

export const ENV = ...

الأحرف الكبيرة تشير إلى إعدادات عالمية (global) على مستوى النشر. عندما ترى ENV.DATABASE_URL، تعرف أن هذا ليس مجرد متغير في الذاكرة. إنه جزء من عقد التطبيق مع النشر والـ CI. ذلك العرف الصغير يقلل العبء الذهني في قواعد الكود الكبيرة.


المستوى التالي: مساعدات Next.js و t3 env

بالنسبة لـ Next.js، @t3-oss/env-nextjs لطيف لأنه يفصل إعدادات الخادم والعميل ويدعم التحقق وقت البناء.

src/env.ts

import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const ENV = createEnv({
  server: {
    NODE_ENV: z.enum(["development", "test", "production"]),
    DATABASE_URL: z.string().url(),
    OPEN_AI_API_KEY: z.string().min(1),
  },
  client: {
    NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1),
  },
  runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY,
    NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
  },
});

استيراد ملف env في next.config.ts يجعل سوء الإعداد يفشل أبكر، قبل النشر، وهو أفضل حتى.

import "./src/env";

const nextConfig = {};

export default nextConfig;

.env.example هو عقد

ارفع (Commit) ملف .env.example. هذا الملف ليس مجرد توثيق. إنه عقد بين المطورين، الـ CI، والبنية التحتية للإنتاج. يظهر المفاتيح المطلوبة والشكل الذي يجب أن تكون عليه.

NODE_ENV=production
DATABASE_URL=
OPEN_AI_API_KEY=
NEXT_PUBLIC_PUBLISHABLE_KEY=
PORT=3000

إذا كان متغير مفقوداً من هذا الملف، فهذه إشارة أن شيئاً ما ليس جزءاً من العقد.


العائد (The payoff)

متغيرات البيئة غير المفحوصة تسبب هشاشة غير مرئية.

عندما تتحقق مبكراً:

  • DevOps يضيعون وقتاً أقل على خيوط كاذبة
  • المهندسون يخسرون نوماً أقل بسبب أخطاء الإنتاج الغامضة
  • عمليات النشر تصبح أقل توتراً
  • "يعمل على جهازي" يتوقف عن كونه نتيجة مقبولة

التحقق من env ليس اختيارياً. إنه جزء من حزام الأمان الذي تضعه قبل تشغيل السيارة.


قائمة مرجعية عملية

لا تحتاج لنظام معقد للقيام بهذا بشكل صحيح. ابدأ بخطوات صغيرة:

  1. أضف مخططاً وتحقق عند بدء التشغيل.
  2. أصدر الإعدادات المتحقق منها كثابت عالمي مثل ENV.
  3. ارفع .env.example وعامله كالعقد القانوني.
  4. إذا أمكن، استورد التحقق من env في خطوة البناء لتفشل بسرعة أثناء الـ CI.

هذه التحركات منخفضة التكلفة وعالية العائد على الاستثمار (ROI).


فكرة أخيرة

إذا كان تطبيقك لا يزال يعتمد على قيم process.env غير المفحوصة، فأنت لا تسأل ما إذا كان الإنتاج سينكسر. أنت تسأل متى سينكسر.

افشل مبكراً. افشل بوضوح. اجعل تجاهل الإعدادات مستحيلاً.

مقالات أخرى

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

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

تغير تصحيح الأخطاء (Debugging) عندما توقفت عن قراءة السجلات يدوياً وبدأت في استخدام وكلاء الذكاء الاصطناعي لربط الأخطاء عبر بيانات المراقبة - وصول أسرع للسبب الجذري، وطرق مسدودة أقل.

السرعة لم تكن أبداً الجزء الصعب في CI/CD

السرعة لم تكن أبداً الجزء الصعب في CI/CD

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