¿Microservicio o librería compartida? Un marco para decidir

Cada vez que aparece lógica compartida entre varios productos, la pregunta suele llegar demasiado pronto: "¿lo sacamos a microservicio?".

La pregunta correcta es menos vistosa y mucho más útil:

Important

¿Qué problema concreto quiero resolver: reuso, secretos, cómputo, despliegue centralizado o aislamiento organizativo?

Mapa de decisión

flowchart TD start["Tengo lógica compartida"] --> client{"¿Debe correr en cliente/offline?"} client -->|Sí| library["Librería compartida"] client -->|No| secrets{"¿Maneja secretos, keys o cómputo pesado?"} secrets -->|Sí| service["Microservicio"] secrets -->|No| consumers{"¿Cuántos consumidores reales hay?"} consumers -->|1| local["Déjalo donde está"] consumers -->|2| wait["Observa y documenta el contrato"] consumers -->|3 o más| library library --> servercase{"¿También hay caso server-side?"} servercase -->|Sí| both["Librería + servicio que la importa"] servercase -->|No| done["Fin"]

Los cuatro objetivos que se confunden

Cuando alguien dice "microservicio", normalmente está mezclando objetivos distintos:

Objetivo real Mejor herramienta ¿Microservicio? Señal práctica
Reusar lógica entre productos Librería compartida No como primera respuesta Mismo código, misma semántica
Proteger secretos Servicio server-side Hay keys, tokens o firmas privadas
Ejecutar cómputo pesado Servicio especializado PDF, browser headless, ML, jobs batch
Cambiar comportamiento sin actualizar clientes API versionada A veces Muchos consumidores externos o equipos

La arquitectura se aclara cuando separas estos objetivos. Un microservicio resuelve bien secretos, cómputo y operación centralizada. No resuelve mágicamente el reuso.

Comparación rápida

Librería compartida

Úsala cuando la lógica es pura, portable y necesita correr cerca del consumidor.

  • Render local.
  • Validadores.
  • Parsers.
  • Transformaciones sin secretos.

Microservicio

Úsalo cuando el runtime importa o el cliente no debe ver lo que ocurre.

  • Keys privadas.
  • Deploys.
  • Exports server-side.
  • Billing, licencias y auditoría.

Ambos

Úsalos cuando hay un núcleo puro y un caso server-side que lo envuelve.

  • Core de render.
  • API de exports.
  • Servicio de publicación.
  • Portal con previews.

La pregunta de runtime

La primera pregunta siempre es dónde debe ejecutarse la lógica.

Runtime necesario Implicación
Navegador No puedes proteger el código. Puedes empaquetarlo mejor, no esconderlo.
Desktop offline Necesitas librería local. Un servicio rompería el modo offline.
Servidor Puedes proteger secretos y controlar despliegues.
Worker o job Puedes aislar coste, memoria y permisos.
Warning

"Lo sacamos a microservicio para proteger el código" es una frase incompleta. Si el cliente necesita ejecutar esa lógica, el código seguirá estando en el cliente.

Caso real: render Markdown en una plataforma multi-producto

El renderer de Markdown enriquecido parecía candidato a microservicio porque lo usaban varios productos. Aplicando el marco:

Pregunta Respuesta Decisión
¿Debe correr offline en el editor? Librería
¿Tiene lógica pura sin secretos? Librería
¿Hay más de tres consumidores? Extraer paquete
¿Hay caso server-side? Sí, exports y publicación Servicio que importa la librería
¿El servicio tiene secretos propios? Sí, deploys, cuotas, credenciales de tercero Microservicio para exports y publicación

Resultado:

flowchart LR core["Core de render<br/>Markdown a HTML"] --> editor["Editor offline"] core --> web["Visor web"] core --> dashboard["Dashboards"] core --> renderService["Servicio de render"] renderService --> outputs["PDF / HTML / Word"] publish["Servicio de publicación"] --> renderService publish --> edge["Edge / object storage"]

No es contradicción usar librería y servicio. La contradicción sería convertir una función pura en API remota y romper el modo offline sólo para llamarlo "arquitectura".

Cuándo el servicio sí es la respuesta

Hay casos donde una librería compartida no basta:

Caso Por qué necesita servicio
Deploy a edge o hosting estático El cliente no debe tener tus tokens de deploy
Firma de licencias La clave privada debe vivir en servidor
Export PDF fiable El motor de render, fuentes y entorno controlado son server-side
Billing El estado de suscripción es autoritativo en servidor
Auditoría El log debe ser central, firmado y no editable por cliente
Tip

Si puedes escribir una función pura con input y output claros, empieza por librería. Si necesitas permisos, secretos, estado autoritativo o infraestructura, empieza por servicio.

Anti-patrones

Señales de que estás creando un microservicio prematuro
  • Sólo tienes un consumidor real.
  • La lógica debe correr offline.
  • No hay secretos ni cómputo pesado.
  • La API sería una llamada remota para envolver una función pura.
  • El servicio sólo existe porque "queda más limpio".
  • El coste de latencia y despliegue supera el ahorro de mantenimiento.
Señales de que estás escondiendo un servicio necesario dentro de una librería
  • La librería necesita tokens de terceros.
  • El cliente podría saltarse cuotas o planes.
  • El resultado debe quedar auditado.
  • El cálculo consume demasiada memoria o CPU para el cliente.
  • Cada consumidor reimplementa permisos, billing o deploy.

Regla operativa

Antes de mover una pieza, escribo esta frase:

Plantilla Ejemplo bueno Ejemplo malo
"Lo saco a X para resolver Y en Z runtime" "Lo saco a servicio para firmar licencias con una clave privada en servidor" "Lo saco a microservicio para proteger el código"
"Lo saco a librería para compartir Y entre Z consumidores" "Lo saco a librería para renderizar Markdown igual en editor, web y dashboard" "Lo saco porque ya está duplicado dos veces"

La regla que me llevo

No elijas entre microservicio y librería por moda. Elige por runtime, secreto, cómputo, número de consumidores y contrato.


Este artículo forma parte de mis notas de arquitectura. Si te interesa el tema, sigue el blog o escríbeme.