انتقل إلى المحتوى

الترحيل والتبنّي ​

القاعدة الأكثر أماناً للترحيل هي: أبقِ مستوى البيانات كما هو، وتبنَّ الحدّ الذي تستطيع شرحه.

احتفظ بإطار Web أو العامل أو المجدول أو وسيط الرسائل أو المسجّل أو مزوّد القياس الموجود لديك. أضف عقداً صريحاً من go-like حول مشكلة حقيقية في دورة الحياة أو في استدعاء الخدمات. تحقّق من هذا الحدّ قبل إضافة مزوّد آخر.

ترحيل على مراحل ​

  1. أبقِ عملية الإقلاع الحالية وشيفرة المسارات/مستوى البيانات كما هي.
  2. حدّد مالكاً واحداً: مستمعاً، أو عاملاً، أو مجدولاً، أو اشتراكاً في وسيط رسائل، أو وجهة للسجلات، أو مزوّد قياس.
  3. أضف محوّلاً بنيوياً لـ Server أو استخدم محوّلاً موجوداً في go-like. عرّف القبول، والإيقاف، والمهلة، ومراقبة الحالة النهائية.
  4. أضف @go-like/context عند حدود الإلغاء أو الموعد النهائي الفعلية. مرّره بوصفه الوسيط الأول للعملية.
  5. أضف فحوص liveness وreadiness باستخدام @go-like/health و@go-like/web/health.
  6. أضف استدعاءً داخلياً أحادياً typed باستخدام @go-like/transport-memory في الاختبارات.
  7. انقل هذا الاستدعاء إلى @go-like/transport-http أو @go-like/transport-http/node فقط عندما تحتاج إلى وسيلة نقل فعلية أو مضيف Node أصلي.
  8. أضف Registry أو Config أو Store أو Cache أو Broker أو السجلات أو المقاييس أو التتبّع، قدرة واحدة في كل مرة.
  9. سجّل المزوّد وبيئة التشغيل والمالك ومسار الدليل لكل حدّ جديد.

لا تبدأ بإعادة كتابة الخدمة كلها. فائدة العقود الصغيرة هي أن تظل وحدة الترحيل صغيرة.

مصفوفة ترحيل الأطر ​

النظام الموجودما يبقى أصلياًما يُعتمد أولاًالحدّ الحالي
NestJSModules, controllers, decorators, DI, interceptors, pipes, adapterServer بنيوي مخصص حول التطبيق الموجود، أو حدّ Client/Server داخلي منفصللا يوجد في هذا المستودع محوّل NestJS لـ go-like ولا تكامل DI تلقائي
FastifyRoutes, plugins, hooks, request/reply, native listenerغلاف دورة حياة مخصص، أو جسر Fetch منفّذ صراحةًلا يوجد تحويل مثبت حالياً من Fastify request/reply إلى go-like Handler
HonoRoutes, middleware, sub-apps, app.fetchnewNodeServer(app.fetch, ...) ثم newApp(...)التكامل المباشر مع Fetch موضّح في examples/hono
ElysiaRoute tree, schema, decorators, derives, hooks, Bun/Web Standard behaviorapp.fetch الأصلي مع مضيف/دورة حياة Core عند الحاجةأبقِ دلالات .listen() الخاصة بـ Bun؛ لا تعدّها API من go-like عابرة لكل بيئات التشغيل
H3H3 router and native handler conversionمسار Fetch Handler الموجود في مثال H3 الحاليapp.fetch في H3 2.x هو الشكل الموضّح حالياً؛ وتحتاج إرشادات toWebHandler الأقدم إلى مثال مثبت الإصدار
KoaMiddleware and external routerغلاف مخصص للمالك، أو استدعاء خدمة داخليلا تقبل @go-like/web كائن Koa الخاص بـ Node request/reply من دون جسر على مستوى التطبيق
tRPCRouter, procedure middleware, input/output parsers, adapterدورة حياة Core حول المضيف، أو حدّ نقل داخلي منفصلإن Endpoint في go-like ليس موجّه إجراءات tRPC

مثال Hono ​

هذا هو شكل التكامل الموضّح في المستودع:

ts
import { Hono } from "hono"
import { name, newApp, server } from "@go-like/core"
import { signal } from "@go-like/core/node"
import { newNodeServer, port } from "@go-like/web/node"

const web = new Hono().get("/users/:id", (c) => c.json({ id: c.req.param("id") }))

const app = newApp(name("users"), server(newNodeServer(web.fetch, port(3000))), signal())

await app.run()

يحافظ مثال Hono الحالي على ملكية Hono للمسارات، ويمرّر Fetch Handler الأصلي إلى مضيف Node. ولا يضيف جدول مسارات من go-like ولا حزمة جسر خاصة بـ Hono.

Elysia وH3 ​

طبّق الحدّ نفسه على إطار يعرِض Fetch Handler قياسياً:

text
framework route table
  -> framework native Fetch handler
  -> @go-like/web/node (when using the Node host)
  -> @go-like/core App

تحقّق من محوّل بيئة التشغيل في الإطار قبل استيراد مسار فرعي خاص بـ Node. لا يتطابق سلوك الاستماع في محوّل Bun لـ Elysia مع محوّل Web Standard. كما تحتاج إصدارات H3 وواجهات تحويل المعالجات فيه إلى مثال مثبت الإصدار. لا تستخدم وجود مثال واحد لتعد بدعم كل تركيبة من إصدار الإطار وبيئة التشغيل.

ترحيل خدمة Go ​

إذا كنت قادماً من Go أو Kratos، فهاجر المفاهيم لا الأسماء:

مفهوم Goمفهوم go-likeالاختلاف المهم
context.ContextContext من @go-like/contextdone() هو AbortSignal أو null، وليس قناة Go
Server lifecycleServer البنيوي في Coreقد يستمر start(ctx) طوال عمر الخدمة، ولا يعني الجاهزية
App runnernewApp وApp.run وApp.stopلا يستقبل App.stop() سياق المستدعي ويعيد Promise مشتركة واحدة
RPC client@go-like/clientالاستدعاءات الداخلية هي JSON Fetch أو SSE؛ وإعادة المحاولة اختيارية
Transport@go-like/transportالمزوّدات وترويسات Fetch عقود TypeScript/Web
Registry@go-like/registryيعيد المراقبون لقطات استبدال كاملة
SelectornewRoundRobinSelector, newRandomSelector, newWeightedRoundRobinSelector, newP2CSelector, newEWMASelectorالملاحظات الراجعة متزامنة وتعتمد على السياسة
Protobuf/IDL@go-like/protoc-gen-likeشيفرة Context-first فوق Protobuf-ES
gRPC stream@go-like/transport-grpc-buf/nativegRPC قياسي بالأنماط الأربعة عبر /native

الخطوة التدريجية الأولى هي استدعاء typed إلى عنوان مباشر عبر Memory Transport:

ts
import { newClient, withEndpoint, withTransport } from "@go-like/client"
import { background, type Context } from "@go-like/context"
import { address, newServer, transport as serverTransport } from "@go-like/server"
import { struct } from "@go-like/struct"
import { defineService } from "@go-like/transport"
import { newMemoryTransport } from "@go-like/transport-memory"

const quoteService = defineService("pricing", {
  quote: {
    request: struct.object({ sku: struct.string() }),
    response: struct.object({ amount: struct.number() })
  }
})

const transport = newMemoryTransport()
const server = newServer(serverTransport(transport), address("memory://pricing"))
quoteService.registerHandler(server, {
  quote(_ctx, request) {
    return { amount: request.sku.length }
  }
})

const client = newClient(withTransport(transport), withEndpoint("memory://pricing"))
const caller = quoteService.newClient(client)
const ctx: Context = background()
const result = await caller.quote(ctx, { sku: "a" })
void result

لا تقدّم Discovery أو مزوّد Registry حقيقياً أو HTTP Transport إلا بعد اختبار هذا الحدّ. هكذا تحافظ على عقد المجال أثناء استبدال الوجهة وتركيب الملكية.

اعتماد Kubernetes ​

أبقِ Kubernetes أصلياً:

  • تظل Deployments وServices وDNS وIngress وRBAC وprobes واستراتيجية rollout وHPA وnetwork policy مسؤوليات للمنصة؛
  • يقرأ @go-like/config-kubernetes مفتاحاً واحداً من ConfigMap أو Secret واحد ضمن namespace واحد، عبر قدرة Fetch محقونة؛
  • يستخدم @go-like/registry-kubernetes سجلات EndpointSlice عندما يكون الاكتشاف المباشر حاجة حقيقية؛
  • لا يُعدّ EndpointSlice هو DNS الخاص بـ Kubernetes Service، ولا يوفّر TTL عاماً للتسجيل؛
  • لملكية Pod الاختيارية وإلغاء التسجيل الصريح دلالات فشل مختلفة.

ابدأ بالصحة والإعداد قبل الاختيار المباشر من EndpointSlice. إذا كان للتطبيق بالفعل اسم DNS ثابت لـ Service، فقد يكون withEndpoint(...) مع HTTP Transport أبسط وأكثر صدقاً من إضافة مزوّد Registry.

اعتماد الوسطاء والمهام ​

أبقِ التسوية وسياسة المهام أصليتين:

مستوى البيانات الموجودما يبقى أصلياًما تضيفه go-like من أجله
NATS CoreConnection, subscription, queue group, Msg, drainnewNatsCoreServer وnewNatsCoreBroker ودورة الحياة وحدّ البايتات
NATS JetStreamStream, durable consumer, JsMsg, ack/nak/term, redelivery, DLQnewNatsJetStreamServer وnewNatsJetStreamBroker ودورة الحياة
RabbitMQConnection, topology, confirm policy, channelدورة حياة subscriber المستعار أو المستعيد، والتسوية الأصلية الآمنة أمام تغيّر الأجيال
BullMQQueue, Worker, processor, retry/backoff, RedisnewBullMqWorkerServer حول Worker رسمي متوقف مؤقتاً
CronerCron expression, time zone, callback, overlap policynewCronerServer حول وظائف Cron أصلية موقوفة مؤقتاً
Memory Brokerخريطة topics داخل العملية ودلالات الاختبارnewBrokerServer وevent codec اختياري

لا تنقل ack/nak/term في NATS، أو التسوية الدائمة في JetStream، أو تأكيدات RabbitMQ، أو عمليات retry في BullMQ إلى تجريد عام لـ go-like Broker. هذه الدلالات هي بالضبط سبب بقاء الكائن الأصلي للمزوّد ظاهراً.

ترحيل الحالة ​

اختر مجال حالة واحداً في كل مرة:

  • Config للقطات إعداد العملية غير القابلة للتغيير وإعادة التحميل؛
  • Registry لقابلية الوصول المؤقتة إلى الخدمات؛
  • Store للسجلات المرجعية والإصدارات وCAS وTTL والصفحات؛
  • Cache للقيم القابلة للتخلص التي يمكن إعادة حسابها.

من اختبارات الترحيل المفيدة أن تكتب ما يحدث بعد إعادة تشغيل العملية، وقراءة قديمة، وتعطّل المزوّد، وضغط watcher، وتعارض CAS، وcache miss. إذا اختلفت الإجابة، فلا ينبغي لهذه المجالات أن تشترك في واجهة repository عامة واحدة.

إضافة قابلية الرصد ​

أضف المزوّد الأصلي أولاً، ثم غلّف الحدّ:

text
application creates logger / Registry / MeterProvider / TracerProvider
  -> go-like wrapper records bounded operation facts
  -> application-owned exporter or destination
  -> explicit Core lifecycle adapter closes the admitted resource

لا يستخدم @go-like/prometheus السجل العام. ولا يثبّت @go-like/otel مزوّدات أو مصدّرات عامة. كما أن محوّلات Pino وWinston لا تستبدل إعداد المسجّل الأصلي. أبقِ labels وattributes محدودة، وطبّق إخفاءً منفصلاً على السجلات التي يملكها التطبيق.

قائمة قبول الترحيل ​

قبل دمج حدّ واحد، تحقّق من الآتي:

  • يوجد مالك واحد مسمّى بوضوح؛
  • يتلقى المالك Context الصحيح ولا يستبدله بـ background()؛
  • القبول أثناء الإقلاع والجاهزية شيئان مختلفان؛
  • يوثّق سلوك مهلة stop بوصفه حدّ انتظار؛
  • تبقى مراقبة الحالة النهائية الأصلية حيثما كانت متاحة؛
  • لا تختلط معالجات Web الخارجية بالمعالجات الداخلية الأحادية؛
  • يطابق تفويض retry عملية العمل؛
  • تملك credentials وmetadata والسجلات وسمات trace سياسة إخفاء؛
  • تبقى دلالات المزوّد الخاصة ظاهرة؛
  • نجح أمر unit/typecheck المركّز في checkout المستهدف؛
  • نُفّذ أمر E2E ذي الصلة ببيئة التشغيل أو المزوّد أو الحزمة المنشورة أو المثال وسُجّل، أو وُسِم صراحةً بأنه لم يُنفّذ.

حدّ الدعم الحالي ​

يوفّر @go-like/protoc-gen-like شيفرة Protobuf RPC مولّدة تضع Context أولاً، باستخدام Protobuf-ES. يوفّر @go-like/transport-grpc-buf عبر Fetch استدعاءات Connect/gRPC-Web الأحادية وتدفق استجابات الخادم؛ ويضيف /native معيار gRPC بالأنماط الأربعة، بما فيها تدفق طلبات العميل والتدفق ثنائي الاتجاه. هذا مسار مستقل عن Transport SPI الأحادي.

لا يشمل ذلك gRPC القياسي داخل المتصفح، أو تدفق طلبات Fetch ثنائي الاتجاه، أو health/reflection القياسيين، أو مصادقة عامة، أو Event Store/replay، أو ORM، أو تنسيق العناقيد.

اجتاز gRPC المُدار في Node 26.12.1 وBun 1.4.4 اختبار تصريف التدفق مع مستهلك بطيء. لم يُتحقق من إصلاح قيود الإلغاء والإيقاف المسجلة سابقاً في Connect 2.1.2 وBun 1.4.2 Fetch.