الموجّه هو السجل
ليس لدى Hanzo Cloud ملف OpenAPI مُودَع في المستودع. جدول المسارات الحي هو المصدر، ويُسقط بثلاث طرق، ويفشل اختبار التقابل البناء إذا انحرف أي إسقاط. 1,362 عملية عبر 963 مسارًا، تُولَّد لكل عملية عند وقت الطلب.

في هذه الصفحة
لا يوجد openapi.yaml في مستودع Hanzo Cloud. لا يوجد سجل ثانٍ يسرد نقاط النهاية، ولا ملف تعليقات، ولا خطوة مولّد في CI تكتب مواصفة إلى القرص. إن أردت أن تعرف ما هي واجهة API، تسأل عملية قيد التشغيل، فتخبرك بقراءة موجّهها نفسه.
يدور هذا المقال حول السبب الذي جعل هذا الترتيب الوحيد الذي يصمد، وحول أجزائه التي لا تعمل، والتي يتبيّن أنها النصف الأكثر فائدة.
جدول واحد، ثلاثة إسقاطات
يؤلّف serve.go جدول مسارات واحدًا. ثم تقرأه ثلاثة أشياء، كلها بعد MountAll حتى يرى كل منها جدولًا مكتملًا:
/zapيعيد تشغيل معالجات/v1عبر ناقل ZAP.- وحدة التحكم تعرضها.
GET /v1/openapi.jsonيصفها.
لا يحتفظ أي من الثلاثة بنسخة من شيء. هذا هو قيد التصميم كله، وكل شيء آخر يتبع منه. إسقاط يحتفظ بقائمته الخاصة من نقاط النهاية يكون مصدر حقيقة ثانٍ، ومصدر الحقيقة الثاني ينحرف، لا أحيانًا، بل دائمًا، وفق جدول يحدده كم مرة ينسى أحدهم.
إسقاط الوصف هو الذي يسأل الناس عنه، إذن: يستدعي openapi.Live(app) الدالة app.Fiber().GetRoutes(true)، وكل دالة أخرى في openapi/ دالة نقية من []Route الذي يعود. الوثيقة لا تُبنى من الموجّه. الوثيقة هي الموجّه، في شكل مختلف.
لماذا قراءة الموجّه الحي هي المصدر الكلي الوحيد
البديل الواضح هو مسح المصدر. لا ينجح، والسبب يستحق أن يُقال بدقة، لأنه السبب الذي يجعل كثيرًا من أدوات API تُقصّر في التقارير بهدوء.
يُسجَّل POST /v1/kms/auth/login على هذا النحو:
Group("/v1/kms/auth").Post("/login")
لا يجد أي grep ذلك المسار. السلسلة /v1/kms/auth/login غير موجودة في قاعدة الشفرة. توجد فقط في الموجّه المُجمَّع، في وقت التشغيل، بعد أن يضم الإطار بادئة المجموعة والورقة.
ثم يسوء الأمر، على نحو هو في الحقيقة صحيح. مجموعة المسارات دالة لتهيئة النشر، cfg.Enabled، إضافة إلى بوابات داخلية مثل if kc != nil الخاصة بـ KMS. نشر لا يركّب الإدارة لا يقدّم مسارات الإدارة. لذا فإن المواصفة الصادقة لذلك النشر لا تعلن عنها.
لهذا تُولَّد الوثيقة لكل عملية، عند وقت الطلب، بدل أن تُبنى مرة واحدة في CI. مواصفة تُبنى في CI تصف نشرًا أقصى افتراضيًا قد لا يكون الذي تخاطبه. مواصفة تُقرأ من الموجّه الحي تصف العملية التي أجابت عليك.
الاختبار الذي يجعل ذلك صحيحًا
معمارية لا تستطيع فيها المواصفة أن تنحرف ادعاء، والادعاءات تحتاج إلى بوابة. بوابتنا تقابل، في cmd/cloud/openapi_test.go:
func TestSpecDoesNotDriftFromLiveRouter(t *testing.T) {
يركّب كل نظام فرعي في apps.Wire()، ويولّد الوثيقة، ويؤكد التخطيط في الاتجاهين: كل مسار حي يظهر كعملية، وكل عملية يستند إليها مسار حي. ليس مجموعة جزئية في أي اتجاه.
شغّله اليوم:
bijection holds over 1362 operations / 963 paths / 138 products
إنه الاختبار الوحيد في المستودع الذي يعني فشله أن الوثيقة المنشورة تكذب. كل ما عدا ذلك ينكسر يجعل المنتج خاطئًا؛ هذا يجعل العقد خاطئًا، وهو أسوأ، لأن المكاملين بنوا عليه بالفعل.
تحرّك العدد ليس مشكلة، فهو يتحرك كلما أُضيف نظام فرعي. العدد الذي يهم هو الفرق بين الجانبين، وذلك العدد 0 وإلا يفشل البناء.
محور المنتج ميكانيكي
أول جزء من المسار بعد /v1/ هو المنتج. يقرأه openapi.Product ويوسم كل عملية به، وهو ما يتيح لواجهة سطر الأوامر بناء hanzo <product> <resource> <verb> من دون أن يمارس أحد حكمًا حول أين ينبغي أن يعيش الأمر.
هو عمدًا ليس اسم النظام الفرعي. يقدّم clients/billing أيضًا /v1/finance/*، وعنوان URL هو ما يراه المستدعي، لذا يفوز عنوان URL. تسمية المحور باسم الحزمة الداخلية كانت ستجعل واجهة سطر الأوامر أثرًا مُنتقى، شيئًا فيه آراء وعلى أحد صيانته، بدل أن يكون إسقاطًا.
ما لا يستطيع الموجّه أن يخبرك به
هذا هو الحد، ولا ننوي الالتفاف حوله.
الطريقة والمسار ومعاملات المسار والمنتج قابلة للاشتقاق من الموجّه. مخططات الطلب والاستجابة، ومعاملات الاستعلام والترويسة، ورموز الحالة، والمصادقة ليست كذلك. يحتفظ الموجّه بـ func(*zip.Ctx) error. نوع الطلب متغير محلي داخل جسم المعالج:
var req secretPutRequest
json.Unmarshal(ctx.Body(), &req)
لا تستطيع Go أن تعكس من قيمة دالة إلى جسمها. لا يوجد إصلاح ذكي هنا، ومحاولة ذلك في المولّد تعني التخمين، وهكذا تكتسب المواصفة مدخلات واثقة وخاطئة.
المسار الوحيد إلى المخططات هو عملية مُنمّطة:
zip.Get[In, Out](...)
يقع In و Out على التسجيل، لذا يتاح المخطط من دون قراءة ذهن أحد. ولأنهما على التسجيل، يُنتج المدخل نفسه أداة MCP أيضًا، فـ zip/openapi.go و zip/mcp.go قارئين لسجل واحد. ذلك هو الإسقاط الرابع، وقد وصل من دون آلية رابعة.
الهجرة تزايدية بالبناء: GetRoutes() مجموعة شاملة لـ app.ops، لذا تحويل معالج إلى عملية مُنمّطة يضيف المخطط من دون تغيير أي شيء أعلى منه.
المسارات التي لا يمكن تنميطها، ولماذا
حُوِّل clients/integrations إلى الحد الذي يسمح به السلك: 20 من 41 مسارًا عمليات مُنمّطة. تبقى الـ 21 الأخرى معالجات خام، وكل واحد مدرج في خريطة rawRoutes في ops_projection_test.go مع سبب عدم قدرته على الانتقال:
- 13 إعادة توجيه للمتصفح. يجيب رد نداء OAuth وكل ساق رابط بـ
302. تعيد العملية المُنمّطة قيمة، لا ترويسةLocationوحالة في نطاق 300. - 6 خطافات ويب. HMAC لدى Slack و GitHub، و Ed25519 لدى Discord، و JWT في Teams Bot Framework، وترويسة الرمز السري لدى Telegram. يصادق كل واحد على توقيع فوق بايتات الطلب الخام. تتلقى العملية المُنمّطة
Inمفكوكًا ولا ترى أبدًا البايتات التي حُسب التوقيع عليها، لذا تنميطها يكسر مصادقتها. - 2 منشئي
202 Accepted. يعطيناcloud.Createdدرزة 201. لا توجد درزة 202. مساران يحتاجان واحدة، لذا مساران ينتظران.
نرى أن تلك القائمة أثمن أثر في النظام كله. إنها جرد مُعدَّد ومثبَّت باختبار للمكان الذي يتوقف فيه التجريد بالضبط، مع سبب لكل مدخل يستطيع المراجع أن يجادل فيه. نمط الفشل الذي تمنعه هو أن ينزلق مسار بصمت عائدًا إلى معالج خام ويختفي من الوثيقة ومجموعة أدوات MCP وواجهة سطر الأوامر دفعة واحدة، من دون أن يُرى، لأن لا شيء كان يؤكد أنه كان هناك.
المعمارية ليست الأجزاء التي تتعمم. إنها الأجزاء التي تتعمم إضافة إلى حساب صادق ومُنفَّذ للأجزاء التي لا تتعمم.
ماذا يشتري هذا
يقرأ مستدعٍ يتكامل مع Hanzo Cloud وثيقة أنتجتها العملية التي تجيب عن طلباته، في النشر الذي يخاطبه فعلًا، قبل بضع ميلي ثوانٍ. يصل وكيل يستدعي عبر MCP إلى المعالج نفسه، مع فحص الاستئجار نفسه، كمستدعٍ لـ REST. يحصل استدعاء /mcp المجهول على 403 نفسه الذي يعطيه REST، لأنه مسار الشفرة نفسه واختبار يثبّته.
لم يتطلب أي من ذلك أن يتذكر أحد إعادة توليد شيء.
الصورة العامة: إن وجب أن يتفق أثران، فلا تولّد أحدهما من الآخر وتأمل. اجعل أحدهما منظرًا للآخر، واكتب الاختبار الذي يفشل حين لا يكونان كذلك.
اقرأ المزيد

Cloud-Native Commerce: Why Architecture Matters
How we built Crowdstart as a cloud-native platform from day one, and why this matters for commerce.

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

Introducing Enso — one API, routed to the best frontier intelligence
Enso is Hanzo's learned router: no single frontier model leads every benchmark, so Enso routes each request to the model most likely to win it — and learns from your feedback as it goes. Microsecond routing on a CPU, a transparent per-request meter, and a 1% fee that pays for itself. Here is how it works, the measured results, and the cost-transparency angle a monolith cannot offer.