Decyzji między monolitem modułowym a mikroserwisami w PHP nie rozstrzyga liczba modułów, wiek kodu ani popularność architektury. Aplikacja biznesowa może rozwijać się zdrowo w ramach jednego wdrożenia, jeśli zachowuje jasne granice. Z kolei przedwczesny podział może przekształcić proste wywołania wewnętrzne w sieć kontraktów, kolejek, ponowień i problemów z koordynacją.
Użyteczne pytanie nie brzmi: „czy powinniśmy używać mikroserwisów?”, lecz: „która zdolność biznesowa musi ewoluować, ulegać awarii, być wdrażana lub skalowana niezależnie i czy możemy ponieść koszt jej takiego utrzymania?”. Odpowiedź powinna wynikać z domeny i rzeczywistej eksploatacji, a nie z docelowego diagramu.
Rozwój funkcjonalny nie wymaga oddzielnych usług

Dodawanie integracji, procesów asynchronicznych lub obszarów produktu nie oznacza, że każdy z nich musi mieć własną usługę. Monolit może zawierać dobrze wyodrębnione moduły, zadania w tle, kolejki i adaptery dla systemów zewnętrznych, nie tracąc spójności operacyjnej.
Pierwszą interwencją zwykle jest ograniczenie wewnętrznego sprzężenia. Jeśli moduł fakturowania importuje bezpośrednio klasy zamówień, modyfikuje ich tabele lub zna wewnętrzne reguły magazynowe, problem nie naprawi się automatycznie po przeniesieniu kodu do innego repozytorium. Zmieni się jedynie w sprzężenie sieciowe i danych.
Monolit modułowy zakłada, że każda zdolność ma wyraźny interfejs wewnętrzny, ukierunkowane zależności i własne reguły. W PHP można to zrealizować za pomocą przestrzeni nazw według domeny, kontraktów aplikacyjnych, cienkich kontrolerów, zdefiniowanych przypadków użycia oraz adapterów dla persystencji lub zewnętrznych API. Wdrażanie wszystkiego razem nadal pozostaje zgodne z tymi granicami.
Co przeanalizować przed zmianą architektury
Przed dyskusją o technologii zidentyfikuj zdolności biznesowe, na przykład: zarządzanie zamówieniami, katalog, tożsamość, fakturowanie, przetwarzanie dokumentów lub powiadomienia. Zdolność nie musi odpowiadać encji ani ekranowi; grupuje reguły i decyzje, które powinny zmieniać się z podobnych powodów.
- Właściciel: ustal, kto utrzymuje reguły, priorytetyzuje zmiany i odpowiada za incydenty.
- Dane: określ, jakie informacje tworzy i którymi zarządza każda zdolność, kto może je modyfikować oraz jakich odczytów przekrojowych potrzebuje.
- Krytyczne przepływy: rozrysuj przebieg istotnej operacji, uwzględniając walidacje, zależności zewnętrzne i kroki asynchroniczne.
- Tempo zmian: rozróżnij częste modyfikacje od sporadycznych zmian. Sama częstotliwość nie wystarcza; istotne jest, czy wymusza koordynację zespołów lub release’ów.
- Profil obciążenia: oddziel ruch interaktywny od zadań intensywnie wykorzystujących CPU, pamięć, przestrzeń dyskową lub wywołania do podmiotów trzecich.
- Wpływ awarii: ustal, co się dzieje, gdy dana zdolność degraduje się na minuty lub godziny, oraz czy rdzeń biznesu może nadal działać.
Ten inwentarz ujawnia zależności, które często pozostają ukryte: współdzielone transakcje, bezpośrednie zapytania do cudzych tabel, reguły zduplikowane w kontrolerach oraz zadania zaplanowane aktualizujące wiele domen. Wydzielenie bez ich rozwiązania tworzy formalnie oddzielne, lecz funkcjonalnie splecione usługi.
Sześć sygnałów, aby zachować monolit modułowy
Te sygnały przemawiają za wzmocnieniem wewnętrznego projektu zamiast rozproszenia odpowiedzialności:
- Zmiany zwykle przechodzą przez kilka modułów. Jeśli funkcja biznesowa wymaga skoordynowanej modyfikacji zamówień, cen i fakturowania, podział może zwielokrotnić liczbę wdrożeń i kontraktów.
- Natychmiastowa spójność jest kluczowa. Gdy operacja potrzebuje pojedynczej transakcji bazy danych, aby zachować krytyczne niezmienniki, granica rozproszona dodaje złożone decyzje dotyczące kompensacji.
- Zespół jest mały lub współdzieli odpowiedzialność. Wiele usług wymaga dyscypliny operacyjnej, dyżurów, pipeline’ów, wersjonowania i diagnostyki dla każdej jednostki.
- Obciążenie skaluje się podobnie. Jeżeli komponenty rosną w tym samym tempie i nie istnieje możliwe do odizolowania wąskie gardło, rozdzielenie nie przynosi wyraźnej korzyści.
- Granice domeny są nadal niestabilne. Wydzielenie zdolności, gdy jej reguły, słownictwo i odpowiedzialności stale się zmieniają, utrwala przedwczesną granicę.
- Obserwowalność i automatyzacja są ograniczone. Bez ustrukturyzowanych logów, metryk, śladów, alertów i powtarzalnych wdrożeń każdy skok sieciowy podniesie koszt badania incydentu.
Zachowanie monolitu nie oznacza akceptacji globalnego kodu. Celem jest, aby moduł mógł ewoluować z logiczną autonomią, nawet jeśli współdzieli proces, repozytorium i release z innymi.
Sześć sygnałów uzasadniających niezależną usługę
Wydzielenie jest łatwiejsze do obrony, gdy łączy się kilka z tych warunków, a nie gdy występuje tylko jeden:
- Istnieje ograniczona i zrozumiała odpowiedzialność. Usługa ma konkretną misję, spójne reguły i własny język domenowy.
- Może być właścicielem swoich danych. Zarządza własnym przechowywaniem danych i udostępnia uzgodnione operacje, zdarzenia lub zapytania, zamiast zezwalać na bezpośredni dostęp do swoich tabel.
- Rzeczywiście potrzebuje niezależnych wdrożeń. Jej cykl zmian musi postępować bez koordynowania każdego release’u z rdzeniem.
- Jej obciążenie jest odmienne. Proces konwersji, wyszukiwania, generowania plików lub intensywnych obliczeń może wymagać innego skalowania i zasobów.
- Jej awaria może zostać odizolowana. System może w sposób jawny działać w trybie ograniczonym, jeśli ta zdolność nie odpowiada, za pomocą ponowień, stanów oczekujących lub odroczonej pracy.
- Istnieje wystarczająca odpowiedzialność operacyjna. Zespół lub osoba odpowiedzialna może utrzymywać jej cykl życia, alerty, incydenty, bezpieczeństwo i kompatybilność.
API samo w sobie nie czyni modułu mikroserwisem. Niezależność zależy także od danych, wdrażania, eksploatacji i zdolności do podejmowania decyzji bez zależności od internals innej aplikacji.
Koszty pojawiające się przy rozdzielaniu odpowiedzialności
Wywołanie funkcji zawodzi inaczej niż wywołanie HTTP, komunikat w kolejce czy zdalne zapytanie. Po wydzieleniu pojawiają się opóźnienia, time-outy, uwierzytelnianie między usługami, limity szybkości, częściowa niedostępność i niekompatybilne wersje.
Zmienia się również model spójności. Jeżeli jedna usługa potwierdzi operację, a druga nie otrzyma lub nie przetworzy zdarzenia, trzeba zdecydować, jak wykryć stan, ponowić próbę bez dublowania skutków oraz kompensować, gdy jest to konieczne. Konsumenci zdarzeń muszą być idempotentni; na przykład dwukrotne przetworzenie komunikatu nie może wystawić dwóch dokumentów ani dwukrotnie pobrać płatności.
Eksploatacja zyskuje na złożoności: korelacja żądań, ślady rozproszone, panele metryk, retencja logów, zarządzanie sekretami, polityki backupu i testy odzyskiwania. Ponadto każdy kontrakt potrzebuje zasad kompatybilności. Dodawanie opcjonalnych pól jest zwykle mniej zakłócające niż zmiana semantyki, usuwanie pól lub ponowne użycie stanu o nowym znaczeniu.
Architektura przejściowa w PHP
Ścieżką o najmniejszym ryzyku jest modularyzacja przed wydzieleniem. Zdefiniuj warstwę aplikacyjną dla każdej zdolności, z przypadkami użycia przyjmującymi polecenia lub zapytania i zwracającymi dobrze zdefiniowane wyniki. Ukryj dostęp do bazy danych za repozytoriami lub portami, gdy reprezentuje to istotną zależność; nie przekształcaj każdej klasy w abstrakcję bez celu.
Reszta monolitu powinna korzystać z modułu przez jego wewnętrzny interfejs publiczny, a nie przez jego encje lub tabele. Jeśli potrzebne są powiadomienia asynchroniczne, publikuj zdarzenia domenowe lub integracyjne z kontrolowanego punktu. Wzorzec transactional outbox może pomóc zapisać zmianę biznesową i oczekujące zdarzenie w tej samej transakcji, aby późniejszy proces dostarczył je niezawodnie.
Ta faza pozwala sprawdzić, czy granica jest rzeczywista. Jeśli wewnętrzny interfejs stale rośnie, wymaga prywatnych obiektów innych modułów lub potrzebuje współdzielonych transakcji w każdym przypadku użycia, nadal nie jest solidnym kandydatem do rozdzielenia.
Jak zdefiniować pierwszą granicę usługi
Pierwsza usługa powinna mieć odpowiedzialność łatwą do wyjaśnienia i ograniczoną zależność od rdzenia. Udokumentuj cztery elementy przed napisaniem infrastruktury:
- Odpowiedzialność: jakie decyzje podejmuje i które pozostają wyraźnie poza jej zakresem.
- API lub zdarzenia: wejścia, wyjścia, błędy, uwierzytelnianie, limity czasu i idempotencja.
- Własność danych: co przechowuje, jakie identyfikatory zewnętrzne zachowuje i jakie informacje odczytuje za pośrednictwem kontraktów.
- Kompatybilność: jak producenci i konsumenci będą współistnieć podczas zmian wersji, w tym w przypadku opóźnionych komunikatów.
Unikaj projektowania API jako odbicia tabel. Kontrakt powinien wyrażać operacje lub fakty biznesowe, a nie ujawniać szczegóły persystencji, które zablokują późniejsze zmiany.
Przykład: przetwarzanie dokumentów bez fragmentowania backoffice
Rozważ backoffice PHP, który zarządza sprawami i musi generować, walidować oraz przechowywać dokumenty. Początkowo przetwarzanie może działać jako moduł wewnętrzny: przyjmuje żądanie wygenerowania, rejestruje zadanie, wykonuje zadanie asynchroniczne i aktualizuje stan widoczny dla użytkownika.
Wydzielenie staje się uzasadnione, jeśli generowanie zużywa bardzo odmienne zasoby, potrzebuje specyficznych zależności do konwersji, otrzymuje własne piki i może działać z żądaniem dokumentowym zawierającym minimalne niezbędne dane. Usługa dokumentowa nie powinna swobodnie odpytywać tabel sprawy. Backoffice może wysłać polecenie z identyfikatorem, mającym zastosowanie szablonem, wersją danych i miejscem docelowym; wynik wraca jako zdarzenie lub stan, który można odpytać.
Wcześniej warto wyjaśnić, że szablon definiuje strukturę wyjściową, podczas gdy model może odnosić się do danych domenowych lub systemu AI. Gdyby wykorzystano AI do klasyfikowania dokumentów, potrzebne byłyby: ograniczony przypadek użycia, ocena na reprezentatywnych danych, przegląd wykonywany przez człowieka przy wrażliwych decyzjach, ochrona danych, kontrola kosztów oraz ręczna lub oparta na regułach alternatywa na wypadek awarii dostawcy.
Kontrole przed wydzieleniem

Nie traktuj wydzielenia jako odizolowanego wdrożenia technicznego. Zdefiniuj stopniową aktywację dla kontrolowanego podzbioru operacji, odrębną od ogłoszenia zmiany wszystkim użytkownikom. Utrzymuj plan współistnienia i wycofania zmian podczas weryfikacji zachowania.
- Testy kontraktowe między producentem a konsumentem, oprócz testów jednostkowych i integracyjnych.
- Metryki opóźnień, błędów, ponowień, oczekujących kolejek, duplikatów i czasu do ukończenia procesu.
- Identyfikatory korelacji do śledzenia operacji między monolitem, kolejkami i usługą.
- Procedury odzyskiwania: bezpieczne ponowne wykonanie, uzgadnianie stanów, backup i odtwarzanie.
- Jawne reguły degradacji funkcjonalnej, gdy usługa jest niedostępna.
- Kryterium wyjścia: jakie dowody wykażą, że wydzielenie ograniczyło konkretny problem, a nie tylko przeniosło złożoność.
Najlepsza decyzja architektoniczna to taka, która chroni ewolucję produktu bez narzucania nieproporcjonalnej platformy. Modułowy, mierzalny i dobrze wyodrębniony monolit PHP jest zwykle właściwym krokiem, dopóki dana zdolność nie wykaże weryfikowalnej potrzeby niezależności.



