تصميم API لأنظمة تعيش أطول من فريقك
كيف تصمم APIs تتحمل تغيير الفرق. Protobuf كمصدر حقيقة للسكيما، tRPC لستاكات TypeScript، GraphQL في الإنتاج، إدارة التغييرات الكاسرة، وعقود الأخطاء.
الـ API اللي تعيش أطول من اللي بناها
كل API تبدأ بفريق واحد، client واحد، وحالة استخدام وحدة. بعد سنتين، 5 فرق تعتمد عليها، 3 شركاء خارجيين يتكاملون معها، والمطورين الأصليين مشوا. قرارات تصميم الـ API صارت دائمة.
بنينا APIs بثلاث استراتيجيات مختلفة: Protobuf كمصدر حقيقة للسكيما للأنظمة متعددة اللغات، tRPC لستاكات TypeScript الكاملة، وGraphQL لاحتياجات العملاء المعقدة. كل وحدة كانت الاختيار الصح لسياقها. هالمقال يغطي متى تستخدم كل وحدة وكيف تصمم APIs تتحمل تغيير الفرق.
كيف هالـ APIs تتناسب مع تصميم الأنظمة الأوسع، شوف دليل بنية الأنظمة ودليل هندسة البرمجيات.
Protobuf: مصدر الحقيقة للسكيما في الأنظمة متعددة اللغات
لما نظامك يشمل عدة لغات (TypeScript API، Go search service، Python ML pipeline)، تحتاج صيغة سكيما تولد أنواع لكلهم. Protobuf يسوي هالشي.
// proto/product.proto
syntax = "proto3";
package commerce.v1;
message Product {
string id = 1;
string name = 2;
string description = 3;
int32 price_cents = 4;
string currency = 5;
repeated string category_ids = 6;
ProductStatus status = 7;
google.protobuf.Timestamp created_at = 8;
}
enum ProductStatus {
PRODUCT_STATUS_UNSPECIFIED = 0;
PRODUCT_STATUS_DRAFT = 1;
PRODUCT_STATUS_ACTIVE = 2;
PRODUCT_STATUS_ARCHIVED = 3;
}
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product);
rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
rpc CreateProduct(CreateProductRequest) returns (Product);
}
ليش الـ Tags مهمة للتطوير
حقول Protobuf تتعرف بأرقام الـ Tags (1, 2, 3...)، مش بالأسماء. هذا يعني تقدر تغير اسم حقل بدون ما تكسر الـ clients الموجودين. تقدر تضيف حقول جديدة (أرقام tags جديدة) بدون ما تكسر الـ clients القديمين. وتقدر تعلم حقول كـ deprecated بدون ما تحذفها.
// تطوير آمن: إضافة حقل
message Product {
string id = 1;
string name = 2;
string description = 3;
int32 price_cents = 4;
string currency = 5;
repeated string category_ids = 6;
ProductStatus status = 7;
google.protobuf.Timestamp created_at = 8;
string sku = 9; // جديد: تمت إضافته بدون كسر الـ clients الموجودين
string brand = 10; // جديد: الـ clients القديمين يتجاهلون الحقول غير المعروفة
}
قواعد التطوير الآمن لـ Protobuf:
- لا تعيد استخدام رقم tag أبداً (حتى بعد حذف حقل)
- لا تغير نوع حقل أبداً
- أضف حقول جديدة بأرقام tags جديدة
- علم الحقول المهملة بـ
reservedعشان تمنع إعادة الاستخدام بالغلط
توليد الكود
# توليد TypeScript وGo وPython من نفس ملف .proto
protoc --ts_out=./gen/ts --go_out=./gen/go --python_out=./gen/py proto/*.proto
ملف سكيما واحد يولد clients وservers آمنة الأنواع بكل لغة. تغييرات السكيما هي pull request. توليد الكود خطوة CI. عدم تطابق الأنواع أخطاء compile، مش أخطاء runtime.
tRPC: لما كل الستاك TypeScript
إذا سيرفر الـ API وكل الـ clients عندك TypeScript، tRPC يلغي طبقة السكيما بالكامل. الأنواع تتدفق من السيرفر للـ client وقت الـ compile. بدون توليد كود، بدون ملفات سكيما، بدون OpenAPI spec.
// السيرفر: تعريف router مع procedures مُنمَّطة
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';
export const productRouter = router({
list: publicProcedure
.input(z.object({
cursor: z.string().optional(),
limit: z.number().min(1).max(100).default(20),
category: z.string().optional(),
}))
.query(async ({ input, ctx }) => {
return ctx.productService.list(input);
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(1).max(200),
price: z.number().positive(),
description: z.string().optional(),
}))
.mutation(async ({ input, ctx }) => {
return ctx.productService.create(ctx.tenantId, input);
}),
});
// العميل: استدلال أنواع كامل، بدون توليد كود
const products = await trpc.product.list.query({
limit: 10,
category: 'electronics',
});
// products منمط بالكامل: { items: Product[], nextCursor?: string }
متى tRPC يشتغل أحسن
- كل الـ clients هم TypeScript (تطبيق ويب، React Native، Node.js services)
- الـ API داخلي (مش مكشوف لأطراف ثالثة)
- السرعة في التكرار أهم من عقود API رسمية
- الفريق صغير ويشتغل سوا (تغييرات السيرفر والـ client تصير مع بعض)
متى tRPC ما يشتغل
- شركاء خارجيين يحتاجون يتكاملون (يحتاجون OpenAPI/Swagger docs)
- الـ clients بلغات ثانية (mobile native، Go، Python)
- تحتاج إصدار API للتوافق الخلفي
- الـ API منتج عام
نستخدم tRPC مع Hono لـ APIs TypeScript الداخلية. شوف دليل TypeScript backends للمقارنة الكاملة.
GraphQL: للاحتياجات المعقدة للعملاء
GraphQL يتألق لما الـ clients يحتاجون queries مرنة: صفحات مختلفة تحتاج مجموعات بيانات مختلفة، الموبايل يحتاج بيانات أقل من الويب، وفريق العميل يبي يكرر على الـ queries بدون تغييرات بالباك إند.
# العميل يطلب بالضبط اللي يحتاجه
query ProductPage($slug: String!) {
product(slug: $slug) {
id
name
price
images { url alt }
reviews(first: 5, status: APPROVED) {
items { rating body customerName }
totalItems
}
relatedProducts(first: 4) {
id name price images { url }
}
}
}
أنماط GraphQL في الإنتاج
Persisted Queries: لا تسمح بـ queries عشوائية في الإنتاج. الـ clients يرسلون hash للـ query، والسيرفر يبحث عن الـ query من registry. هذا يمنع إساءة استخدام الـ queries ويمكن التخزين المؤقت.
Depth Limiting: بدون حدود، client يقدر يرسل query متداخلة بعمق تربط كل جدول بقاعدة بياناتك.
apiOptions: {
middleware: [depthLimit(10)],
shopApiPlayground: false, // تعطيل في الإنتاج
}
منع N+1: استخدم DataLoader لتجميع استعلامات قاعدة البيانات. بدونه، query تجيب 20 منتج مع تصنيفاتهم تسوي 20 استعلام تصنيف منفصل.
// DataLoader يجمع N استعلام فردي في 1
const categoryLoader = new DataLoader(async (ids: string[]) => {
const categories = await categoryRepo.findByIds(ids);
return ids.map(id => categories.find(c => c.id === id));
});
تحليل التعقيد: خصص تكلفة لكل حقل. ارفض الـ queries اللي تتجاوز ميزانية التعقيد.
كيف نستخدم GraphQL في Vendure commerce، شوف دليل Vendure للإنتاج.
التغييرات الكاسرة: التطوير بالـ Tags مقابل إصدار URL
| الاستراتيجية | كيف تشتغل | الأفضل لـ |
|---|---|---|
| بالـ Tags (Protobuf) | إضافة حقول بـ tags جديدة، الـ clients القديمين يتجاهلونها | متعدد اللغات، gRPC |
إصدار URL (/v1/, /v2/) | endpoints منفصلة لكل إصدار | REST APIs مع مستهلكين خارجيين |
إصدار Header (Accept: application/vnd.api.v2+json) | نفس الـ URL، الإصدار بالـ header | REST APIs اللي تبي URLs نظيفة |
| GraphQL (بدون إصدار) | إضافة حقول، تعليم القديمين بـ @deprecated | GraphQL APIs |
| tRPC (بدون إصدار) | الأنواع تتطور مع الكود | APIs TypeScript داخلية |
لأغلب الـ APIs الداخلية، تجنب إصدار URL. يضاعف سطح الصيانة. أضف حقول جديدة، علم القديمين كـ deprecated، احذفهم بعد ما كل الـ clients يهاجرون.
للـ APIs الخارجية (تكاملات أطراف ثالثة، APIs عامة)، إصدار URL أأمن لأنك ما تقدر تتحكم متى الـ clients يحدثون.
عقود الأخطاء
الأخطاء جزء من عقد الـ API. الـ clients يحتاجون أخطاء منظمة يقدرون يتصرفون عليها، مش رسائل نصية يحللونها بـ regex.
// استجابة خطأ منظمة
interface ApiError {
code: string; // مقروء آلياً: "PRODUCT_NOT_FOUND", "INSUFFICIENT_STOCK"
message: string; // مقروء بشرياً: "Product with ID xyz not found"
details?: object; // سياق إضافي للتصحيح
requestId: string; // معرف ربط للدعم
}
// أمثلة استجابات
// 404
{
"code": "PRODUCT_NOT_FOUND",
"message": "Product with ID prod_123 not found",
"requestId": "req_abc456"
}
// 409
{
"code": "INSUFFICIENT_STOCK",
"message": "Only 2 units available, requested 5",
"details": { "available": 2, "requested": 5 },
"requestId": "req_def789"
}
// 422
{
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": {
"fields": [
{ "field": "price", "error": "must be positive" },
{ "field": "name", "error": "must not be empty" }
]
},
"requestId": "req_ghi012"
}
حقل code هو العقد. الـ clients يسوون switch عليه. الـ message للبشر. الـ details توفر سياق. الـ requestId يمكن الدعم من إيجاد الطلب بالضبط بالـ logs.
حوكمة API
لما الـ API تكبر، تحتاج حوكمة عشان تمنع عدم الاتساق:
| القاعدة | ليش |
|---|---|
| كل الـ endpoints تتطلب مصادقة | بدون endpoints عامة بالغلط |
| كل الـ mutations تتطلب idempotency keys | إعادة المحاولة بالشبكة ما تسوي نسخ مكررة |
كل الاستجابات تتضمن requestId | كل طلب قابل للتتبع |
| كل الأخطاء تستخدم الصيغة المنظمة | الـ clients يقدرون يتعاملون مع الأخطاء برمجياً |
| كل endpoints القوائم تدعم pagination | بدون queries غير محدودة |
| التغييرات الكاسرة تتطلب مراجعة | شخص واحد ما يقدر يكسر كل المستهلكين |
| الـ Deprecations تتطلب جدول هجرة | "Deprecated" بدون موعد يعني "ما راح يتحذف أبداً" |
أخطاء شائعة
-
REST مقابل GraphQL كهوية. اختار بناءً على احتياجات العميل، مش تفضيل الفريق. REST لـ CRUD البسيط مع مستهلكين خارجيين. GraphQL لـ queries مرنة مع فرق الفرونت إند. tRPC لـ TypeScript الداخلي. Protobuf للمتعدد اللغات.
-
بدون عقد أخطاء. رسائل خطأ نصية تتغير مع كل إصدار تكسر كل client يحاول يتعامل معها.
-
endpoints قوائم غير محدودة. endpoint يرجع كل الـ 50,000 منتج باستجابة وحدة يكرش الـ clients ويحمل قاعدة بياناتك فوق طاقتها. Pagination إلزامي.
-
إصدار APIs داخلية. إذا تتحكم بكل الـ clients، طور الـ API بمكانها. الإصدار يضاعف الصيانة بدون فائدة.
-
بدون جدول deprecation. تعليم حقل كـ
@deprecatedبدون تاريخ حذف يعني يبقى للأبد. حدد تاريخ، بلغ المستهلكين، احذفه. -
GraphQL بدون depth limits. API GraphQL غير مقيدة هي ناقل denial-of-service. حدد العمق، التعقيد، وتكلفة الـ query.
أهم النقاط
-
Protobuf للأنظمة متعددة اللغات. تطوير بالـ tags، توليد كود، كفاءة wire format. السكيما هي العقد.
-
tRPC لستاكات TypeScript الكاملة. صفر توليد كود، استدلال أنواع كامل، أسرع سرعة تكرار. بس يشتغل فقط لما كل الـ clients هم TypeScript.
-
GraphQL لاحتياجات العملاء المرنة. الـ clients يطلبون بالضبط اللي يحتاجونه. بس أضف depth limits وpersisted queries وتحليل التعقيد للإنتاج.
-
عقود الأخطاء بنفس أهمية عقود النجاح. أكواد مقروءة آلياً، رسائل مقروءة بشرياً، معرفات ربط، وتفاصيل منظمة.
-
طور بمكانها للـ APIs الداخلية، أصدر للخارجية. إضافة حقول آمنة. حذف حقول يتطلب جدول هجرة. إصدار URL هو الملاذ الأخير.
نصمم APIs كجزء من ممارسة تطوير الويب والبرمجيات المخصصة. إذا تحتاج مساعدة ببنية الـ API، تواصل مع فريقنا أو اطلب عرض سعر.
المواضيع المغطاة
أدلة ذات صلة
بنية الأنظمة والقابلية للتوسع
دليل شامل لتصميم أنظمة تدوم. تعلم عن أنماط البنية، تصميم API، أنظمة المصادقة، البنية التحتية للوقت الحقيقي، والبناء للتوسع بدون إفراط في الهندسة.
اقرأ الدليلالدليل الشامل لأنظمة الذكاء الاصطناعي الوكيلي
دليل تقني لأنظمة الذكاء الاصطناعي الوكيلي في بيئات الأعمال. تعرف على البنية والقدرات والتطبيقات العملية للوكلاء المستقلين.
اقرأ الدليلالتجارة الوكيلية: كيف تخلي وكلاء الذكاء الاصطناعي يشترون بأمان
كيف تصمم تجارة وكيلية محكومة. محركات السياسات، بوابات الموافقة البشرية، إيصالات HMAC، الـ idempotency، عزل المستأجرين، وبروتوكول الدفع الوكيلي الكامل.
اقرأ الدليلجاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟
فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.
ابدأ محادثة