Dal monolite ai microservizi
Decida cosa suddividere e come definire i confini dei servizi
Dal monolite ai microservizi è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.
Dal monolite ai microservizi
I microservizi sono una scelta organizzativa e operativa, non una soluzione predefinita. Suddividere un monolite PHP significa rinunciare alla semplicità delle chiamate in-process in cambio di chiamate di rete, dati distribuiti e complessità di deployment. Se lo si fa per i motivi sbagliati, tutto diventa più lento.
Questa lezione riguarda la parte più difficile: definire i confini dei servizi. Se i confini sono corretti, il resto è semplice infrastruttura; se sono sbagliati, si costruisce un monolite distribuito: tutti i costi e nessun vantaggio.
Perché (e perché non) suddividere
Buoni motivi per suddividere:
- Possibilità di deployment indipendente e responsabilità dei team.
- Scalabilità indipendente dei sottosistemi più sollecitati.
- Isolamento della tecnologia e del runtime, oltre al contenimento dei guasti.
Cattivi motivi: "il codice è disordinato" (prima effettui il refactoring del monolite) o il semplice inseguimento di una moda. Se due servizi devono sempre essere distribuiti insieme o condividono una tabella del database, sono un unico servizio che svolge due ruoli.
Contesti delimitati
Il Domain-Driven Design offre lo strumento più preciso per definire i confini: il bounded context. All'interno di un contesto, i termini hanno un unico significato preciso. "Cliente" nella Fatturazione (il destinatario di una fattura) è diverso da "Cliente" nell'Assistenza (l'autore di un ticket).
Ogni bounded context è un candidato ideale per diventare un servizio. I confini seguono il linguaggio del business e il modo in cui l'organizzazione comunica realmente: è la Legge di Conway in azione.
Alta coesione, basso accoppiamento
Un buon confine di servizio mantiene al suo interno gli elementi che cambiano insieme ed esclude quelli che cambiano indipendentemente. Valuti una possibile suddivisione in base a:
- Quante funzionalità richiedono di intervenire su due servizi contemporaneamente? (Dovrebbero essere poche.)
- Quanto è verboso il modello di chiamata tra i servizi? (Dovrebbe essere a grana grossa.)
Se per implementare una funzionalità è necessario oltrepassare continuamente un confine, il confine si trova nel posto sbagliato.
<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
$product = $catalogApi->get($item->productId); // one call PER item!
$names[] = $product['name'];
}
// Coarse-grained: one batch call across the boundary
$ids = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";Un database per servizio
La regola inderogabile è la seguente: ogni servizio possiede i propri dati e nessun altro servizio accede alle sue tabelle. I database condivisi ricreano l'accoppiamento dal quale si cercava di liberarsi: una modifica allo schema interrompe servizi non correlati.
Di conseguenza, le query tra servizi che nel monolite erano un SQL JOIN diventano chiamate API o read model replicati. Questo è il prezzo da pagare, ed è proprio il punto.
<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
FROM orders o JOIN customers c ON c.id = o.customer_id';
// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id); // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";Il pattern Strangler Fig
Non riscriva mai un monolite tutto in una volta. Il pattern Strangler Fig estrae le funzionalità in modo incrementale: instradi una parte del traffico verso un nuovo servizio attraverso una facade o un proxy, lo fai crescere e dismetti il vecchio percorso del codice solo quando quello nuovo lo copre completamente.
Un reverse proxy (o un API gateway) si trova davanti ai servizi e decide, per ogni route, se chiamare il monolite legacy o il nuovo servizio. La migrazione procede una capacità alla volta e ogni passaggio può sempre essere rilasciato.
<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
$migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
foreach ($migrated as $prefix) {
if (str_starts_with($path, $prefix)) {
return 'http://billing-svc' . $path;
}
}
return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";Estrazione di un modulo
Un ordine pratico per l'estrazione:
- Individui un modulo con poche dipendenze in ingresso e una chiara titolarità dei dati.
- Racchiuda innanzitutto le sue chiamate in-process dietro un'interfaccia nel monolite.
- Sposti i dati di sua proprietà in uno schema o DB dedicato.
- Sostituisca l'implementazione dell'interfaccia con un client di rete.
- Trasferisca il traffico tramite la facade; elimini il vecchio codice.
Racchiudere prima l'implementazione dietro un'interfaccia all'interno del monolite riduce i rischi del passaggio alla rete.
<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
public function generate(string $orderId): string; // returns invoice id
}
// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
public function generate(string $orderId): string { return 'inv-1'; }
}Coerenza dei dati distribuiti
Una volta separati i dati, si perdono le transazioni ACID tra servizi. Accetti la consistenza eventuale: i servizi pubblicano eventi relativi ai propri dati e gli altri costruiscono read model locali a partire da tali eventi.
Il servizio Orders non interroga Customers a ogni richiesta: mantiene una piccola proiezione (solo i campi necessari), aggiornata dagli eventi CustomerUpdated. In questo modo si elimina una dipendenza a runtime e un passaggio aggiuntivo che introduce latenza.
<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
$db->prepare(
'INSERT INTO customer_read_model (id, email)
VALUES (:id, :email)
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
)->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}La realtà operativa
I microservizi spostano la complessità dal codice alle operazioni. Prima di suddividere il sistema, sono necessari:
- Logging centralizzato e tracing distribuito (ID di correlazione attraverso tutti i passaggi).
- CI/CD per ogni servizio e deployment indipendenti con versionamento.
- Controlli di salute, timeout, retry e circuit breaker per ogni chiamata.
- Contract test, affinché un provider non possa interrompere silenziosamente un consumer.
Se il team non riesce a gestire bene un solo servizio, con dieci servizi la situazione sarà dieci volte peggiore.
Dimensionamento adeguato dei servizi
Il termine "micro" è fuorviante: dimensioni i servizi in base alla capacità aziendale e alla responsabilità del team, non al numero di righe di codice. Se la granularità è eccessiva (nanoservices), una singola funzionalità genera una raffica di chiamate di rete; se è troppo ridotta, si torna ad avere un monolite.
Una buona regola pratica è che un servizio deve poter essere gestito da un solo team, distribuito autonomamente e in grado di soddisfare i propri casi d'uso principali senza una catena sincrona che attraversi molti servizi.
L'alternativa del monolite modulare
Prima di passare a un sistema distribuito, consideri il monolite modulare: un'unica unità distribuibile, suddivisa internamente in moduli con confini espliciti e schemi propri, che comunicano esclusivamente tramite interfacce pubblicate, senza accesso alle tabelle degli altri moduli.
Si ottengono confini chiari e refactoring semplici, senza il costo dei sistemi distribuiti. Quando un modulo ha davvero bisogno di scalabilità o responsabilità indipendenti, è già strutturato per essere estratto tramite il pattern Strangler Fig. Per la maggior parte dei team, questo è il punto di partenza corretto.
<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing; // owns billing_* tables
interface BillingFacade {
public function invoiceForOrder(string $orderId): string;
}
namespace App\Sales; // owns sales_* tables
final class Checkout {
public function __construct(private \App\Billing\BillingFacade $billing) {}
// Sales never SELECTs from billing_* directly - only via the facade.
}Verifica rapida
Riconoscere una suddivisione sbagliata.
Riepilogo
Definire correttamente i confini dei servizi:
- Suddivida per possibilità di deployment, scalabilità e responsabilità, non perché il codice è disordinato.
- Allinei i confini ai bounded context; punti a un'alta coesione e a un basso accoppiamento.
- Un database per servizio: nessuna tabella condivisa; sostituisca i JOIN con API o read model.
- Effettui la migrazione in modo incrementale con il pattern Strangler Fig.
- Accetti la consistenza eventuale e investa nelle operazioni prima di aumentare il numero dei servizi.
Prossimo argomento: come comunicano concretamente questi servizi, tramite REST e gRPC.
Domande Frequenti
La lezione «Dal monolite ai microservizi» è gratuita?
Sì — il testo completo di «Dal monolite ai microservizi» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.
Cosa imparerò in «Dal monolite ai microservizi»?
Decida cosa suddividere e come definire i confini dei servizi Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare PHP Academy?
Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.
Quanto tempo richiede la lezione «Dal monolite ai microservizi»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione PHP Academy?
Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Dal monolite ai microservizi
- Comunicazione tra servizi: REST e gRPC
- API Gateway e individuazione dei servizi
- Resilienza: circuit breaker e retry