Migrar un sistema monolític en producció cap a una arquitectura de microserveis és una de les maniobres més arriscades que afronta un equip de programari. L'error clàssic és la reescriptura completa ("big bang rewrite"): congelar el desenvolupament de noves funcionalitats, construir el nou sistema des de zero durant mesos i, el dia de la veritat, descobrir que no replica tots els matisos de l'original. La indústria ha après a fer-ho d'una altra manera: de forma incremental, mantenint el monòlit viu i en producció mentre se li van extraient peces. El patró que dóna nom a aquesta estratègia és el Strangler Fig (figuera estranguladora), encunyat per Martin Fowler. En aquesta lliçó veurem l'estratègia incremental, com identificar les costures del monòlit, com protegir els models nous amb una Anti-Corruption Layer, la tècnica de Branch by Abstraction i els riscos que has d'anticipar.
Contingut
- El problema de la reescriptura "big bang"
- El patró Strangler Fig
- Identificar les costures (seams) del monòlit
- Branch by Abstraction
- Anti-Corruption Layer (ACL)
- Estratègia incremental pas a pas
- Riscos i com mitigar-los
- Errors Comuns i Consells
- Exercicis
- Conclusió
- El problema de la reescriptura "big bang"
La temptació de llençar el monòlit i començar de zero és enorme: el codi vell "fa fàstic", ningú no entén del tot com funciona i el nou paradigma promet netedat. Però la reescriptura total falla gairebé sempre per les mateixes raons:
- El monòlit codifica anys de regles de negoci implícites. Moltes d'aquestes regles no estan documentades: viuen en
ifaparentment absurds que en realitat resolen un cas límit real. - El negoci no s'atura. Mentre reescrius durant 18 mesos, el monòlit continua rebent canvis. Persegueixes un blanc en moviment.
- El dia del canvi (cutover) és un salt al buit. Tota la migració de dades i de trànsit passa alhora, amb màxim risc i mínima reversibilitat.
L'alternativa madura és estrangular el monòlit a poc a poc. Cada pas és petit, està en producció i és reversible.
- El patró Strangler Fig
El nom ve de la figuera estranguladora: una planta que creix al voltant d'un arbre amfitrió, l'envolta i, amb els anys, el substitueix per complet fins que l'arbre original desapareix. Aplicat al programari, la idea és interposar una capa d'enrutament (normalment un gateway o proxy) davant del monòlit. Aquesta capa decideix, petició a petició, si l'atén el monòlit antic o un servei nou ja extret.
graph TD
Cliente[Client] --> GW[Gateway / Strangler Facade]
GW -->|funcionalitat migrada| MS1[Servei Pagaments nou]
GW -->|funcionalitat migrada| MS2[Servei Catàleg nou]
GW -->|encara no migrat| Mono[Monòlit llegat]
MS1 --> DBN[(BD del servei)]
Mono --> DBM[(BD del monòlit)]El diagrama mostra la peça clau: el Strangler Facade (la façana o gateway). Al principi, gairebé tot el trànsit va al monòlit. A mesura que extreus capacitats, vas redirigint rutes concretes (per exemple, /pagaments/*) cap al servei nou. El client no s'assabenta del canvi perquè sempre parla amb la mateixa façana. Quan la darrera ruta abandona el monòlit, aquest queda buit i es pot apagar.
Avantatges davant del big bang:
- Lliurament continu de valor: cada extracció és un increment que ja aporta.
- Risc acotat: si un servei nou falla, redirigeix aquesta ruta de tornada al monòlit.
- Sense congelació: el monòlit continua evolucionant en les parts no migrades.
- Identificar les costures (seams) del monòlit
Una costura (seam, terme de Michael Feathers) és un punt del sistema on pots alterar el comportament sense editar el codi en aquell lloc. Per extreure un microservei necessites trobar costures netes que delimitin una capacitat de negoci. Les millors candidates a sortir primer són mòduls que compleixen:
| Criteri | Per què facilita l'extracció |
|---|---|
| Baix acoblament amb la resta | Poques dependències a trencar en separar-lo |
| Alta cohesió interna | Forma una capacitat de negoci completa per si mateixa |
| Dades relativament independents | La seva base de dades no està embolicada amb totes les taules |
| Alt valor o alt canvi | Justifica l'esforç: o escala diferent, o canvia molt |
| Risc controlable | Si falla, no tomba tot el negoci |
El pitjor candidat per començar és el mòdul central que toca tot (per exemple, "Client" en moltes empreses). Comença pels pètals, no pel cor.
// ABANS: el monòlit crida directament la seva classe interna
public class ProcesadorPedido {
private final CalculadoraEnvio calculadora = new CalculadoraEnvio(); // dependència rígida
public Pedido procesar(Pedido p) {
double coste = calculadora.calcular(p.getDestino(), p.getPeso());
p.setCosteEnvio(coste);
return p;
}
}Aquí no hi ha costura: ProcesadorPedido instancia directament CalculadoraEnvio amb new. Per poder substituir el càlcul d'enviament per un servei remot, primer hem de crear una costura. Això ens porta a la tècnica següent.
- Branch by Abstraction
Branch by Abstraction és una tècnica per fer un canvi gran en petits passos sense fer servir branques de llarga durada al control de versions. La idea: introdueixes una abstracció (una interfície) entre el consumidor i la implementació actual; després afegeixes una segona implementació darrere de la mateixa abstracció; finalment commutes d'una a l'altra i esborres la vella.
// PAS 1: extreure una abstracció (interfície) sobre la capacitat
public interface ServicioEnvio {
double calcular(Direccion destino, double peso);
}
// PAS 2: la implementació actual (llegada) compleix la interfície, sense canviar la seva lògica
public class ServicioEnvioLocal implements ServicioEnvio {
private final CalculadoraEnvio calculadora = new CalculadoraEnvio();
public double calcular(Direccion destino, double peso) {
return calculadora.calcular(destino, peso);
}
}
// PAS 3: el consumidor depèn de l'abstracció, no de la classe concreta
public class ProcesadorPedido {
private final ServicioEnvio envio; // injectat
public ProcesadorPedido(ServicioEnvio envio) { this.envio = envio; }
public Pedido procesar(Pedido p) {
p.setCosteEnvio(envio.calcular(p.getDestino(), p.getPeso()));
return p;
}
}Anem pas a pas per què això habilita la migració:
- El pas 1 crea la "branca per abstracció":
ServicioEnvioés ara el punt de commutació. Tota la diferència entre el món vell i el nou passarà per aquesta interfície. - El pas 2 embolcalla la lògica existent sense tocar-la. El sistema continua comportant-se igual; només hem interposat una capa fina.
- El pas 3 inverteix la dependència:
ProcesadorPedidoja no sap com es calcula l'enviament, només que existeix unServicioEnvio.
Ara podem afegir una nova implementació que cridi per xarxa el microservei:
// PAS 4: nova implementació que delega al microservei remot
public class ServicioEnvioRemoto implements ServicioEnvio {
private final ClienteHttp cliente;
public ServicioEnvioRemoto(ClienteHttp cliente) { this.cliente = cliente; }
public double calcular(Direccion destino, double peso) {
// crida HTTP al nou servei d'enviaments
return cliente.post("/envios/calcular",
Map.of("destino", destino, "peso", peso), Double.class);
}
}Amb un feature flag decideixes quina implementació s'injecta. Pots commutar per a l'1% del trànsit, observar, i pujar gradualment. Si alguna cosa va malament, tornes a ServicioEnvioLocal a l'instant. Quan el 100% del trànsit faci servir la versió remota durant un temps prudencial, elimines la implementació local. La "branca" s'ha fusionat sense drames.
- Anti-Corruption Layer (ACL)
El monòlit llegat sol tenir un model de dades vell, amb noms confusos, camps reutilitzats per a diverses coses i conceptes que ja no encaixen amb el negoci actual. Si el microservei nou adopta aquest model tal qual, hereta la corrupció. L'Anti-Corruption Layer (capa anticorrupció, terme d'Eric Evans en DDD) és una capa de traducció que aïlla el model net del servei nou del model brut del llegat.
graph LR
MSnuevo[Servei nou\nmodel net] --> ACL[Anti-Corruption Layer\ntraducció]
ACL --> Legado[Monòlit / sistema llegat\nmodel antic]// Model net del servei nou
public record Cliente(String id, String nombreCompleto, NivelFidelidad nivel) {}
// L'ACL tradueix del format llegat al model net
public class ClienteAcl {
private final ClienteLegadoApi legado;
public ClienteAcl(ClienteLegadoApi legado) { this.legado = legado; }
public Cliente obtener(String id) {
// El llegat retorna un mapa amb noms críptics: CL_NOM, CL_AP1, CL_TIP
Map<String,String> raw = legado.fetch(id);
String nombre = raw.get("CL_NOM") + " " + raw.get("CL_AP1");
// "TIP=3" al llegat significa client VIP: ho traduïm al nostre enum
NivelFidelidad nivel = "3".equals(raw.get("CL_TIP"))
? NivelFidelidad.VIP : NivelFidelidad.ESTANDAR;
return new Cliente(id, nombre.trim(), nivel);
}
}Què aconseguim amb aquesta ACL:
- El servei nou mai no veu
CL_NOMniCL_TIP. Treballa ambCliente,nombreCompletoi un enumNivelFidelidadclar. - Tota la "lletjor" del llegat queda encapsulada en un únic lloc. Si el llegat canvia, només retoques l'ACL.
- És un punt natural d'esborrat: quan el llegat mori, elimines l'ACL i el model net sobreviu intacte.
L'ACL té un cost (codi i latència de traducció), però protegeix la inversió en el model nou. És gairebé obligatòria en integrar amb sistemes heretats que no controles.
- Estratègia incremental pas a pas
Una seqüència típica i segura per estrangular un monòlit:
- Posa una façana davant del monòlit (gateway/proxy). De moment reenvia el 100% al monòlit. No canvia res funcional, però crea el punt de commutació.
- Afegeix observabilitat: logs, mètriques i traces distribuïdes. No pots migrar a cegues; necessites mesurar latència i errors abans i després.
- Tria la primera capacitat segons els criteris de costures (baix acoblament, dades separables).
- Aplica Branch by Abstraction dins del monòlit per a aquesta capacitat.
- Construeix el microservei amb la seva pròpia base de dades. Si toca dades del llegat, separa les dades (replicació o doble escriptura temporal).
- Posa una ACL si el model del llegat contamina.
- Commuta trànsit gradualment amb feature flags; observa; reverteix si cal.
- Elimina el codi mort del monòlit un cop estabilitzat.
- Repeteix amb la capacitat següent fins a buidar el monòlit.
La separació de dades sol ser la part més dura. Tècniques habituals: vistes de només lectura sobre la BD del monòlit al principi, després doble escriptura, i finalment la BD pròpia del servei com a font de veritat.
- Riscos i com mitigar-los
| Risc | Mitigació |
|---|---|
| Transaccions que creuaven mòduls ja no són ACID | Patró Saga, consistència eventual i disseny de compensacions |
| Latència: el que era crida en memòria ara és xarxa | Mesurar, agrupar crides, cachejar; no extreure si el chatter és altíssim |
| Base de dades compartida que impedeix separar | Identificar taules per domini, vistes, doble escriptura progressiva |
| El monòlit "zombi" que mai no acaba de morir | Definir mètrica de progrés (rutes migrades) i data d'apagada |
| Explosió de complexitat operativa | Invertir abans en CI/CD, observabilitat i plataforma |
| Fronteres mal triades | Validar amb DDD (Bounded Contexts) abans de tallar |
El risc més subestimat és el de consistència de dades: dins del monòlit una transacció local ho resolia tot; en el distribuït necessites sagues i acceptar consistència eventual. No extreguis un servei si la seva capacitat participa en transaccions fortament acoblades amb d'altres, tret que estiguis preparat per gestionar la compensació.
- Errors Comuns i Consells
- Començar pel mòdul central. És el més embolicat. Comença pels perifèrics per guanyar experiència i confiança.
- Migrar sense observabilitat. Sense mètriques i traces no sabràs si el servei nou és millor o pitjor. Instrumenta abans de tallar.
- Compartir la base de dades "temporalment" per sempre. Una BD compartida entre el monòlit i el servei nou és un microservei distribuït en el pitjor dels dos mons. Tingues un pla real de separació.
- No fer servir feature flags. Sense commutació gradual ni reversió, cada extracció torna a ser un mini big bang.
- Oblidar-se d'esborrar el codi vell. Si no elimines la implementació llegada després de commutar, acumules deute i confusió.
- Consell: tracta cada extracció com un experiment amb hipòtesi ("aquest servei baixarà la latència de pagaments un 30%"). Si no es compleix, planteja't revertir.
- Exercicis
Exercici 1. Tens un monòlit de comerç electrònic amb els mòduls: Catàleg, Cistella, Pagaments, Client i Recomanacions. Quin extrauries primer i quin deixaries per al final? Justifica-ho amb els criteris de costures.
Exercici 2. El mòdul de "Notificacions" instancia directament new EmailSender() en 12 llocs. Descriu com aplicaries Branch by Abstraction per poder substituir-lo per un microservei de notificacions, pas a pas.
Exercici 3. El servei nou de "Facturació" ha de llegir clients del llegat, la taula del qual fa servir la columna EST amb valors A, B, C que signifiquen actiu, baixa temporal i baixa definitiva. Explica quina peça faries servir i per què, i esbossa la traducció.
Solucions
Solució 1. Primer Recomanacions: baix acoblament (és un afegit), dades relativament independents, i si falla no trenca la compra. Després Catàleg o Pagaments. Client al final: és el mòdul central, gairebé tot en depèn, i separar-ne les dades és el més arriscat.
Solució 2. (1) Crear la interfície ServicioNotificaciones. (2) Implementar-la amb NotificacionesLocal que embolcalli l'EmailSender actual sense canviar la seva lògica. (3) Reemplaçar les 12 instanciacions per injecció de ServicioNotificaciones. (4) Crear NotificacionesRemoto que cridi per xarxa el microservei. (5) Commutar amb feature flag de forma gradual. (6) Esborrar NotificacionesLocal i EmailSender quan el 100% faci servir la versió remota de forma estable.
Solució 3. Una Anti-Corruption Layer, perquè el servei de Facturació no hereti la columna críptica EST. L'ACL tradueix: A → EstadoCliente.ACTIVO, B → EstadoCliente.BAJA_TEMPORAL, C → EstadoCliente.BAJA_DEFINITIVA, exposant un enum clar al model net. Si demà el llegat canvia els codis, només es toca l'ACL.
- Conclusió
Migrar de monòlit a microserveis no és un salt, sinó un degoteig. El patró Strangler Fig manté el sistema viu i en producció mentre se li extreuen capacitats una a una a través d'una façana d'enrutament. Hem vist com identificar costures netes, com crear-les amb Branch by Abstraction i feature flags, com protegir el model nou amb una Anti-Corruption Layer, i quins són els riscos —sobretot els de dades i consistència— que cal anticipar. Tot canvi incremental d'aquesta envergadura genera moltes decisions que convé registrar i justificar perquè l'equip no les oblidi ni les repeteixi. Precisament d'això tracta la lliçó següent: la Governança Arquitectònica i els Architecture Decision Records (ADR), on aprendrem a documentar i governar les decisions d'arquitectura de forma lleugera i sostenible.
Curs d'Arquitectura d'Aplicacions
Mòdul 1: Fonaments de l'Arquitectura d'Aplicacions
- Què és l'Arquitectura d'Aplicacions?
- El Rol de l'Arquitecte de Programari
- Atributs de Qualitat i Requisits No Funcionals
- Decisions Arquitectòniques i Compromisos (Trade-offs)
- Documentació d'Arquitectura: Vistes i el Model C4
Mòdul 2: Principis i Tàctiques de Disseny
- Acoblament, Cohesió i Separació de Responsabilitats
- Principis SOLID Aplicats a l'Arquitectura
- DRY, KISS, YAGNI i Altres Principis de Disseny
- Tàctiques Arquitectòniques per als Atributs de Qualitat
- Gestió del Deute Tècnic
Mòdul 3: Estils i Patrons Arquitectònics
- Arquitectura Monolítica
- Arquitectura en Capes (N-Tier)
- Arquitectura Client-Servidor
- Arquitectura Hexagonal (Ports i Adaptadors)
- Arquitectura Neta i Ceba (Clean & Onion)
Mòdul 4: Arquitectures Distribuïdes i Microserveis
- Introducció als Sistemes Distribuïts
- Arquitectura de Microserveis
- Descomposició de Serveis i Bounded Contexts
- API Gateway, Service Discovery i Comunicació entre Serveis
- Patrons de Resiliència: Circuit Breaker, Retry i Bulkhead
- El Teorema CAP i la Consistència de Dades
Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria
- Fonaments de l'Arquitectura Orientada a Esdeveniments
- Missatgeria Asíncrona: Cues i Brokers
- Patrons d'Esdeveniments: Event Sourcing i CQRS
- Gestió de Transaccions Distribuïdes: Patró Saga
- Streaming de Dades en Temps Real
Mòdul 6: Disseny Dirigit pel Domini (DDD)
- Conceptes Fonamentals del DDD
- Disseny Estratègic: Bounded Contexts i Llenguatge Ubic
- Disseny Tàctic: Entitats, Agregats i Repositoris
- Mapatge de Contextos (Context Mapping)
Mòdul 7: Dades i Persistència
- Estratègies de Persistència: SQL vs NoSQL
- Patrons d'Accés a Dades: Repository, Unit of Work i DAO
- Base de Dades per Servei i Gestió de Dades Distribuïdes
- Cau i Estratègies d'Invalidació
Mòdul 8: Arquitectura al Núvol i Desplegament
- Fonaments del Cloud Computing (IaaS, PaaS, SaaS)
- Contenidors i Orquestració amb Docker i Kubernetes
- Arquitectura Serverless
- Patrons de Disseny Cloud-Native
- Infraestructura com a Codi (IaC)
Mòdul 9: Qualitat, Seguretat i Observabilitat
- Escalabilitat: Horitzontal vs Vertical i Balanceig de Càrrega
- Alta Disponibilitat i Tolerància a Fallades
- Seguretat per Disseny i Autenticació/Autorització
- Observabilitat: Logging, Mètriques i Traçabilitat
- Rendiment i Proves de Càrrega
