Od monolitu do mikroserwisów
Zdecyduj, co podzielić i jak wyznaczyć granice usług
Od monolitu do mikroserwisów to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Od monolitu do mikrousług
Mikrousługi to decyzja organizacyjna i operacyjna, a nie domyślny wybór. Dzielenie monolitu PHP oznacza rezygnację z prostoty wywołań wewnątrz procesu na rzecz wywołań sieciowych, rozproszonych danych i złożoności wdrażania. Wykonane z niewłaściwych powodów spowalnia wszystko.
Ta lekcja dotyczy najtrudniejszego elementu: wyznaczania granic usług. Jeśli punkty podziału zostaną wyznaczone właściwie, reszta jest tylko kwestią infrastruktury; jeśli nie, powstaje monolit rozproszony — cały koszt i żadnych korzyści.
Dlaczego dzielić (i dlaczego nie)
Dobre powody, aby dzielić system:
- Niezależne wdrażanie i odpowiedzialność zespołów.
- Niezależne skalowanie najbardziej obciążonych podsystemów.
- Izolacja technologii i środowiska uruchomieniowego oraz ograniczanie skutków awarii.
Złe powody to „nieuporządkowany kod” (najpierw należy zrefaktoryzować monolit) albo pogoń za trendem. Jeśli dwie usługi zawsze muszą być wdrażane razem lub współdzielić tabelę bazy danych, są jedną usługą w dwóch rolach.
Ograniczone konteksty
Domain-Driven Design dostarcza najlepszego narzędzia do wyznaczania granic: ograniczonego kontekstu. W obrębie jednego kontekstu terminy mają jedno precyzyjne znaczenie. „Klient” w kontekście Rozliczeń (adresat faktury) różni się od „Klienta” w kontekście Wsparcia (autora zgłoszenia).
Każdy ograniczony kontekst jest dobrym kandydatem na usługę. Granice powinny wynikać z języka biznesowego i rzeczywistego sposobu komunikacji w organizacji — w praktyce jest to prawo Conwaya.
Wysoka spójność, niskie sprzężenie
Dobra granica usługi pozostawia wewnątrz elementy, które zmieniają się razem, a na zewnątrz wypycha te, które zmieniają się niezależnie. Proponowany podział należy oceniać na podstawie następujących kwestii:
- Ile funkcji wymaga jednoczesnego modyfikowania dwóch usług? (Powinno ich być niewiele.)
- Jak intensywna jest komunikacja między usługami? (Powinna odbywać się na wysokim poziomie ogólności.)
Jeśli implementacja jednej funkcji stale przekracza wyznaczoną granicę, granica znajduje się w niewłaściwym miejscu.
<?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";Baza danych na usługę
Bezwzględna zasada brzmi: każda usługa jest właścicielem swoich danych i żadna inna usługa nie może uzyskiwać dostępu do jej tabel. Współdzielone bazy danych odtwarzają sprzężenie, którego chcieli Państwo uniknąć, dzieląc system — zmiana schematu psuje niezwiązane usługi.
Oznacza to, że zapytania między usługami, które w monolicie były operacjami SQL JOIN, stają się wywołaniami API lub korzystają z replikowanych modeli odczytu. Taka jest cena — i właśnie o to chodzi.
<?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";Wzorzec strangler fig
Nigdy nie należy przepisywać monolitu w ramach jednego, całościowego projektu. Wzorzec strangler fig umożliwia stopniowe wydzielanie: część ruchu jest kierowana przez fasadę lub proxy do nowej usługi, która jest następnie rozwijana, a stara ścieżka kodu jest wycofywana dopiero wtedy, gdy nowa w pełni ją zastąpi.
Reverse proxy (lub API gateway) znajduje się z przodu systemu i dla każdej trasy decyduje, czy skierować żądanie do starszego monolitu, czy do nowej usługi. Migracja przebiega po jednej funkcji naraz, a każdą zmianę można wdrożyć.
<?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";Wydzielanie modułu
Pragmatyczna kolejność wydzielania:
- Znaleźć moduł z niewieloma zależnościami przychodzącymi i jasno określoną własnością danych.
- Najpierw opakować jego wywołania wewnątrz procesu za interfejsem w monolicie.
- Przenieść należące do niego dane do osobnego schematu lub bazy danych.
- Zastąpić implementację interfejsu klientem sieciowym.
- Przełączyć ruch za pomocą fasady i usunąć stary kod.
Opakowanie wywołań za interfejsem najpierw wewnątrz monolitu zmniejsza ryzyko związane z przejściem na sieć.
<?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'; }
}Spójność rozproszonych danych
Po rozdzieleniu danych tracą Państwo możliwość korzystania z transakcji ACID między usługami. Należy zaakceptować spójność ostateczną: usługi publikują zdarzenia dotyczące własnych danych, a pozostałe budują na ich podstawie lokalne modele odczytu.
Usługa Orders nie odpytuje usługi Customers przy każdym żądaniu — przechowuje niewielką projekcję (tylko potrzebne pola), aktualizowaną przez zdarzenia CustomerUpdated. Eliminuje to zależność w czasie działania i jedno przejście sieciowe.
<?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']]);
}Rzeczywistość operacyjna
Mikrousługi przenoszą złożoność z kodu do obszaru operacji. Przed podziałem systemu potrzebne są:
- Scentralizowane logowanie i śledzenie rozproszone (identyfikatory korelacji przekazywane przez kolejne usługi).
- CI/CD dla każdej usługi i niezależne wdrożenia z wersjonowaniem.
- Kontrole stanu, limity czasu, ponowienia i mechanizmy circuit breaker przy każdym wywołaniu.
- Testy kontraktowe, aby dostawca nie mógł po cichu zepsuć odbiorcy.
Jeśli zespół nie potrafi dobrze uruchomić jednej usługi, dziesięć będzie dziesięć razy większym problemem.
Dobór właściwego rozmiaru usług
Określenie „micro” może być mylące — rozmiar usług należy wyznaczać na podstawie możliwości biznesowych i odpowiedzialności zespołu, a nie liczby wierszy kodu. Przy zbyt drobnym podziale (nanousługi) jedna funkcja rozchodzi się na burzę wywołań sieciowych; przy zbyt dużym podziale wracają Państwo do monolitu.
Zdrowa zasada mówi, że za usługę powinien móc odpowiadać jeden zespół, powinna dać się wdrażać niezależnie i realizować swoje główne przypadki użycia bez synchronicznego łańcucha wywołań przez wiele usług równorzędnych.
Alternatywa w postaci monolitu modułowego
Przed przejściem do systemu rozproszonego warto rozważyć monolit modułowy: jeden wdrażany artefakt, ale wewnętrznie podzielony na moduły z wyraźnymi granicami i własnymi schematami, komunikujące się wyłącznie za pośrednictwem opublikowanych interfejsów — bez dostępu do tabel innych modułów.
Zyskują Państwo czyste granice i łatwe refaktoryzowanie bez narzutu systemów rozproszonych. Gdy moduł rzeczywiście wymaga niezależnego skalowania lub odpowiedzialności, jego struktura jest już przygotowana do wydzielenia za pomocą wzorca strangler. Dla większości zespołów jest to właściwy pierwszy krok.
<?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.
}Szybki test
Rozpoznawanie złego podziału.
Podsumowanie
Dobre wyznaczanie granic usług:
- Dzielić system ze względu na możliwość wdrażania, skalowanie i odpowiedzialność — nie dlatego, że kod jest nieuporządkowany.
- Dopasowywać granice do ograniczonych kontekstów; dążyć do wysokiej spójności i niskiego sprzężenia.
- Baza danych na usługę — bez współdzielonych tabel; zastępować operacje JOIN wywołaniami API lub modelami odczytu.
- Przeprowadzać migrację stopniowo za pomocą wzorca strangler fig.
- Akceptować spójność ostateczną i inwestować w obszar operacji przed skalowaniem systemu.
Następnie: jak te usługi faktycznie się komunikują — REST i gRPC.
Często zadawane pytania
Czy lekcja „Od monolitu do mikroserwisów” jest bezpłatna?
Tak — pełny tekst „Od monolitu do mikroserwisów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Od monolitu do mikroserwisów”?
Zdecyduj, co podzielić i jak wyznaczyć granice usług Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Od monolitu do mikroserwisów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Od monolitu do mikroserwisów
- Komunikacja usług: REST i gRPC
- Bramy API i wykrywanie usług
- Odporność: circuit breakery i ponowienia