Empiezas un proyecto nuevo y alguien dice: «esto hay que hacerlo con microservicios, que es lo que usa Netflix». Suena moderno y escalable. Pero para la gran mayoría de proyectos es una trampa: pagas desde el primer día el precio de un problema que quizá nunca tengas. Lo sensato es empezar con un monolito, bien ordenado, y separar piezas solo cuando haya un motivo real. En este post te explico por qué, con una tienda online de ejemplo y sin jerga.
Explicación
Antes de nada, una comparación para que se entienda sin saber nada de servidores.
Un monolito es un restaurante con una sola cocina. Todos los cocineros trabajan en la misma sala. Si el de los postres necesita nata, se la pide al de al lado en voz alta y en un segundo la tiene. Si algo sale mal, lo ves enseguida, porque todo pasa delante de ti.
Los microservicios son una calle de food trucks. Cada camión hace una cosa: uno las hamburguesas, otro las patatas, otro los postres. Cada uno es independiente, puede abrir, cerrar o cambiar de menú sin preguntar a nadie. Pero para servir un menú completo, un camarero tiene que ir corriendo de camión en camión. Y si el de las patatas se queda sin gas, el menú llega cojo… o no llega.
Para una calle con miles de clientes y decenas de equipos de cocina, los food trucks tienen sentido. Para un restaurante que acaba de abrir, son un lío.
Lo mismo, en código
Monolito: una sola aplicación (por ejemplo, un proyecto Symfony), que se despliega de una vez y usa una sola base de datos. Dentro están todas las partes: el catálogo, los pedidos, las facturas, los usuarios.
Microservicios: cada parte es una aplicación separada, con su propio servidor y su propia base de datos. Para hablar entre ellas se mandan peticiones por la red.
MONOLITO MICROSERVICIOS
┌──────────────────────────┐ ┌──────────┐ red ┌──────────┐
│ Catálogo Pedidos │ │ Pedidos │ ────▶ │ Facturas │
│ Facturas Usuarios │ └────┬─────┘ └────┬─────┘
└────────────┬─────────────┘ │ │
│ [ BD 1 ] [ BD 2 ]
[ una BD ]
┌──────────┐ ┌──────────┐
│ Catálogo │ │ Usuarios │
└────┬─────┘ └────┬─────┘
[ BD 3 ] [ BD 4 ]
«Monolito primero» significa empezar por la izquierda y pasar a la derecha solo si hace falta, y solo con la pieza que lo necesite. La idea la popularizó Martin Fowler hace años y sigue siendo el mejor consejo para casi cualquier proyecto nuevo.
El ejemplo: una tienda de camisetas
Vamos a seguir un caso concreto. Montas una tienda online de camisetas. Cuando alguien compra, tienen que pasar tres cosas:
- Guardar el pedido.
- Restar las camisetas del stock.
- Crear la factura.
Con un monolito
Las tres cosas son llamadas a métodos dentro de la misma aplicación, y van juntas en una transacción: o se guardan las tres, o no se guarda ninguna.
$this->entityManager->wrapInTransaction(function () use ($order) {
$this->orders->save($order);
$this->stock->reserve($order);
$this->invoices->createFor($order);
});
Si la factura falla, el pedido y el stock se deshacen solos. No queda nada a medias.
Con microservicios
Pedidos, stock y facturas son tres aplicaciones distintas. El servicio de pedidos guarda el suyo y luego llama a los otros dos por la red:
$this->orders->save($order);
$this->httpClient->request('POST', 'http://stock-service/reservations', [
'json' => ['orderId' => $order->getId()],
]);
$this->httpClient->request('POST', 'http://billing-service/invoices', [
'json' => ['orderId' => $order->getId()],
]);
Parece casi igual, pero ahora hazte estas preguntas:
- ¿Qué pasa si el servicio de stock tarda 30 segundos en responder?
- ¿Y si el stock se reserva, pero el de facturas está caído? Tienes un pedido sin factura.
- ¿Y si la petición al stock falla por la red, pero en realidad sí se llegó a reservar? Si reintentas, reservas dos veces.
- ¿Cómo deshaces el pedido y el stock si la factura no se puede crear? Ya no hay transacción que lo haga por ti.
Todas tienen solución (reintentos, colas, idempotencia, operaciones de «deshacer»…), pero cada una es trabajo, código y cosas que pueden fallar. En el monolito, ninguna de estas preguntas existe.
Por qué empezar con un monolito
1. Todo es más sencillo de programar
Lo acabamos de ver: una llamada a un método no se cae, no tarda 30 segundos y no llega dos veces. Una llamada por la red, sí. Cuantas más partes de tu aplicación se hablan por la red, más código dedicas a defenderte de la red en vez de a tu negocio.
2. Todavía no sabes dónde cortar
Para separar en servicios hay que saber qué va con qué. ¿Los descuentos son parte de «pedidos» o de «catálogo»? ¿Los envíos van con «pedidos» o son algo aparte? Al empezar un proyecto eso no se sabe bien: se descubre con el uso.
Si cortas mal en un monolito, mueves unas clases de carpeta y listo: es una tarde. Si cortas mal con microservicios, tienes que mover datos entre bases de datos, cambiar APIs y coordinar despliegues: son semanas.
3. Hay una sola cosa que desplegar y vigilar
Con un monolito, subir una versión nueva es subir una aplicación. Si algo falla, miras un log. Con cuatro servicios tienes cuatro despliegues, cuatro logs, cuatro servidores que vigilar y la red entre ellos. Y un error puede empezar en un servicio y notarse en otro, así que encontrarlo es como seguir una pista por toda la calle de food trucks.
4. Cambiar algo que toca varias partes es fácil
Imagina que quieres añadir «talla» a los pedidos, y eso afecta al catálogo, al stock y a la factura. En un monolito es un cambio, una prueba y un despliegue. Con microservicios son tres cambios en tres aplicaciones que hay que desplegar en el orden correcto para no romper nada por el camino.
5. Es más barato
Un monolito pequeño cabe en un servidor. Varios servicios necesitan varios servidores (o un sistema para orquestarlos, como Kubernetes), y alguien que sepa mantenerlo todo. Ese dinero y ese tiempo no se dedican a tu producto.
6. Los microservicios resuelven un problema de equipos, no de código
Esta es la clave. La gran ventaja de los microservicios es que muchos equipos puedan trabajar sin pisarse: cada uno con su servicio, desplegando cuando quiera. Netflix o Amazon los usan porque tienen miles de programadores. Si sois una, cinco o veinte personas, ese problema no lo tenéis, y pagáis todo el coste sin disfrutar de la ventaja.
Lo que cuesta cada opción
| Monolito | Microservicios | |
|---|---|---|
| Aplicaciones que mantener | 1 | Una por servicio |
| Bases de datos | 1 | Una por servicio |
| Hablar entre partes | Llamar a un método | Peticiones por la red |
| Guardar varias cosas a la vez | Una transacción | Reintentos, colas y compensaciones |
| Buscar un error | Un log | Seguir la petición por varios servicios |
| Equipos trabajando en paralelo | Se pueden pisar si son muchos | Cada uno a lo suyo |
Los mitos del monolito
«Un monolito no escala»
Sí escala. Puedes poner varias copias de la misma aplicación detrás de un balanceador de carga, y cada copia atiende una parte de las visitas. Y lo que suele ahogarse primero no es el código, sino la base de datos, y eso pasa igual con microservicios. Hay empresas enormes, como Shopify o Stack Overflow, conocidas por funcionar durante años con un monolito.
«Un monolito acaba siendo código espagueti»
Un monolito desordenado, sí. Pero el desorden no se arregla repartiéndolo en servicios: se arregla ordenando. Si separas un monolito desordenado, obtienes un desorden repartido por la red, que es peor. Ahora vemos cómo ordenarlo.
«Luego será imposible separarlo»
Solo si todo está mezclado con todo. Si desde el principio lo organizas por módulos (lo siguiente que vamos a ver), sacar uno a un servicio es bastante directo.
«Es lo que hacen las grandes empresas»
Las grandes empresas llegaron a los microservicios después de crecer, cuando el monolito ya les quedaba pequeño para tanta gente. No empezaron así. Copiar la arquitectura de una empresa con miles de programadores es como comprar un autobús porque a veces llevas a tus hijos al colegio.
Un monolito bien ordenado: por módulos
El secreto está en organizar el monolito como si fueran servicios, pero dentro de la misma aplicación. A esto se le llama monolito modular. En la tienda de camisetas, las carpetas quedarían así:
src/
├── Catalog/ ← camisetas, tallas, precios
├── Order/ ← carrito y pedidos
├── Stock/ ← cuántas quedan de cada una
├── Billing/ ← facturas
└── Shared/ ← lo poco que usan todos
Cada carpeta es un módulo, y se siguen tres reglas sencillas.
Regla 1: cada módulo se ocupa de sus datos
Order no hace consultas a las tablas de Billing. Si necesita algo
de facturación, se lo pide al módulo de facturación. Es como en la cocina: no metes la
mano en la nevera del pastelero, se lo pides a él.
Regla 2: los módulos se hablan a través de un servicio
Cada módulo ofrece un servicio con lo que los demás pueden usar, y lo demás es «privado».
Así, si Order quiere saber si queda stock, hace esto:
// bien: le pregunta al módulo de stock
if (!$this->stock->isAvailable($productId, $quantity)) {
throw new OutOfStockException();
}
Y no esto:
// mal: se mete en la tabla de otro módulo
$item = $this->stockItemRepository->findOneBy(['product' => $productId]);
if ($item->getQuantity() < $quantity) {
throw new OutOfStockException();
}
La diferencia parece pequeña, pero en el primer caso, si mañana el stock cambia de forma de
guardarse (o se va a otro servicio), Order no se entera. En el segundo, se
rompe.
Regla 3: para avisar, eventos
Cuando se paga un pedido, Order no llama uno a uno a todos los que les
interesa. Publica un aviso («el pedido 42 se ha pagado») y cada módulo que lo necesite lo
escucha. Con Messenger de Symfony:
// en Order: aviso de que el pedido se ha pagado
$this->bus->dispatch(new OrderPaid($order->getId()));
// en Billing: escucho el aviso y hago lo mío
#[AsMessageHandler]
final class CreateInvoiceOnOrderPaid
{
public function __construct(private InvoiceService $invoices)
{
}
public function __invoke(OrderPaid $event): void
{
$this->invoices->createForOrder($event->orderId);
}
}
La ventaja: Order no sabe ni que existe Billing. Mañana añades un
módulo que manda un email de agradecimiento, y solo tienes que escuchar el mismo aviso,
sin tocar pedidos. Si quieres ver cómo funciona Messenger por dentro, lo cuento en
asincronía en Symfony.
Cuándo sí separar un servicio
Hay motivos reales para sacar una pieza fuera. Casi todos tienen algo en común: esa pieza es muy diferente del resto.
- Consume recursos muy distintos. En la tienda, generar la imagen de cada camiseta personalizada gasta mucha CPU. Si lo haces en la misma aplicación, las páginas van lentas cuando hay muchos encargos. Separarlo permite darle sus propias máquinas.
- Necesita otra tecnología. Un sistema de recomendaciones en Python junto a tu aplicación PHP.
- Tiene que estar aislado. Por ejemplo, los cobros con tarjeta: por seguridad o por normativa, conviene que vivan aparte del resto.
- Hay muchos equipos pisándose. Cuando coordinar despliegues entre equipos cuesta más que mantener un servicio aparte.
Y fíjate en lo que no está en la lista: «por si algún día crecemos», «porque es más moderno» o «porque el código está desordenado».
Cómo separar un servicio, paso a paso
Si llega el día, y el monolito está ordenado por módulos, el camino es tranquilo. Sigamos con el ejemplo de las imágenes personalizadas:
- Que sea un módulo limpio. Todo lo de imágenes está en su carpeta, con sus datos, y los demás solo lo usan a través de su servicio o de eventos.
- Crea la aplicación nueva con el código de ese módulo y su propia base de datos.
- Cambia cómo se le llama. Donde antes se llamaba al servicio del módulo, ahora se envía un mensaje a una cola o una petición HTTP. Si ya usabas eventos, a menudo basta con que viajen por una cola en vez de quedarse dentro.
- Ponlo en marcha poco a poco. Primero una parte de los encargos, luego todos. Si algo va mal, vuelves atrás.
- Borra el módulo del monolito cuando el servicio nuevo ya lo hace todo.
De uno en uno, y solo la pieza que lo necesita. Es muy normal acabar con un monolito grande y dos o tres servicios alrededor, y eso está perfecto.
El resumen
| Situación | Qué hacer |
|---|---|
| Proyecto nuevo, equipo pequeño | Monolito |
| El monolito empieza a ser un lío | Ordenarlo por módulos, no partirlo |
| Una pieza necesita otros recursos, otra tecnología o aislamiento | Sacar solo esa pieza |
| Muchos equipos que no pueden trabajar sin pisarse | Microservicios, poco a poco |
Y ya está
Los microservicios no son malos: son una herramienta para un problema concreto, el de muchas personas trabajando sobre la misma aplicación. Si no tienes ese problema, te llevas todos sus inconvenientes sin ninguna de sus ventajas.
Empieza con un restaurante con una sola cocina. Ordénalo bien desde el primer día, con cada cocinero en su puesto. Y si algún día un puesto necesita su propio camión, lo sacas a la calle. Ese día será fácil, porque ya estaba separado por dentro.