Od monolitu w PHP do mikroserwisów w Go historia migracji i konkretne wskazówki technologiczne

0
115
2/5 - (2 votes)

Brief decyzyjny: czy problemem jest naprawdę architektura, czy raczej wdrożenia, testy, wspólna baza i brak granic w kodzie? Czy Go ma dać realną przewagę operacyjną, czy tylko zastąpić znajomy stos nowym? Czy pierwszy kandydat do wydzielenia ma własne dane, czytelny kontrakt i niezależny rytm zmian? Jak przejść z monolitu w PHP do mikroserwisów w Go bez zatrzymania rozwoju produktu i bez kosztownego „big bangu”?

Frazy pomocnicze: monolit PHP, mikroserwisy Go, migracja etapowa, strangler pattern, modularny monolit, kontrakty API, eventual consistency, observability, wydzielanie usług, granice domenowe, komunikacja PHP i Go, gotowość zespołu

Nawigacja:

1. Nie migruj architektury, dopóki nie nazwiesz właściwego problemu

Co wiemy, a czego jeszcze nie wiemy

Najdroższy błąd przy haśle „od monolitu w PHP do mikroserwisów w Go” polega na tym, że zespół leczy objawy, a nie przyczynę. Wolne release’y bywają skutkiem słabego procesu wdrożeń, nie architektury. Problemy z wydajnością często wynikają z nieoptymalnych zapytań SQL, cache’u ustawionego przypadkowo albo zbyt agresywnego ładowania danych. Z kolei napięcia między zespołami mogą wynikać z braku ownership i niejasnych granic odpowiedzialności, a nie z samego faktu istnienia monolitu.

Dobrze rozdzielić trzy klasy problemów. Pierwsza to problem wydajnościowy: wysokie zużycie CPU, blokujące zapytania do bazy, kolejki zadań rosnące szybciej niż są obsługiwane, zbyt długi czas odpowiedzi pod obciążeniem. Druga to problem organizacyjny: jedna zmiana wymaga koordynacji kilku osób, release jest możliwy tylko raz na tydzień, a każda poprawka przechodzi przez długi łańcuch zależności. Trzecia to problem architektoniczny: brak granic domenowych, kod płatności odwołuje się do modułu promocji, a raporty czytają tabele, których formalnie nie powinny znać.

Co wiemy? Zwykle to, gdzie boli najbardziej: wdrożenia, awarie po zmianach, trudność testowania, niestabilne integracje, przeciążona baza. Czego nie wiemy? Który z tych problemów zniknie po wydzieleniu usług, a który tylko zmieni formę. Jeśli dziś zespół nie mierzy czasu wdrożenia, liczby rollbacków, średniego czasu naprawy i liczby modułów dotkniętych jedną zmianą, to decyzja o mikroserwisach powstaje bardziej z intuicji niż z faktów.

Sygnały mylące, które popychają w złą stronę

Monolit w PHP bardzo często bywa obwiniany za wszystko. Tymczasem częsty obraz jest prostszy: testy end-to-end trwają zbyt długo, bo obejmują cały system; migracje bazy danych wykonywane są ręcznie; pipeline CI uruchamia pełny zestaw kontroli nawet dla kosmetycznej zmiany; cache aplikacyjny maskuje problemy tylko do momentu większego ruchu. W takim układzie podział na mikroserwisy nie rozwiązuje sedna, a dokłada nowe warstwy operacyjne.

Inny mylący sygnał to lokalne przeciążenie jednego obszaru systemu. Jeśli aplikacja e-commerce działa poprawnie, ale endpoint do importu ofert od partnerów obciąża procesy workerów, to rozwiązaniem nie musi być rozbicie całego systemu. Często wystarcza wydzielenie jednego modułu integracyjnego albo osobnych kolejek. Zamiast rozpoczynać szeroką migrację, lepiej najpierw sprawdzić, czy problem da się odseparować mniejszym ruchem.

Bywa też odwrotnie: wydajność nie jest głównym bólem, ale każda zmiana biznesowa uruchamia lawinę zmian technicznych. Przykład typowy: zmiana w płatnościach wymaga poprawki w zamówieniach, historii zdarzeń, panelu administracyjnym, fakturowaniu i webhookach. Monolit może odpowiadać szybko, ale koszt zmiany jest nieproporcjonalnie wysoki. Wtedy migracja do hybrydy albo mikroserwisów ma sens nie dlatego, że PHP jest za wolne, tylko dlatego, że sprzężenia kosztują zbyt dużo.

Krótka diagnostyka przed pierwszą decyzją

Zanim pojawi się plan wydzielania usług, opłaca się przejść przez prostą listę pytań kontrolnych:

  • Czy problemem jest release? Jak długo trwa od merge do produkcji?
  • Czy problemem jest skala? Które moduły wymagają osobnego skalowania?
  • Czy problemem jest zmiana? Ile miejsc dotyka jedna funkcja biznesowa?
  • Czy problemem jest baza? Które obszary są spięte wspólnymi transakcjami?
  • Czy problemem jest odpowiedzialność? Czy wiadomo, kto odpowiada za dany obszar od kodu po utrzymanie?

Jeżeli odpowiedzi wskazują głównie na proces i jakość inżynieryjną, porządkowanie monolitu może dać większy zwrot niż migracja. Jeżeli dominują zależności domenowe, brak możliwości niezależnych wdrożeń i potrzeba osobnego skalowania wybranych obszarów, wtedy migracja z monolitu PHP do mikroserwisów w Go staje się racjonalną hipotezą, a nie modą.

2. Oceń uczciwie, czy mikroserwisy są rozwiązaniem, czy tylko nową etykietą

Kiedy to ma sens

Mikroserwisy mają sens wtedy, gdy istnieją wyraźne granice domenowe i realna potrzeba niezależności. Klasyczny przypadek to system, w którym powiadomienia, rozliczenia, import danych, raportowanie i panel administracyjny rozwijają się w różnym tempie. Jeżeli moduł integracji z partnerami wymaga częstych zmian i innego cyklu release niż rdzeń zamówień, wydzielenie tej części przestaje być teorią architektoniczną, a zaczyna być ruchem organizacyjno-technicznym.

Drugi mocny sygnał to różny profil obciążenia. Monolit PHP często bywa wystarczający dla operacji CRUD i klasycznych ścieżek webowych. Problem zaczyna się tam, gdzie jeden obszar systemu jest silnie I/O-heavy: odbiera webhooki, konsumuje kolejki, wykonuje wiele równoległych połączeń do zewnętrznych API, przetwarza pliki albo agreguje dane. Wydzielona usługa w Go może wtedy zużywać zasoby bardziej przewidywalnie i dać łatwiejsze skalowanie poziome.

Trzeci argument to niezależne wdrożenia. Jeżeli zmiana w module wysyłki wiadomości nie powinna czekać na pełny release całego monolitu, a dziś czeka, to architektura blokuje proces. W takim układzie jedna lub kilka usług obok rdzenia może przynieść zauważalną poprawę nawet bez pełnego przejścia na mikroserwisy.

Kiedy lepiej się wstrzymać

Jeśli zespół jest mały, nie ma sensownego CI/CD, nie utrzymuje monitoringu i nie ma praktyki on-call, to mikroserwisy będą głównie mnożeniem punktów awarii. W monolicie jedna awaria jest trudna. W kilku usługach bez observability każda awaria jest trudniejsza, bo trzeba ustalić nie tylko co się zepsuło, ale także gdzie i po jakiej ścieżce.

Drugie ostrzeżenie dotyczy danych. Jeżeli niemal cały system opiera się na jednej silnie transakcyjnej bazie i większość funkcji biznesowych zakłada wspólny commit na wielu tabelach, to przejście do mikroserwisów bez przebudowy modelu danych kończy się zwykle półśrodkiem: osobne repozytoria kodu, ale jedna baza i zależności ukryte głębiej niż wcześniej. To nie jest realna dekompozycja, tylko nowa warstwa organizacyjnego kosztu.

Trzecia sytuacja to brak dojrzałości granic w samym monolicie. Jeżeli dziś moduły w PHP nie mają czytelnych interfejsów, logika domenowa jest rozlana po kontrolerach, serwisach, listenerach i zapytaniach SQL, to wydzielanie usług od razu przenosi chaos na sieć. W takiej sytuacji modularny monolit jest często etapem koniecznym, nie zbędnym.

Trzy rozsądne ścieżki zamiast jednego hasła

ŚcieżkaKiedy pasujeZaletyRyzyka
Porządny monolitProblemy są głównie procesowe lub jakościoweNajniższa złożoność operacyjna, szybkie porządkiOgraniczona niezależność wdrożeń i skalowania
Modularny monolitGranice domenowe są możliwe, ale dane nadal mocno wspólneLepsza separacja bez kosztu sieci i wielu środowiskPozorna modularność, jeśli brak dyscypliny architektonicznej
Hybryda z kilkoma usługamiNiektóre obszary mają inny rytm rozwoju lub skalowaniaKontrolowany zysk przy ograniczonym ryzykuOkres przejściowy PHP + Go wymaga dobrych kontraktów

W praktyce najrozsądniejsza bywa hybryda: rdzeń zostaje w monolicie lub modularnym monolicie, a obszary naturalnie odrębne przechodzą do usług. To ważne, bo zatrzymanie się przed pełnymi mikroserwisami nie jest porażką. Czasem jest najlepszą możliwą decyzją.

3. Wybieraj Go z powodów operacyjnych, nie wizerunkowych

Gdzie Go ma praktyczną przewagę

Go bywa wybierane przy wydzielaniu usług z monolitu nie dlatego, że „zastępuje PHP”, ale dlatego, że dobrze sprawdza się w określonym typie zadań. Najczęściej chodzi o lekkie usługi API, worker’y kolejkowe, przetwarzanie webhooków, integracje z wieloma zewnętrznymi systemami oraz komponenty infrastrukturalne. Tam przewaga wynika z prostego modelu budowania binarek, sensownej współbieżności i przewidywalnego zużycia pamięci.

Jeżeli usługa ma odbierać tysiące krótkich żądań, czekać na odpowiedzi z zewnętrznych API, obsługiwać timeouty i przekazywać dane dalej, Go jest praktycznym wyborem. Podobnie w przypadku procesów asynchronicznych: konsumentów kolejek, schedulerów, usług retry, dedykowanych walidatorów plików czy serwisów notyfikacyjnych. Tego typu komponenty zwykle mają prostszy model domenowy, ale dużo operacji sieciowych i współbieżności.

Dodatkowym argumentem jest wygoda operacyjna. Pojedyncza binarka, prosty deployment i niewielka zależność od środowiska wykonawczego upraszczają uruchamianie oraz skalowanie. Dla zespołu operacyjnego ma to większe znaczenie niż marketingowe hasła o „nowoczesnym stosie”.

Gdzie nie warto go forsować

Nie każda część systemu zyskuje na przepisaniu do Go. Jeśli logika biznesowa jest mocno osadzona w dojrzałym frameworku PHP, wykorzystuje bogaty ekosystem bibliotek domenowych, a zespół ma lata doświadczenia w tym stosie, to koszt migracji może zjeść przewagę techniczną. Dotyczy to zwłaszcza skomplikowanych obszarów biznesowych, gdzie najtrudniejsze nie jest wystawienie API, tylko utrzymanie poprawnej logiki i szybkości zmian.

Problemem może być też gotowość zespołu. Go jest językiem stosunkowo prostym składniowo, ale prostota języka nie usuwa potrzeby ustalenia standardów: obsługi błędów, struktury pakietów, loggera, middleware, testów integracyjnych, kontraktów, sposobu budowania klienta HTTP czy podejścia do kontekstu i timeoutów. Jeśli zespół nie ma czasu na wypracowanie takich zasad, nowy język zaczyna generować dług architektoniczny od pierwszego sprintu.

Niekiedy lepsza decyzja to pozostawienie części wydzielanych usług również w PHP, szczególnie gdy celem jest przede wszystkim separacja odpowiedzialności i niezależny deploy. Mikroserwisy nie wymagają automatycznie zmiany języka. Go ma sens tam, gdzie charakter usługi uzasadnia ten wybór.

Praktyczne kryteria wyboru Go

  • Wybieraj Go, gdy usługa jest sieciowa, I/O-heavy, ma dużo współbieżności lub działa jako worker.
  • Zastanów się dwa razy, gdy logika domenowa jest złożona i silnie osadzona w obecnym frameworku PHP.
  • Wstrzymaj zmianę języka, jeśli zespół nie ma jeszcze standardów CI/CD, observability i testów kontraktowych.
  • Nie licz na sam język, jeśli głównym problemem są słabe granice domenowe albo wspólna baza.

Przykład praktyczny: serwis powiadomień, który przyjmuje zdarzenia, dobiera kanał, pilnuje retry i statusów dostarczeń, jest naturalnym kandydatem do Go. Moduł wyceny złożonych promocji, głęboko związany z istniejącymi modelami zamówień i regułami biznesowymi, już znacznie mniej.

4. Pierwszy serwis wybierz po granicach domenowych, a nie po prestiżu modułu

Cechy dobrego pierwszego kandydata

Pierwszy serwis ma przede wszystkim obniżyć ryzyko. Powinien mieć czytelny zakres odpowiedzialności, niski coupling względem reszty systemu i sensowny model komunikacji. Dobrze, jeśli jego dane mają wyraźnego właściciela i nie wymagają rozbudowanych wspólnych transakcji z rdzeniem. Taki kandydat daje szansę zbudować standardy migracji bez paraliżu produktu.

Dobrym sygnałem jest osobny rytm skalowania. Jeśli jeden moduł bywa przeciążony w inny sposób niż reszta aplikacji, łatwiej uzasadnić wydzielenie. Podobnie wtedy, gdy obszar wykonuje dużo operacji zewnętrznych: woła partnerów, kolejki, systemy mailowe, SMS, push, narzędzia raportowe. Tam granice techniczne i biznesowe częściej pokrywają się sensownie.

Przy wyborze pierwszej usługi pomocne są krótkie pytania kontrolne:

  • Czy moduł ma jednego czytelnego właściciela biznesowego i technicznego?
  • Czy można opisać jego odpowiedzialność jednym zdaniem, bez długiej listy wyjątków?
  • Czy dane tego obszaru da się wydzielić bez utrzymywania wspólnego commita z połową systemu?
  • Czy awaria tej funkcji nie zatrzyma całej sprzedaży, logowania albo krytycznej ścieżki użytkownika?
  • Czy zespół umie już wskazać kontrakt wejścia i wyjścia: API, zdarzenia, retry, błędy, timeouty?

Jeśli na większość tych pytań odpowiedź brzmi „nie wiemy”, to sygnał ostrzegawczy. Co wiemy? Że system jest trudny. Czego nie wiemy? Gdzie naprawdę przebiegają granice. W takiej sytuacji lepiej najpierw uporządkować moduł w monolicie: odseparować logikę, nazwać interfejsy, ograniczyć bezpośrednie odwołania do cudzych tabel. Dopiero potem podejmować decyzję o wyjściu za sieć.

Złe pierwsze wybory zdarzają się częściej niż dobre

Kuszące bywa wydzielenie modułu „najważniejszego”, bo wtedy zmiana wygląda strategicznie. W praktyce to często najgorszy ruch. Płatności, koszyk, autoryzacja, pricing albo rdzeń zamówień zwykle mają najwięcej wyjątków, zależności i ukrytych powiązań z danymi. Wydzielenie takiego obszaru na starcie zamienia migrację w program naprawczy całego systemu, a nie kontrolowany eksperyment architektoniczny.

Znacznie lepiej sprawdzają się obszary poboczne, ale pełne operacyjnie: notyfikacje, webhooki, eksporty, generowanie dokumentów, asynchroniczne integracje z partnerami, czasem wyszukiwarka. Taki serwis nadal uczy zespoł standardów komunikacji między PHP i Go, ale nie stawia od razu całego biznesu na jednej decyzji. To różnica między testem granic a hazardem.

Przykład z typowej praktyki: moduł wysyłki e-maili i SMS-ów bywa dobrym kandydatem, bo ma własne retry, własne statusy i dużo wywołań zewnętrznych. Ten sam system może mieć jednocześnie fatalnego kandydata w postaci kalkulacji rabatów, jeśli reguły promocji są porozrzucane po kilku warstwach PHP i zależą od wspólnego stanu zamówienia.

Rozsądny kolejny krok jest prosty: wybierz jeden obszar z czytelną granicą, opisz kontrakt, zmierz zależności i dopiero wtedy decyduj, czy potrzebujesz mikroserwisu, modularnego monolitu, czy po prostu lepiej uporządkowanego PHP. Architektura zaczyna pomagać dopiero wtedy, gdy odpowiada na konkretny problem, a nie wtedy, gdy tylko dobrze wygląda na diagramie.

5. Migrację prowadź etapami: strangler pattern, adaptery i cienka warstwa przejściowa

Najdroższy błąd to próba przepisania całego obszaru naraz. Brzmi ambitnie, ale zwykle kończy się długim okresem podwójnej logiki, opóźnieniami i sporami o to, który system jest źródłem prawdy. Bezpieczniejszy wariant jest prostszy: najpierw odetnij wejścia, potem stopniowo przekierowuj ruch i dopiero na końcu wycinaj stary kod.

Jak wygląda to w praktyce

Strangler pattern nie polega na „pisaniu nowego obok starego” bez planu. Chodzi o kontrolowane przejmowanie odpowiedzialności przez nową usługę. Monolit nadal działa, ale część wywołań przechodzi przez warstwę pośrednią: adapter, fasadę albo bramkę aplikacyjną. Dzięki temu decyzja o przełączeniu nie jest zaszyta w kilkunastu kontrolerach i trzech cronach.

  • Krok 1: ustal punkt wejścia — jedno miejsce, przez które monolit komunikuje się z wydzielanym obszarem.
  • Krok 2: wprowadź adapter — dziś woła kod lokalny, jutro może wołać usługę HTTP lub publikować zdarzenie.
  • Krok 3: uruchom nowy serwis obok starego — najpierw bez pełnego ruchu produkcyjnego.
  • Krok 4: przełączaj scenariusze stopniowo — po typach żądań, klientach, regionach albo feature flagach.
  • Krok 5: usuń martwą ścieżkę — dopiero gdy logi, metryki i obsługa błędów potwierdzą stabilność.

Praktyczny sens jest prosty: ryzyko rozkłada się na serię małych decyzji. Jeśli nowa usługa nie domyka wymagań, można się wycofać bez awarii całego modułu.

Nie buduj grubej warstwy przejściowej

Warstwa pośrednia ma pomagać w migracji, a nie stać się nowym mini-monolitem. To częsty problem: adapter zaczyna mapować dziesiątki wyjątków, scala dane z kilku miejsc i zawiera coraz więcej reguł biznesowych. Po kilku miesiącach zespół utrzymuje już trzy byty: stary kod PHP, nową usługę Go i „tymczasową” logikę między nimi.

Dobra warstwa przejściowa powinna robić niewiele: autoryzować, tłumaczyć kontrakt, dodawać timeouty, obsłużyć retry tam, gdzie to uzasadnione, i emitować metryki. Jeśli zaczyna podejmować decyzje domenowe, to znak, że granica została źle narysowana.

Krótki przykład: moduł generowania dokumentów można schować za interfejsem DocumentService. Najpierw wywołuje klasę w PHP, później klienta HTTP do Go. Kontrolery i joby nie wiedzą, co jest pod spodem. To dobra zmiana. Zła zmiana zaczyna się wtedy, gdy ten sam adapter sam ustala szablony, rabaty, walidacje i obieg akceptacji.

6. Kontrakty między PHP i Go zaprojektuj tak, jakby miały przetrwać kilka lat

Okres przejściowy zwykle trwa dłużej, niż zakłada plan. Dlatego kontrakt między monolitem a usługą nie może być „tymczasowym JSON-em”, który później się poprawi. To właśnie tu najczęściej pojawiają się koszty ukryte: niejawne pola, brak wersjonowania, różne interpretacje błędów i timeoutów.

Co powinno być ustalone od początku

  • Semantyka operacji — kto za co odpowiada i co oznacza sukces albo częściowe powodzenie.
  • Wersjonowanie — niekoniecznie skomplikowane, ale jawne. Zmiana kontraktu bez planu migracji szybko psuje oba systemy.
  • Idempotencja — szczególnie dla webhooków, płatności, notyfikacji i jobów retry.
  • Timeouty i retry — osobno dla wywołań synchronicznych i osobno dla kolejek czy eventów.
  • Kody błędów — biznesowe i techniczne nie powinny mieszać się w jeden komunikat typu 500.
  • Tracing i correlation ID — bez tego diagnoza problemów między PHP i Go będzie zgadywaniem.

Co wiemy, gdy kontrakt jest słaby? Że „działa u nas”. Czego nie wiemy? Jak zachowa się po opóźnieniach sieci, dubletach wiadomości i częściowych awariach partnerów. To nie detal implementacyjny, tylko koszt utrzymania w czystej postaci.

HTTP czy zdarzenia?

Nie ma jednej właściwej odpowiedzi. Wywołania synchroniczne są prostsze do zrozumienia i dobre tam, gdzie użytkownik czeka na odpowiedź. Zdarzenia i kolejki lepiej sprawdzają się przy procesach, które mogą zakończyć się później: wysyłka powiadomień, eksport, synchronizacja z partnerem, przeliczanie indeksów wyszukiwarki.

Rozsądna reguła jest taka: jeśli wynik musi wrócić od razu do użytkownika lub kolejnej warstwy biznesowej, użyj dobrze opisanego API. Jeśli proces może zostać odłożony, rozważ komunikację asynchroniczną i jawny model statusów.

Zespół prezentuje rozwiązania technologiczne w nowoczesnym biurze
Źródło: Pexels | Autor: Mikhail Nilov

Typowy błąd to udawanie asynchroniczności przez synchroniczne wywołanie, które i tak trwa długo, bo pod spodem czeka na kilka zewnętrznych systemów. Lepszy model bywa prostszy: przyjęcie żądania, zapis stanu, publikacja zdarzenia i osobny endpoint do sprawdzenia statusu.

7. Dane rozdzielaj ostrożnie: wspólna baza przyspiesza start, ale spowalnia wyjście

Najbardziej problematyczny moment migracji zwykle nie dotyczy API, tylko danych. Dopóki dwa światy współdzielą tabele i transakcje, dopóty niezależność jest ograniczona. Z drugiej strony zbyt szybkie cięcie bazy może rozsadzić procesy biznesowe, które opierają się na wspólnym stanie.

Praktyczna kolejność decyzji

Najpierw ustal właściciela danych. Każdy wydzielany obszar powinien mieć jasno wskazane encje, za które odpowiada. Potem sprawdź, kto je tylko odczytuje, a kto naprawdę modyfikuje. To robi dużą różnicę, bo read-model można czasem skopiować lub odtworzyć zdarzeniami, ale współdzielony zapis szybko prowadzi do konfliktów.

  • Na starcie dopuszczalne bywa czytanie ze starej bazy, jeśli to etap przejściowy i ma datę końcową.
  • Unikaj wspólnego zapisu z dwóch stron do tych samych tabel, bo odpowiedzialność staje się nieczytelna.
  • Osobna baza ma sens, gdy usługa realnie przejmuje ownership nad własnym stanem.
  • Eventual consistency trzeba nazwać wprost, a nie traktować jako przypadkowe opóźnienie replikacji.

Transakcje rozproszone rzadko są dobrym pierwszym wyborem

Jeśli wydzielany proces wymaga jednoczesnego commita w monolicie i nowej usłudze, to zwykle znaczy, że granica jest jeszcze niedojrzała albo przepływ wymaga przeprojektowania. W praktyce lepiej sprawdzają się lokalne transakcje po każdej stronie i komunikacja zdarzeniowa z mechanizmem kompensacji tam, gdzie to konieczne.

To nie znaczy, że wszystko można „załatwić eventami”. Oznacza tylko tyle, że spójność danych trzeba dobrać do procesu. Status notyfikacji może być finalizowany asynchronicznie. Finalizacja zamówienia z ograniczonym stanem magazynowym wymaga już większej ostrożności i często nie jest dobrym kandydatem na pierwszy etap wydzielenia.

Krótki przykład z praktyki: eksport dokumentów do partnera zniesie opóźnienie i ponowne przetworzenie. Aktualizacja limitu kredytowego klienta już niekoniecznie. Jeśli krytyczny proces nie toleruje opóźnień, duplikatów ani ręcznej kompensacji, nie zakładaj z góry, że nowa usługa uprości sprawę.

8. Observability i bezpieczeństwo potraktuj jak warunek wejścia, nie etap „po migracji”

Monolit bywa trudny, ale przynajmniej zwykle ma jedno miejsce, w którym można szukać problemu. Po wydzieleniu usług pojawia się sieć, timeouty, ponowienia, zależności od brokerów i zewnętrznych API. Bez sensownych logów, metryk i trace’ów diagnoza awarii wydłuża się natychmiast.

Minimalny zestaw, bez którego nie ma sensu ruszać

  • Ustrukturyzowane logi z identyfikatorem żądania i kluczowym kontekstem biznesowym.
  • Metryki techniczne: latency, error rate, throughput, saturation.
  • Distributed tracing między PHP, Go i zależnościami zewnętrznymi.
  • Health checki i readiness sensownie opisujące stan usługi.
  • Jawne timeouty po obu stronach, bez domyślnego „czekaj w nieskończoność”.
  • Sekrety, autoryzacja usług i audyt — szczególnie gdy nowy serwis dotyka danych klientów lub operacji finansowych.

To nie jest „infrastrukturalny luksus”. Jeśli usługa działa bez trace’ów i bez ustalonych timeoutów, pierwszy incydent produkcyjny szybko pokaże, ile kosztuje pozorna oszczędność. Ten koszt wraca zwykle w nocy, przy on-callu.

Jak mierzyć, czy migracja pomaga

Nie tylko przez CPU i czas odpowiedzi. Trzeba patrzeć szerzej: lead time zmian, liczba incydentów, czas diagnozy, częstotliwość deployów, zakres rollbacków, zależność między zespołami. Jeżeli po wydzieleniu jednej usługi dwa zespoły czekają na siebie dłużej niż wcześniej, to architektura nie poprawiła przepływu pracy, tylko go przeorganizowała.

Dobre sygnały są dość konkretne: krótszy czas wdrażania zmian w danym obszarze, mniejszy blast radius awarii, łatwiejsze skalowanie wybranego procesu i mniej przypadków, w których modyfikacja jednego modułu psuje niepowiązany fragment systemu.

9. Zmiana architektury bez zmiany odpowiedzialności zespołu zwykle nie działa

Mikroserwisy nie porządkują organizacji automatycznie. Jeśli nadal nikt nie ma pełnego ownership nad usługą, jej kontraktem, testami, deployem i reakcją na incydenty, to zespół tylko rozłoży ten sam chaos na większą liczbę repozytoriów.

Jakie zmiany organizacyjne są naprawdę potrzebne

  • Jasny owner usługi — techniczny i, jeśli to możliwe, produktowy.
  • Samodzielny pipeline CI/CD dla wydzielanego obszaru.
  • Testy kontraktowe i integracyjne zamiast opierania się wyłącznie na testach manualnych.
  • On-call lub przynajmniej czytelny model wsparcia dla awarii w nowej usłudze.
  • Standardy inżynierskie wspólne dla usług — logowanie, błędy, struktura endpointów, polityka wersjonowania.

Jeżeli nowa usługa trafia do „wspólnej odpowiedzialności wszystkich”, zwykle oznacza to odpowiedzialność niczyją. To jeden z powodów, dla których część zespołów rozsądnie zatrzymuje się na modularnym monolicie lub hybrydzie. Gdy struktura organizacyjna nie wspiera niezależnych usług, koszt koordynacji zaczyna przewyższać zysk z podziału.

Najbardziej praktyczny następny krok nie musi być spektakularny: wybrać jeden kandydacki obszar, rozpisać jego granice danych i kontrakt komunikacji, a potem sprawdzić, czy zespół potrafi go samodzielnie wdrożyć, monitorować i utrzymać. Jeśli odpowiedź jest niejasna, problem prawdopodobnie nie leży jeszcze w braku mikroserwisów.

10. Zatrzymaj migrację, jeśli pierwsze wyniki nie poprawiają przepływu pracy

Najdroższy błąd nie polega na tym, że pierwszy serwis wyjdzie niedoskonale. Kosztowniej jest iść dalej mimo sygnałów, że zespół produkuje nową złożoność szybciej niż realne korzyści. Mikroserwisy nie są strategią „raz uruchomionej” migracji. Po każdym etapie trzeba sprawdzić, co faktycznie się poprawiło.

Sygnały, że kierunek ma sens

  • Zmiany w wydzielonym obszarze wdrażają się szybciej niż wcześniej w monolicie.
  • Awaria nowej usługi nie zatrzymuje całego systemu, tylko ogranicza konkretną funkcję.
  • Zespół umie samodzielnie diagnozować problemy bez przekopywania się przez kilka warstw „czy to jeszcze PHP, czy już Go”.
  • Skalowanie wybranego procesu stało się prostsze, bo obciążony komponent da się rozwijać niezależnie.

Sygnały alarmowe, przy których lepiej zwolnić

  • Każda zmiana wymaga synchronizacji kilku zespołów, choć wcześniej robił to jeden.
  • Nowe API jest obchodzone bezpośrednim dostępem do starej bazy, bo inaczej „się nie da”.
  • Incydenty są częstsze, a diagnoza trwa dłużej mimo dodania nowej infrastruktury.
  • Go przejęło zadania, które nie potrzebują niezależności, a monolit nadal pozostaje głównym miejscem zmian biznesowych.

To moment na proste pytania kontrolne: co wiemy? Że wydzielona usługa działa. Czego nie wiemy? Czy obniża koszt zmian i ryzyko awarii, czy tylko przenosi odpowiedzialność w inne miejsce. Jeśli odpowiedź na drugie pytanie jest mglista, lepiej dopracować granice i operacyjność niż wydzielać kolejny moduł.

11. Hybryda bywa lepsza niż pełna dekompozycja

Nie każdy dojrzały system powinien dojść do kilkunastu czy kilkudziesięciu mikroserwisów. W wielu przypadkach sensowny stan docelowy to modularny monolit w PHP plus kilka wyspecjalizowanych usług w Go. Taki układ rozwiązuje konkretny problem, zamiast kopiować modny wzorzec.

Najczęściej wygląda to tak:

  • rdzeń biznesowy zostaje w monolicie, jeśli ma dużo wspólnych transakcji i gęste zależności,
  • Go przejmuje obszary technicznie odseparowane, na przykład przetwarzanie asynchroniczne, integracje, kolejki, generowanie dokumentów, notyfikacje, API o wysokiej współbieżności,
  • granice są świadomie ograniczone, żeby liczba usług nie rosła szybciej niż zdolność zespołu do ich utrzymania.

To nie jest półśrodek. To często rozsądny kompromis między niezależnością a kosztem koordynacji. Jeżeli monolit po uporządkowaniu modułów nadal daje przewidywalny development, a tylko kilka obszarów naprawdę potrzebuje osobnego cyklu życia, hybryda wygrywa prostotą.

Kiedy nie iść dalej niż kilka usług

Jeśli większość zmian nadal przecina wiele domen naraz, jeśli zespół nie ma dojrzałego CI/CD albo jeśli operacyjnie każda nowa usługa zwiększa ryzyko on-call bardziej niż poprawia tempo pracy, dalsza dekompozycja zwykle jest przedwczesna. W takiej sytuacji lepiej domknąć modularność monolitu, uprościć zależności i dopiero wtedy ponownie ocenić potrzebę kolejnych wydzieleń.

12. Zacznij od małego planu decyzyjnego, nie od programu transformacji

Najbardziej użyteczny pierwszy ruch jest zwykle mniej widowiskowy, niż zakładają slajdy architektoniczne. Zamiast planować „przejście na mikroserwisy”, lepiej rozpisać jeden obszar i sprawdzić go przez kilka filtrów:

  1. Czy problem jest naprawdę architektoniczny? Jeśli chodzi głównie o bałagan w kodzie, brak testów albo ręczne wdrożenia, nowa usługa tego nie naprawi.
  2. Czy obszar ma wyraźnego właściciela danych i procesu? Bez tego granica szybko stanie się sztuczna.
  3. Czy może działać z jawną asynchronicznością lub stabilnym API? Jeśli wymaga stałych, wielostronnych transakcji, to zły kandydat na start.
  4. Czy zespół potrafi go samodzielnie wdrożyć i utrzymać? Bez observability, CI/CD i ownership decyzja jest przedwczesna.
  5. Czy sukces da się zmierzyć? Na przykład krótszym lead time, mniejszym blast radius albo łatwiejszym skalowaniem konkretnego procesu.

Krótki przykład praktyczny: moduł wysyłki powiadomień często przechodzi te filtry dobrze, bo ma czytelne wejście, toleruje retry i opóźnienia, a awaria nie musi zatrzymać całego checkoutu. Moduł rozliczeń lub przydziału stanów magazynowych zwykle wymaga ostrożniejszej oceny, bo koszt błędu i potrzeba ścisłej spójności są dużo większe.

Najrozsądniejszy kolejny krok to nie deklaracja „idziemy w mikroserwisy”, tylko decyzja o jednym eksperymencie z jasnym zakresem: kandydat, kontrakt, ownership, metryki sukcesu i data przeglądu. Po takim etapie zwykle widać znacznie więcej niż po kolejnej dyskusji o tym, czy Go i mikroserwisy są nowocześniejsze od dobrze utrzymanego monolitu w PHP.

13. Rozdział danych zacznij od reguł dostępu, nie od kopiowania tabel

Najwięcej kosztownych pomyłek pojawia się nie przy samym API, tylko przy danych. Jeśli nowa usługa w Go dostaje własny deployment, ale nadal czyta i zapisuje bezpośrednio do tych samych tabel co monolit, to formalnie powstał mikroserwis, a praktycznie nadal istnieje wspólna aplikacja z dodatkową latencją.

Najpierw trzeba odpowiedzieć na dwa krótkie pytania: co wiemy? Które dane są naprawdę własnością wydzielanego procesu. Czego nie wiemy? Które zależności w bazie są tylko techniczne, a które odzwierciedlają realną regułę biznesową.

Jak odcinać dane bez wywołania efektu domina

  • Ustal właściciela zapisu — tylko jeden komponent powinien być źródłem prawdy dla danego obszaru.
  • Zostaw odczyty przejściowe, ale ograniczaj je świadomie — tymczasowy read model bywa akceptowalny, wspólny write model zwykle nie.
  • Wyciągaj najpierw prostsze byty — takie, które mają mniej transakcji krzyżowych i mniej ukrytych zależności.
  • Dokumentuj obejścia — jeśli usługa jeszcze czyta starą bazę, ten dług musi być widoczny, nie „na później”.

Praktyczny sens jest prosty: bez tych reguł każda kolejna zmiana w schemacie bazy zaczyna wymagać ustaleń między kilkoma repozytoriami i zespołami. To zwykle sygnał, że granica została narysowana za wcześnie albo w złym miejscu.

14. Nie przenoś transakcji 1:1 między usługi, zaprojektuj proces na nowo

W monolicie jedna transakcja obejmująca kilka tabel bywa zwykłą codziennością. Po rozbiciu na usługi taki sam model zaczyna być źródłem napięć: blokad, timeoutów, prób odtwarzania „distributed transaction” i trudnych rollbacków. Tu najczęściej kończy się prosty entuzjazm dla dekompozycji.

W praktyce lepiej rozdzielić proces na kroki i zaakceptować, że część spójności będzie osiągana po chwili, a nie w tej samej milisekundzie.

Gdzie eventual consistency ma sens, a gdzie trzeba uważać

  • Dobra kandydatura — notyfikacje, synchronizacja z systemami zewnętrznymi, generowanie dokumentów, przetwarzanie kolejek.
  • Obszary wymagające ostrożności — płatności, stany magazynowe, przydziały limitów, rozliczenia i wszystko, gdzie duplikat albo opóźnienie ma realny koszt biznesowy.

Krótki przykład: wysłanie e-maila o zmianie statusu zamówienia może poczekać i może zostać ponowione. Rezerwacja ostatniej sztuki produktu zwykle nie powinna opierać się wyłącznie na luźnej wymianie zdarzeń bez dobrze przemyślanej kompensacji.

Jeżeli zespół próbuje odtworzyć dawną transakcję SQL jako łańcuch synchronicznych wywołań między PHP i Go, to koszt operacyjny szybko rośnie. Lepiej wtedy wrócić krok wcześniej i sprawdzić, czy dany proces w ogóle nadaje się do wydzielenia.

15. Asynchroniczność pomaga, ale tylko z idempotencją i retry pod kontrolą

Kolejka, broker zdarzeń czy background worker często rozwiązują realny problem przeciążenia monolitu. Nie rozwiązują jednak problemu semantyki komunikatu. Jeśli ten sam event może przyjść dwa razy albo przyjść później niż zakładano, system musi to umieć przeżyć bez ręcznej interwencji.

Minimalny zestaw zabezpieczeń

  • Idempotentne przetwarzanie — ta sama wiadomość nie może tworzyć dwóch zamówień, dwóch płatności albo dwóch dokumentów.
  • Jawna polityka retry — z limitem prób, backoffem i obsługą dead-letter queue.
  • Klucz korelacyjny — żeby dało się prześledzić drogę jednego procesu przez PHP, brokera i usługę w Go.
  • Wersjonowanie zdarzeń — payload będzie się zmieniał; pytanie brzmi nie czy, tylko kiedy.

Praktyczny sens tych reguł staje się widoczny przy pierwszym błędzie integracji. Bez nich zespół widzi tylko, że „coś nie doszło”. Z nimi da się odróżnić problem chwilowy od błędu kontraktu albo wadliwej logiki po stronie konsumenta.

16. Ustal wspólny standard błędów i timeoutów zanim liczba usług wzrośnie

Jedna z mniej efektownych, ale bardzo opłacalnych decyzji dotyczy tego, jak usługi odpowiadają na błędy. Bez wspólnego podejścia PHP zwraca jeden format, Go drugi, gateway trzeci, a zespół przy incydencie musi najpierw tłumaczyć sam system.

Dobrze działa prosty zestaw zasad:

  • spójna struktura błędu — kod, typ, komunikat techniczny, identyfikator żądania,
  • timeouty ustawione jawnie po obu stronach połączenia,
  • rozróżnienie błędów biznesowych i infrastrukturalnych,
  • brak cichych fallbacków, które ukrywają utratę danych albo niepełne wykonanie procesu.

To nie jest kosmetyka. Jeśli nowa usługa ma być wdrażana niezależnie, musi też dać się niezależnie diagnozować. W przeciwnym razie każdy alert kończy się pytaniem, czy problem leży w kontrakcie, sieci, bazie czy zachowaniu klienta.

17. Benchmarki zostaw na później, najpierw sprawdź profil obciążenia

Go bywa wybierane słusznie, ale nie dlatego, że „jest szybsze od PHP” w oderwaniu od kontekstu. Trzeba rozróżnić kilka sytuacji. Jeśli problemem jest duża liczba prostych wywołań I/O, obsługa kolejek, współbieżność i lekki serwis sieciowy, Go zwykle ma mocny sens operacyjny. Jeśli natomiast głównym problemem jest złożona logika biznesowa z nieczytelnymi zależnościami, zmiana języka nie usunie źródła tarcia.

Kiedy Go rzeczywiście pomaga

  • dużo współbieżnych zadań i potrzeba przewidywalnego użycia zasobów,
  • serwisy infrastrukturalne lub integracyjne, które mają działać prosto i stabilnie,
  • małe, samodzielne procesy, którym służy szybki build i prosty deployment.

Kiedy przewaga będzie mniejsza, niż zakłada zespół

  • gdy dominują zapytania do wolnej bazy albo ograniczenia zewnętrznego API,
  • gdy brakuje testów i observability, więc szybsza usługa jest tylko trudniej diagnozowalna,
  • gdy zespół dopiero uczy się Go, a presja produktowa nie daje miejsca na spokojne wejście.

Najuczciwsza ocena zwykle wygląda przyziemnie: jaki jest profil ruchu, gdzie naprawdę ucieka czas odpowiedzi i czy nowa usługa ma mieć prosty, wyraźny zakres. Jeśli nie ma, argument technologiczny zaczyna być wtórny wobec architektonicznego.

18. Ustal punkt zatrzymania migracji jeszcze przed wydzieleniem kolejnej usługi

To jedna z bardziej praktycznych zasad zarządzania ryzykiem. Zespół powinien wcześniej wiedzieć, przy jakich wynikach kontynuuje dekompozycję, a przy jakich zostaje przy hybrydzie. Bez tego łatwo pomylić wysiłek już poniesiony z dowodem, że trzeba iść dalej.

Dobry punkt kontrolny może być bardzo konkretny:

  • czy czas wdrożenia zmian w nowym obszarze faktycznie spadł,
  • czy liczba zależności między zespołami się zmniejszyła,
  • czy incydenty da się szybciej diagnozować niż wcześniej,
  • czy usługa ma własny, realny lifecycle, a nie tylko osobne repozytorium.

Jeżeli odpowiedzi są niejednoznaczne, rozsądniejszy ruch to dopracowanie granic, kontraktów i odpowiedzialności niż uruchamianie następnego serwisu. Czasem najtrafniejsza decyzja po pierwszym etapie brzmi: ten obszar zostaje w Go, reszta zostaje modularnym monolitem. To nie porażka migracji, tylko zamknięcie jej tam, gdzie kończy się korzyść.

19. Bez observability nowa architektura tylko lepiej ukrywa problemy

Przy monolicie wiele rzeczy da się jeszcze „znaleźć po logach”. Przy układzie PHP + Go + broker + osobne bazy to przestaje wystarczać bardzo szybko. Jeśli zespół nie widzi drogi jednego żądania przez system, to pierwsza poważniejsza awaria zamienia się w ręczne odtwarzanie faktów.

Tu dobrze zadać dwa pytania kontrolne: co wiemy od razu po alercie? oraz czego nie wiemy bez zaglądania do trzech narzędzi i pięciu repozytoriów?

Minimum, które powinno powstać przed kolejnymi usługami

  • spójny request ID i correlation ID — jeden identyfikator musi przechodzić przez HTTP, joby i zdarzenia,
  • metryki techniczne i biznesowe — nie tylko CPU i pamięć, ale też liczba nieudanych operacji, czas przetwarzania i retry,
  • distributed tracing — szczególnie tam, gdzie jedno działanie użytkownika uruchamia kilka wywołań,
  • logi strukturalne — tak, żeby dało się filtrować po usłudze, typie błędu, identyfikatorze procesu i wersji,
  • czytelne SLI/SLO — bez tego trudno odróżnić chwilowy szum od realnego pogorszenia jakości.

Praktyczny sens jest prosty: nowa usługa ma nie tylko działać, ale też dać się utrzymać o drugiej w nocy. Jeśli po wdrożeniu odpowiedź na pytanie „gdzie zginęło żądanie?” brzmi „to zależy”, to migracja wyprzedziła fundamenty operacyjne.

20. Bezpieczeństwo ustaw jako warunek wejścia, nie etap drugi

Rozdzielenie monolitu zwykle zwiększa liczbę połączeń, sekretów, punktów wejścia i uprawnień. To nie jest detal wdrożeniowy. W praktyce każdy nowy serwis dodaje nową powierzchnię ataku i nowe miejsca, gdzie można przypadkiem otworzyć za szeroki dostęp.

Najbardziej opłacalne zabezpieczenia na starcie

  • jawna autoryzacja między usługami — nie zakładaj, że „wewnętrzna sieć” załatwia temat,
  • krótko żyjące sekrety i rotacja — hasła zapisane na stałe w CI albo plikach konfiguracyjnych szybko stają się długiem,
  • minimalne uprawnienia do bazy i brokerów — każda usługa powinna mieć tylko to, czego potrzebuje,
  • walidacja wejścia po obu stronach kontraktu — szczególnie przy eventach i payloadach rozwijanych przez kilka zespołów,
  • audyt działań wrażliwych — kto zmienił limit, zatwierdził operację, uruchomił kompensację.

Krótki przykład z praktyki zespołów: wydzielenie usługi płatności bywa uznawane za sukces architektoniczny, ale jeśli ta usługa nadal dziedziczy szerokie konto monolitu do całej bazy, to granica jest bardziej organizacyjna niż bezpieczeństwa.

21. Zmień ownership zespołu zanim liczba repozytoriów zacznie rosnąć

Mikroserwisy rzadko psują się przez sam kod. Częściej problemem jest brak jasnej odpowiedzialności. Kto wdraża? Kto odbiera alert? Kto decyduje o zmianie kontraktu? Jeśli odpowiedź brzmi „w sumie wszyscy”, to po kilku usługach zaczyna się chaos operacyjny.

Fakt jest prosty: architektura rozproszona wymaga bardziej precyzyjnego modelu pracy niż monolit. Interpretacja jest równie prosta: bez tego zysk z dekompozycji będzie krótkotrwały.

Co powinno być przypisane do konkretnego ownera

  • repozytorium i pipeline — ktoś odpowiada za stan builda i wydania,
  • kontrakt API lub eventu — zmiana nie może być „wrzucona i zobaczymy”,
  • dashboardy, alerty i runbook — incydent bez właściciela trwa dłużej niż powinien,
  • backlog długu przejściowego — adaptery, tymczasowe odczyty i obejścia muszą mieć opiekuna,
  • decyzje o kompatybilności wstecznej — szczególnie gdy PHP i Go rozwijają się w różnym tempie.

Nie chodzi o tworzenie silosów. Chodzi o to, by każda usługa miała czytelny lifecycle i kogoś, kto widzi całość: kod, wdrożenie, koszt zmiany i skutki awarii.

22. CI/CD i testy kontraktowe są ważniejsze niż sam wybór frameworka

Przy pierwszym serwisie łatwo skupić uwagę na tym, czy w Go użyć konkretnego routera, ORM-u albo generatora klienta. Operacyjnie ważniejsze jest coś innego: jak szybko i bezpiecznie zmiana przechodzi od commita do produkcji oraz jak wcześnie wykrywany jest rozjazd między producentem i konsumentem.

Praktyczny zestaw na etap przejściowy

  • pipeline budujący, testujący i wersjonujący usługę niezależnie,
  • testy kontraktowe między PHP i Go — szczególnie dla pól opcjonalnych, kodów błędów i zmian typów danych,
  • deploy stopniowy — feature flagi, canary albo ruch kierowany przez gateway,
  • szybki rollback — nie tylko na poziomie obrazu, ale też kontraktu i konfiguracji,
  • test środowiskowy po wdrożeniu — prosty smoke test bywa tańszy niż późniejszy incydent.

Dobry przykład to endpoint wydzielony z monolitu, który zwraca poprawny JSON, ale zmienia semantykę jednego pola. Unit testy przechodzą, usługa działa, a integracja po stronie PHP zaczyna podejmować błędne decyzje. To właśnie miejsce dla testów kontraktowych, nie dla domysłów.

23. Nie licz sukcesu migracji liczbą usług, tylko spadkiem kosztu zmiany

Najbardziej mylący wskaźnik postępu to liczba wydzielonych komponentów. Technicznie wygląda to dobrze na diagramie, ale biznesowo i organizacyjnie niczego jeszcze nie dowodzi. Lepsze pytanie brzmi: czy po wydzieleniu zmiana w danym obszarze stała się szybsza, bezpieczniejsza i mniej zależna od reszty systemu?

Wskaźniki, które mają sens decyzyjny

  • lead time dla zmian — czy poprawka lub nowa funkcja trafia szybciej na produkcję,
  • częstotliwość wdrożeń — czy nowy obszar można publikować niezależnie od monolitu,
  • MTTR — czy po awarii szybciej wiadomo, gdzie jest problem i jak go cofnąć,
  • liczba zależności krzyżowych — ile zmian nadal wymaga koordynacji między kilkoma modułami i zespołami,
  • stabilność kontraktów — częste, chaotyczne zmiany API to zwykle sygnał źle narysowanej granicy.

Jeśli po kilku miesiącach wdrożenia są równie trudne, incydentów nie diagnozuje się szybciej, a zależności między zespołami rosną, to warto zatrzymać narrację o „postępie architektonicznym” i wrócić do faktów. Być może system potrzebuje lepszego modularnego monolitu, a nie kolejnych usług.

24. Zostaw część domeny w monolicie, jeśli granica nie broni się w praktyce

Nie każdy moduł powinien trafić do osobnej usługi. Czasem najlepsza decyzja jest zachowawcza: uporządkować kod, doprecyzować moduły, odizolować zależności i zatrzymać się na architekturze hybrydowej. To szczególnie sensowne tam, gdzie proces jest silnie transakcyjny, często zmieniany i głęboko spleciony z resztą systemu.

Sygnały, że lepiej nie wydzielać kolejnego obszaru

  • granica domenowa jest niejasna i zespół nie potrafi wskazać jednego ownera danych,
  • większość zmian nadal przecina kilka modułów naraz,
  • proces wymaga ścisłej spójności synchronicznej, a koszt kompensacji byłby wysoki,
  • brakuje mocy operacyjnej na monitoring, on-call i utrzymanie kolejnych usług,
  • cel migracji jest głównie wizerunkowy, nie związany z realnym bottleneckiem.

Rozsądny następny krok bywa mniej widowiskowy: domknąć pierwszy serwis, usunąć tymczasowe obejścia, dopracować kontrakty i dopiero wtedy zdecydować, czy cokolwiek jeszcze zyskuje na wydzieleniu. W wielu przypadkach to właśnie ten moment oddziela migrację kontrolowaną od kosztownej przebudowy bez jasnego zwrotu.