Saltar al contenido

Migración y adopción ​

La regla más segura para migrar es: conserva el plano de datos y adopta la frontera que puedas explicar.

Conserva el framework Web, worker, scheduler, broker, logger o proveedor de telemetría que ya tienes. Añade un contrato explícito de go-like alrededor de un problema real de ciclo de vida o de llamadas entre servicios. Verifica esa frontera antes de sumar otro provider.

Una migración por etapas ​

  1. Deja intactos el arranque existente y el código de rutas/plano de datos.
  2. Identifica un responsable: listener, worker, scheduler, suscripción a un broker, destino de logs o proveedor de telemetría.
  3. Añade un adaptador estructural Server o usa un adaptador existente de go-like. Define admisión, parada, timeout y observación terminal.
  4. Añade @go-like/context en las fronteras reales de cancelación o deadline. Pásalo como primer argumento de la operación.
  5. Añade liveness y readiness con @go-like/health y @go-like/web/health.
  6. Añade una llamada interna unary tipada usando @go-like/transport-memory en las pruebas.
  7. Mueve esa llamada a @go-like/transport-http o @go-like/transport-http/node solo cuando necesites un wire real o un host nativo de Node.
  8. Añade Registry, Config, Store, Cache, Broker, logs, métricas o trazas, una capacidad a la vez.
  9. Registra el provider, runtime, responsable y nivel de evidencia de cada frontera nueva.

No empieces reescribiendo todo el servicio. La idea de los contratos pequeños es que la unidad de migración pueda seguir siendo pequeña.

Matriz de migración de frameworks ​

Sistema existenteConserva lo nativoAdopta primeroFrontera actual
NestJSModules, controllers, decorators, DI, interceptors, pipes, adapterUn Server estructural propio alrededor de la aplicación existente o una frontera interna separada de Client/ServerEn este repositorio no existe un bridge de NestJS para go-like ni integración automática con DI
FastifyRoutes, plugins, hooks, request/reply, native listenerUn wrapper de ciclo de vida propio o un bridge Fetch implementado de forma explícitaNo se ha demostrado una conversión actual de Fastify request/reply a go-like Handler
HonoRoutes, middleware, sub-apps, app.fetchnewNodeServer(app.fetch, ...), seguido de newApp(...)La integración nativa con Fetch está demostrada en examples/hono
ElysiaRoute tree, schema, decorators, derives, hooks, Bun/Web Standard behaviorapp.fetch nativo más el host/ciclo de vida de Core cuando correspondaConserva la semántica Bun específica de .listen(); no lo llames una API de go-like para todos los runtimes
H3H3 router and native handler conversionLa ruta de handler Fetch del ejemplo actual de H3app.fetch de H3 2.x es la forma demostrada actualmente; la guía antigua de toWebHandler necesita su propio ejemplo fijado
KoaMiddleware and external routerUn wrapper del responsable o una llamada interna al servicio@go-like/web no acepta el objeto Node request/reply de Koa sin un bridge de aplicación
tRPCRouter, procedure middleware, input/output parsers, adapterEl ciclo de vida de Core alrededor del host o una frontera de transporte interna separadago-like Endpoint no es un router de procedimientos de tRPC

Ejemplo con Hono ​

Esta es la forma de integración demostrada:

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()

El ejemplo actual con Hono conserva el control de Hono sobre las rutas y pasa el handler Fetch nativo al host de Node. No añade una tabla de rutas de go-like ni un paquete bridge específico de Hono.

Elysia y H3 ​

Aplica la misma frontera a un framework que expone un handler Fetch estándar:

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

Revisa el adaptador de runtime del framework antes de importar un subpath de Node. El adaptador Bun de Elysia y su adaptador Web Standard no tienen un comportamiento de escucha idéntico. Las versiones de H3 y sus APIs para convertir handlers también necesitan un ejemplo fijado. No uses la existencia de un solo ejemplo para prometer cualquier combinación de versión de framework y runtime.

Migrar un servicio de Go ​

Para quien viene de Go o Kratos, migra conceptos, no nombres:

Concepto de GoConcepto de go-likeDiferencia importante
context.Context@go-like/context Contextdone() es un AbortSignal o null, no un canal de Go
Server lifecycleServer estructural de Corestart(ctx) puede durar toda la vida del servicio y no significa readiness
App runnernewApp, App.run, App.stopApp.stop() no recibe Context del caller y devuelve una sola Promise compartida
RPC client@go-like/clientLas llamadas internas son JSON Fetch o SSE; el retry es opt-in
Transport@go-like/transportLos providers y los headers Fetch son contratos de TypeScript/Web
Registry@go-like/registryLos watchers devuelven snapshots de reemplazo completo
SelectornewRoundRobinSelector, newRandomSelector, newWeightedRoundRobinSelector, newP2CSelector, newEWMASelectorEl feedback es síncrono y depende de la policy
Protobuf/IDL@go-like/protoc-gen-likeCódigo Context-first sobre Protobuf-ES
gRPC stream@go-like/transport-grpc-buf/nativegRPC estándar con cuatro cardinalidades mediante /native

Un primer paso incremental es hacer una llamada tipada a una dirección directa sobre 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

Solo después de probar esta frontera conviene introducir Discovery, un provider real de Registry o un transporte HTTP. Así conservas el contrato de dominio mientras sustituyes el destino y la configuración de ownership.

Adoptar Kubernetes ​

Conserva Kubernetes como está:

  • Deployments, Services, DNS, Ingress, RBAC, probes, estrategia de rollout, HPA y network policy siguen siendo responsabilidades de la plataforma;
  • @go-like/config-kubernetes lee una clave de un solo ConfigMap o Secret dentro de un namespace mediante una capacidad Fetch inyectada;
  • @go-like/registry-kubernetes usa registros de EndpointSlice cuando el discovery directo es realmente necesario;
  • un EndpointSlice no es el DNS de un Kubernetes Service ni ofrece un TTL universal de registro;
  • las referencias opcionales al owner del Pod y el deregistration explícito tienen semánticas de fallo distintas.

Empieza por health y configuración antes de seleccionar directamente desde EndpointSlice. Si la aplicación ya tiene un nombre DNS estable de Service, withEndpoint(...) más un transporte HTTP puede ser más sencillo y más honesto que añadir un provider de Registry.

Adoptar brokers y jobs ​

Conserva nativos el settlement y la policy de jobs:

Plano de datos existenteConservaAñade go-like para
NATS CoreConnection, subscription, queue group, Msg, drainnewNatsCoreServer, newNatsCoreBroker, ciclo de vida y frontera de bytes
NATS JetStreamStream, durable consumer, JsMsg, ack/nak/term, redelivery, DLQnewNatsJetStreamServer, newNatsJetStreamBroker, ciclo de vida
RabbitMQConnection, topology, confirm policy, channelCiclo de vida del subscriber borrowed o recovering y settlement nativo seguro frente a generaciones
BullMQQueue, Worker, processor, retry/backoff, RedisnewBullMqWorkerServer alrededor de un Worker oficial en pausa
CronerCron expression, time zone, callback, overlap policynewCronerServer alrededor de jobs Cron nativos pausados
Memory BrokerMapa de topics en proceso y semántica de pruebasnewBrokerServer y un codec de eventos opcional

No migres ack/nak/term de NATS, el settlement durable de JetStream, las confirmaciones de RabbitMQ ni los retries de BullMQ a una abstracción genérica de go-like Broker. Esas semánticas son precisamente la razón por la que el objeto nativo del provider sigue visible.

Migrar el estado ​

Elige un dominio de estado a la vez:

  • Config para snapshots inmutables de configuración del proceso y reload;
  • Registry para reachability efímera de servicios;
  • Store para registros autoritativos, revisiones, CAS, TTL y páginas;
  • Cache para valores desechables que se pueden recalcular.

Una prueba útil de migración es anotar qué sucede después de un reinicio del proceso, una lectura stale, una caída del provider, una compactación del watcher, un conflicto de CAS y un cache miss. Si la respuesta cambia, no deberían compartir una interfaz de repository genérica.

Añadir observabilidad ​

Añade primero el provider nativo y después envuelve la frontera:

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 no usa el registro global. @go-like/otel no instala providers ni exporters globales. Los adaptadores de Pino y Winston no reemplazan la configuración nativa del logger. Mantén acotados los labels y attributes, y redacta por separado los logs que son responsabilidad de la aplicación.

Checklist de aceptación de la migración ​

Antes de integrar una frontera, verifica:

  • existe un responsable claramente nombrado;
  • el responsable recibe el Context correcto y no lo reemplaza por background();
  • la admisión durante el arranque y readiness son cosas distintas;
  • el comportamiento del timeout de stop está documentado como una frontera de espera;
  • se conserva la observación terminal nativa cuando existe;
  • no se mezclan handlers Web externos con handlers internos unary;
  • la autorización del retry corresponde a la operación de negocio;
  • credenciales, metadata, logs y atributos de trace tienen una policy de redacción;
  • las semánticas específicas del provider siguen visibles;
  • pasó el comando de unit/typecheck enfocado en el checkout objetivo;
  • el comando E2E relevante de runtime, provider, published o example se ejecutó y quedó registrado, o se marcó explícitamente como no ejecutado.

Frontera de soporte actual ​

@go-like/protoc-gen-like genera código Protobuf RPC con Context como primer argumento sobre Protobuf-ES. @go-like/transport-grpc-buf ofrece unary y server-streaming de Connect/gRPC-Web mediante Fetch; /native añade gRPC estándar con las cuatro cardinalidades, incluidas client-streaming y bidi. Es una vía independiente del camino interno Fetch/SSE.

Esto no incluye gRPC estándar en el navegador, request-streaming/bidi mediante Fetch, health/reflection oficiales, autenticación genérica, Event Store/replay, ORM ni orquestación de clústeres.

gRPC administrado en Node 26.12.1 y Bun 1.4.4 pasó la regresión de drain con un consumidor lento. No se ha verificado la resolución de los límites históricos de cancelación y apagado de Connect 2.1.2 y Bun 1.4.2 Fetch.