MACH (Microservices, API-first, Cloud-native, Headless) è un insieme di principi per comporre un e-commerce da servizi indipendenti. Qui vedi che cosa chiede ciascuna lettera, che cosa costa e come arrivarci senza riscrivere tutto.
MACH: quattro principi per comporre un negozio da pezzi
MACH è un acronimo usato dalla MACH Alliance, un'organizzazione di fornitori di software: non è un prodotto né uno standard con una certificazione, è una famiglia di principi. Le lettere sono Microservices, API-first, Cloud-native e Headless. L'idea che le tiene insieme si chiama composable: invece di comprare una suite unica che fa tutto in modo mediocre, scegli per ogni funzione il servizio più adatto (ricerca, pagamenti, contenuti, gestione dell'anagrafica prodotti) e li colleghi tramite API.
Un paragone dalla cucina: una cucina componibile ha elementi con misure standard e puoi cambiare il forno senza rifare il mobile; una cucina monolitica è un blocco unico che costa meno finché non si rompe qualcosa. Ma nella cucina componibile qualcuno deve garantire che le misure coincidano, e nel software quel qualcuno sei tu. È il costo nascosto di MACH e ritornerà in ogni sezione.
Microservices: ogni servizio possiede i propri dati
Martin Fowler e James Lewis, nell'articolo del 2014 che ha reso popolare il termine, descrivono un microservizio come un servizio piccolo, con il proprio processo, costruito attorno a una capacità di business e rilasciabile in modo indipendente. In un negozio le capacità sono catalogo, prezzi, giacenze, carrello, ordini, pagamenti, spedizioni. Ciascuna ha un proprio confine (in linguaggio Domain-Driven Design, un bounded context): dentro il confine "prodotto" significa una cosa precisa, e fuori se ne usa solo una copia.
La conseguenza pratica più importante è che ogni servizio ha il proprio archivio dati. Il listato mostra perché conta: due archivi separati, uno dei prezzi e uno degli ordini, e l'ordine che copia il prezzo unitario nel momento dell'acquisto invece di rileggerlo dopo. Il servizio degli ordini non conosce il servizio dei prezzi, quindi non può interrogarne il database; se potesse, sarebbe tentato di ricalcolare il totale con il listino di oggi e un ordine passato cambierebbe importo.
// Ogni servizio ha il proprio archivio. Il servizio prezzi non sa nulla degli ordini, e viceversa.
const archivioPrezzi = new Map<string, number>([['moka-3', 2400]]); // centesimi
type RigaOrdine = { sku: string; quantita: number; prezzoUnitarioCents: number };
const archivioOrdini: { id: number; righe: RigaOrdine[] }[] = [];
function creaOrdine(sku: string, quantita: number) {
// Il prezzo si COPIA nell'ordine al momento dell'acquisto: l'ordine non lo rilegge piu'.
const righe = [{ sku, quantita, prezzoUnitarioCents: archivioPrezzi.get(sku)! }];
archivioOrdini.push({ id: archivioOrdini.length + 1, righe });
}
const totale = (righe: RigaOrdine[]) => righe.reduce((s, r) => s + r.quantita * r.prezzoUnitarioCents, 0);
creaOrdine('moka-3', 2);
archivioPrezzi.set('moka-3', 2900); // il listino cambia il giorno dopo
creaOrdine('moka-3', 1);
for (const o of archivioOrdini) console.log(`ordine ${o.id}: ${totale(o.righe) / 100} EUR`);
// Output:
// ordine 1: 48 EUR
// ordine 2: 29 EUR
Il secondo ordine costa 29 euro e il primo resta a 48 (due unità da 24) anche se il listino è salito nel frattempo: la riga prezzoUnitarioCents: archivioPrezzi.get(sku)!
copia il valore invece di conservare un riferimento. È l'accettazione di una duplicazione voluta, e ha un prezzo: se il prezzo di un prodotto viene corretto per errore, nessun ordine si aggiorna da solo e serve una procedura esplicita. Il listato è un modello in memoria, eseguito con Node 24, per mostrare il principio:
un sistema reale lo implementa con archivi e transazioni distinti.
API-first: il contratto prima del codice
API-first vuol dire scrivere la descrizione dell'interfaccia prima di implementarla, così chi costruisce il frontend e chi costruisce il servizio possono lavorare in parallelo sullo stesso accordo.
Lo strumento più diffuso per REST è la OpenAPI Specification (qui la 3.1.0, pubblicata il 15 febbraio 2021): un file YAML o JSON che elenca percorsi, parametri, risposte e schemi.
Il listato descrive un servizio di catalogo con due operazioni: l'elenco paginato per cursore e il dettaglio di un prodotto, con la risposta 404 definita una volta in components e riusata con $ref.
openapi: 3.1.0
info:
title: Catalog Service API
version: 2.0.0
paths:
/products:
get:
summary: Elenco prodotti, paginato per cursore
parameters:
- name: cursor
in: query
schema: { type: string }
- name: category
in: query
schema: { type: string }
responses:
'200':
description: Una pagina di prodotti
content:
application/json:
schema: { $ref: '#/components/schemas/ProductPage' }
/products/{id}:
get:
summary: Dettaglio di un prodotto
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200':
description: Il prodotto
content:
application/json:
schema: { $ref: '#/components/schemas/Product' }
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
Product:
type: object
required: [id, title, priceCents, currency]
properties:
id: { type: string }
title: { type: string }
priceCents: { type: integer }
currency: { type: string, minLength: 3, maxLength: 3 }
ProductPage:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Product' }
nextCursor: { type: [string, 'null'] }
responses:
NotFound:
description: Prodotto inesistente
# Output:
# openapi 3.1.0, 2 percorsi, problemi: 0
Il file l'ho controllato con un piccolo script che lo legge con js-yaml e verifica due regole: ogni $ref deve puntare a un nodo esistente, e ogni segnaposto come {id} nel percorso
deve essere dichiarato come parametro in: path. È un controllo sintattico, non una validazione completa contro lo schema OpenAPI: per quella serve un validatore dedicato. Il secondo controllo non è
pedanteria: togliendo la dichiarazione del parametro id lo script segnala /products/{id}: parametro {id} non dichiarato: un percorso con un segnaposto senza parametro è un contratto incoerente, che i generatori di client rifiutano o interpretano ciascuno a modo suo.
Un contratto vale solo se cambia con regole. La specifica Semantic Versioning dice di incrementare la versione maggiore quando le modifiche all'API sono incompatibili, la minore quando aggiungi funzioni in modo retrocompatibile
e la patch per le correzioni. Dichiararlo nel campo info.version non basta, ma dà al team che usa l'API un segnale preciso su che cosa rischia aggiornando.
Cloud-native: costruito per ambienti elastici
Cloud-native non significa «gira su un cloud»: significa che il servizio assume di poter essere avviato e fermato in qualunque momento, in più copie, su macchine che non conosce. Ne derivano abitudini concrete: nessuno stato dentro il processo (la sessione sta in un archivio condiviso), configurazione presa dall'ambiente, rilasci automatizzati, e comunicazione asincrona tramite eventi quando una risposta immediata non serve. Per un negozio, un esempio chiaro: la conferma d'ordine non deve aspettare l'invio dell'email; l'ordine pubblica un evento e il servizio di notifica lo legge quando può.
Il costo da non sottovalutare è che ogni confine tra servizi è una rete, e la rete si guasta. Le richieste vanno fatte con scadenze e tentativi limitati, e va deciso in anticipo che cosa mostrare se il servizio dei consigliati non risponde (di solito: niente consigliati, ma la pagina funziona). Fowler e Lewis lo chiamano design for failure nello stesso articolo citato sopra.
Headless e il livello BFF: una richiesta, molti servizi
La lettera H l'hai vista nel primo articolo: il frontend consuma API e non è generato dal backend. In una architettura a molti servizi, però, la pagina di un catalogo richiede dati da più di uno: prodotti, filtri laterali, consigliati. Chiederli dal browser uno a uno significa tante richieste su rete mobile (l'effetto sulle metriche di velocità è nell'articolo su Core Web Vitals e SEO); per questo si inserisce un BFF (Backend for Frontend), un servizio piccolo, scritto per un singolo frontend, che raccoglie i dati dai servizi e restituisce quello che serve alla pagina.
Il listato simula tre servizi con ritardi di 80, 120 e 60 millisecondi e li chiama in due modi. In sequenza le attese si sommano (circa 260 ms); con Promise.all partono insieme e il tempo totale
è quello del più lento (circa 120 ms). Le tre chiamate sono indipendenti tra loro, ed è per questo che si possono parallelizzare: se la seconda avesse bisogno del risultato della prima, non si potrebbe.
// Tre servizi finti: ciascuno risponde dopo un ritardo fisso, come farebbe una chiamata di rete.
const attesa = (ms: number) => new Promise((r) => setTimeout(r, ms));
const catalogo = async () => (await attesa(80), ['moka-3', 'macinacaffe']);
const faccette = async () => (await attesa(120), { tostatura: ['chiara', 'scura'] });
const consigliati = async () => (await attesa(60), ['filtro-v60']);
async function inSequenza() {
const prodotti = await catalogo();
const filtri = await faccette();
const correlati = await consigliati();
return { prodotti, filtri, correlati };
}
async function inParallelo() {
const [prodotti, filtri, correlati] = await Promise.all([catalogo(), faccette(), consigliati()]);
return { prodotti, filtri, correlati };
}
async function misura(nome: string, f: () => Promise<unknown>) {
const t0 = performance.now();
await f();
console.log(`${nome}: ${Math.round(performance.now() - t0)} ms`);
}
await misura('in sequenza', inSequenza);
await misura('in parallelo', inParallelo);
// Output:
// in sequenza: 260 ms
// in parallelo: 120 ms
I numeri sono la somma e il massimo dei ritardi che ho scelto io, non una misura di rete: servono a vedere la regola, e cambiano di qualche millisecondo da un’esecuzione all'altra (in tre prove ho letto 260-261 e 120-121 ms).
Nel mondo reale i ritardi sono variabili, e il caso peggiore è il servizio lento: Promise.all attende comunque il più lento, e se uno dei tre fallisce la promessa intera si rifiuta. Per i dati non essenziali,
come i consigliati, conviene Promise.allSettled e una pagina degradata invece di un errore.
Dove MACH va storto
Gli errori ricorrenti nascono tutti dal frammentare troppo e troppo presto. Fowler, nel breve testo Monolith First, osserva che quasi tutte le storie di microservizi riuscite sono partite da un monolite diventato troppo grande e poi suddiviso, mentre i sistemi progettati a microservizi da zero hanno incontrato più spesso difficoltà. È un’osservazione di un singolo autore, non una legge, ma indica da dove conviene partire quando il negozio è piccolo.
- Servizi prematuri: venti servizi per un negozio con pochi ordini sono venti sistemi da rilasciare e monitorare, per un guadagno che non esiste ancora.
- Contratti senza versione: se un team cambia un campo senza dirlo, rompe quelli che lo consumano. Il file OpenAPI e la versione semantica sono la protezione.
- Chiamate a cascata: ogni richiesta che ne scatena altre in sequenza somma le latenze e le probabilità di guasto.
- Database condiviso: due servizi sullo stesso archivio sono in pratica un unico servizio con due codici, e non si possono più rilasciare separatamente.
L'ultimo è il più subdolo, perché all'inizio sembra un risparmio. Se per capire se un cambiamento è sicuro devi leggere il codice di un altro servizio, i confini non esistono: hai pagato il prezzo dei microservizi senza averne i vantaggi.
Arrivarci per gradi
Non si passa a MACH in una notte. Lo schema più citato per la migrazione graduale è lo Strangler Fig di Fowler: il nuovo sistema cresce attorno al vecchio e ne sostituisce una funzione alla volta, finché il vecchio può essere spento. Applicato a un negozio, il primo passo è mettere un frontend headless davanti al sistema esistente, usando le API che già espone. Il secondo è estrarre la funzione che ha l'esigenza più specifica, tipicamente la ricerca, in un servizio dedicato scelto sul mercato. Un altro candidato frequente sono i contenuti editoriali, di cui parla l'articolo sul content commerce. Poi si prosegue una funzione alla volta, ma solo finché ogni passo ha una ragione che sai misurare: se la ricerca andava bene com'era, lasciala dov'è.
Che cosa sai fare adesso
Sai distinguere le quattro lettere di MACH e il problema che ciascuna risolve, scrivere un contratto OpenAPI controllabile, spiegare perché un servizio non deve leggere il database di un altro, e usare un BFF con chiamate parallele per costruire una pagina dai dati di più servizi. Soprattutto sai che il confine dei servizi si traccia dove il negozio ha esigenze diverse, non dove sembra elegante. Nei prossimi capitoli applichiamo l'idea a piattaforme concrete, a partire da Shopify Hydrogen, e proseguiamo con le due piattaforme open source: Medusa e Saleor.
Fonti
- MACH Alliance - sito dell'organizzazione che usa e promuove il termine MACH; consultato il 2026-11-12
- Microservices (James Lewis e Martin Fowler, 2014) - definizione di microservizio, dati decentralizzati, design for failure; consultata il 2026-11-12
- Monolith First (Martin Fowler, 2015) - osservazione sulle storie di microservizi riuscite; consultata il 2026-11-12
- Strangler Fig (Martin Fowler, 2024) - migrazione graduale di un sistema esistente; consultata il 2026-11-12
- OpenAPI Specification v3.1.0 - struttura di percorsi, parametri, componenti e
$ref; consultata il 2026-11-12 - Semantic Versioning 2.0.0 - regole per incrementare maggiore, minore e patch; consultata il 2026-11-12

Commenti
Caricamento commenti...
Accedi per commentare