عندما يؤدي تغيير واجهة برمجة التطبيقات إلى كسر ما يعتمد عليه المستخدمون دون علمهم

تنشر إصدارًا جديدًا من خدمة الواجهة الخلفية الخاصة بك. خط أنابيب CI/CD أخضر. السجلات نظيفة. بعد خمس دقائق، تبدأ دردشة فريقك في الاشتعال: تطبيق الجوال يعرض شاشات فارغة، وواجهة الويب الأمامية تظهر بيانات مفقودة، وخدمة فريق آخر ترمي أخطاء 500 في كل طلب.

ماذا حدث؟ لقد غيرت اسم حقل من nama إلى full_name في استجابة API. بدا الأمر غير ضار. لكن تطبيق الجوال كان يحلل ذلك الحقل بالاسم، وواجهة الويب كانت تعرضه مباشرة، وخدمة الفريق الآخر كانت تربطه بعمود قاعدة البيانات الخاص بهم. لم يتلق أي منهم الإشعار لأنك لم تكن تعلم بوجودهم.

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

المشكلة التي لا تراها

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

هنا يصبح التوافق العكسي ضرورة عملية، وليس مثالية نظرية. التوافق العكسي يعني أن الإصدار الجديد من API الخاص بك لا يزال قادرًا على خدمة الطلبات باستخدام نفس تنسيق الإصدار القديم. إذا كان نقطة النهاية /users الخاصة بك تعيد حقلًا يسمى nama، فلا يمكن للإصدار الجديد إعادة تسميته فجأة إلى full_name دون فترة انتقالية. إذا كان معامل page يقبل عددًا صحيحًا، فلا يمكن للإصدار الجديد أن يتطلب فجأة سلسلة نصية. هذه التغييرات تكسر العقد، والعقود المكسورة تعني مستهلكين معطلين.

ما يعتبر تغييرًا جذريًا

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

لكن هناك تغييرات أكثر دقة. تغيير ترتيب الحقول في استجابة JSON يمكن أن يكسر الكود الذي يعتمد على فهرسة المصفوفات. إضافة رأس مطلوب جديد يمكن أن يكسر العملاء الذين لا يرسلونه. حتى تغيير رمز حالة HTTP لحالة خطأ معينة يمكن أن يكسر المنطق الذي يتحقق من قيم الحالة المحددة.

تأمل هذا المثال البسيط. كان API الخاص بك يعيد كائن مستخدم مثل هذا:

{
  "id": 42,
  "nama": "Ani Wijaya",
  "email": "ani@example.com"
}

بعد إعادة التسمية "للتنظيف"، يعيد:

{
  "id": 42,
  "full_name": "Ani Wijaya",
  "email": "ani@example.com"
}

بالنسبة لك، إنه اسم أفضل. بالنسبة لتطبيق الجوال الذي يحلل response["nama"]، إنه undefined. بالنسبة للواجهة الأمامية التي تعرض user.nama، إنه فارغ. بالنسبة لخدمة الشريك التي تربط nama بعمود قاعدة البيانات الخاص بها، إنه null صامت. الحقل لا يزال موجودًا في المعنى، لكن العقد مكسور.

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

اكتشاف التغييرات الجذرية قبل وصولها إلى الإنتاج

النهج الأكثر أمانًا هو اكتشاف التغييرات الجذرية تلقائيًا في خط أنابيب CI الخاص بك. لا تريد الاعتماد على المراجعة اليدوية أو الأمل في أن يتذكر شخص ما التحقق. هناك أدوات مصممة خصيصًا لهذا الغرض.

إذا كنت تستخدم تنسيق مواصفات API مثل OpenAPI، فإن أدوات مثل OpenAPI Diff أو Spectral يمكنها مقارنة الإصدارات القديمة والجديدة من المواصفات. تحدد بالضبط ما الذي تغير وما إذا كان تغييرًا جذريًا. إذا كان إطار العمل الخاص بك يولد المواصفات تلقائيًا، مثل FastAPI أو SpringDoc أو ASP.NET Core مع Swashbuckle، يمكنك تشغيل هذه المقارنة على كل طلب سحب.

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

هذا يحول المحادثة من "آمل ألا يكسر هذا أي شيء" إلى "هذا هو بالضبط ما تغير وما إذا كان يكسر العقد." يحول المخاطر غير المرئية إلى نقطة قرار مرئية.

عندما لا يمكنك تجنب التغييرات الجذرية

لا يمكن تجنب جميع التغييرات الجذرية. أحيانًا تجبر متطلبات العمل على إعادة التصميم. أحيانًا تحتاج البنية إلى التطور. أحيانًا يكون التصميم القديم خاطئًا ببساطة ويحتاج إلى الاستبدال.

عندما يجب عليك إجراء تغيير جذري، فإن إصدار API هو النهج القياسي. الفكرة واضحة: تحتفظ بإصدارات متعددة من API الخاص بك في وقت واحد. يستمر المستهلكون القدامى في استخدام الإصدار القديم بينما يتبنى المستهلكون الجدد الإصدار الجديد. تمنح الجميع وقتًا للترحيل.

هناك عدة طرق لتنفيذ الإصدار، لكل منها مقايضات.

إصدار URL هو النهج الأكثر شيوعًا. تضع الإصدار في المسار: /v1/users و /v2/users. من السهل فهمه، وسهل التوجيه، وسهل الاختبار. الجانب السلبي هو أن عناوين URL تصبح أطول، وتحتفظ بمسارات كود منفصلة لكل إصدار.

إصدار الرأس يحافظ على نظافة URL ولكنه يتطلب من المستهلكين تعيين رأس مخصص، مثل Accept: application/vnd.myapi.v1+json. هذا أكثر توافقًا مع REST من الناحية النظرية، لكنه يضيف تعقيدًا للمستهلكين الذين يحتاجون إلى تكوين الرؤوس بشكل صحيح.

إصدار معامل الاستعلام يستخدم شيئًا مثل ?version=1. إنه بسيط ولكنه أقل شيوعًا لأنه يمكن أن يزعج سلاسل الاستعلام ويصعب تخزينه مؤقتًا بشكل صحيح.

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

المسار الأفضل: التصميم للتطور

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

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

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

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

قائمة مراجعة عملية لتغييرات API

قبل دمج طلب السحب هذا، راجع هذه النقاط:

  • هل قمت بإزالة أو إعادة تسمية أي حقل أو معامل أو نقطة نهاية موجودة؟
  • هل قمت بتغيير نوع البيانات لأي حقل أو معامل موجود؟
  • هل أضفت حقلًا مطلوبًا إلى نص الطلب؟
  • هل قمت بتغيير تنسيق استجابات الأخطاء؟
  • هل قمت بتغيير رموز حالة HTTP للسيناريوهات الموجودة؟
  • هل قمت بتشغيل مقارنة تلقائية ضد مواصفات API السابقة؟

إذا أجبت بنعم على أي من الأسئلة الخمسة الأولى، فلديك تغيير جذري. قرر ما إذا كنت ستؤجله، أو تصدره، أو تقوم بتوصيله بوضوح إلى جميع المستهلكين المعروفين.

الخلاصة

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

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