Chmura8 min czytania

Systemy legacy to nie problem!

Systemy legacy to nie problem!

To historia o tym, jak przekształciliśmy dziesięcioletnią platformę logistyczną opartą na .NET Framework 4.5 w nowoczesną aplikację na .NET 9 i Nuxt 4, bez zatrzymywania biznesu, bez przepisywania wszystkiego od zera za jednym zamachem (big-bang rewrite) i bez utraty wiedzy, która od początku stanowiła o wartości tego systemu.

Mit „złego” kodu legacy

W naszej branży krąży niebezpieczny mit, że kod legacy to zły kod. Otóż nie! Kod legacy to kod, który przetrwał.

Zastanów się, co to właściwie oznacza. Ten kod przetworzył miliony transakcji. Obsługiwał przypadki brzegowe, których nikt nie udokumentował. Utrzymywał biznes w ruchu, podczas gdy frameworki JavaScript rosły w siłę i upadały jak imperia. Ta aplikacja w AngularJS z 2014 roku? Przeżyła Backbone, Ember i co najmniej dwie wersje Angulara, które podobno miały ją zastąpić.

Kiedy zgłosiła się do nas firma logistyczna z dziesięcioletnim dashboardem w .NET Framework 4.5, zobaczyłem w tym szansę. System, który dobrze służył im przez lata, był teraz gotowy na modernizację. Dzięki naszemu doświadczeniu w migracjach systemów legacy wiedziałem dokładnie, od czego zacząć.

Oto co odziedziczyliśmy:

  • .NET Framework 4.5 z ASP.NET MVC 5 i uwierzytelnianiem OWIN
  • AngularJS 1.x (ten oryginalny, sprzed wielkiego przepisania Angulara)
  • Breeze.js do zarządzania danymi z zapytaniami OData
  • Entity Framework 6 z logiką biznesową rozrzuconą po kontrolerach
  • jQuery 2.1, które spajało to wszystko

Nowoczesne? Według standardów z 2025 roku nawet w przybliżeniu nie. Ale ten system niezawodnie obsługiwał ich biznes przez ponad dziesięć lat - ogromne uznanie dla inżyniera, który go zbudował. Taka długowieczność powinna być fundamentem każdego projektu. Bądźmy jednak szczerzy: żaden system nie jest wolny od wad. Nawet jeśli funkcja wydaje się działać idealnie, w miarę jak system rośnie i gromadzi coraz więcej danych, pojawiają się przypadki brzegowe, które potrafią zabić wydajność, pogorszyć doświadczenie użytkownika i stopniowo podkopywać niezawodność.

Dlaczego większość modernizacji kończy się porażką

Znasz ten film. Typowe podejście rozwija się przewidywalnie:

  1. Uznaj system za niemożliwy do utrzymania
  2. Zdobądź zgodę na przepisanie go od nowa
  3. Spędź 8-15 miesięcy na budowaniu czegoś nowego
  4. Odkryj, że umknęły Ci krytyczne przypadki brzegowe
  5. Trać przewagę konkurencyjną, podczas gdy biznes czeka

Problemem nie jest kod legacy. Problemem jest mentalność „przepiszmy wszystko naraz”.
Udana modernizacja wymaga zrozumienia, dlaczego podjęto określone decyzje, a nie tylko skatalogowania tego, co istnieje. Oznacza rozpoznawanie wzorców z poprzednich generacji technologii i wiedzę o tym, które „złe praktyki” były w swojej epoce najlepszymi praktykami. Przede wszystkim wymaga dyscypliny, żeby zmieniać system stopniowo, a nie heroicznie.

Właściwe podejście: wzorzec Strangler Fig

Co więc naprawdę działa przy modernizacji dziesięcioletniego systemu? Po przeanalizowaniu różnych podejść zdecydowaliśmy się na wzorzec Strangler Fig Martina Fowlera. Jego główna idea jest zwodniczo prosta, a zarazem elegancka: zamiast ryzykownej wymiany wszystkiego naraz stopniowo budujesz nowy system wokół starego. Nie burzysz tego, co działa - budujesz obok, aż stary system naturalnie zniknie.

Faza 1: nowy frontend, ta sama baza danych

Zanim napisaliśmy choćby jedną linię nowego kodu, zmapowaliśmy istniejący system: każdy punkt integracji, każdy przepływ danych, każde założenie wpisane w architekturę.

Diagram 1: Zarówno stary frontend w AngularJS, jak i nowy w Nuxt 4 korzystają z tej samej bazy danych SQL Server, co umożliwia migrację bez przestojów.

Kluczowa decyzja projektowa: oba systemy korzystały z tej samej bazy danych. Użytkownicy mogli dowolnie przełączać się między interfejsami. Gdyby w nowym systemie pojawiły się problemy, mogliśmy natychmiast wrócić do starego. Biznes ani na chwilę nie przestał działać. Żadnego dramatycznego przełączenia. Tylko stopniowe przejście, w miarę jak użytkownicy nabierali zaufania do nowego systemu.

Faza 2: backend w Clean Architecture

W nowym backendzie zastosowaliśmy Clean Architecture z CQRS. Nie dlatego, że to modne, ale dlatego, że wymusza separację, której systemom legacy zwykle brakuje. Kiedy logika biznesowa jest rozrzucona po kontrolerach, walidacja żyje w trzech różnych miejscach, a nikt nie jest pewien, gdzie leżą obowiązujące reguły, wyraźne granice stają się niezbędne.

Diagram 2: Cztery odrębne warstwy wymuszają separację, której często brakuje systemom legacy.

Architektura udowadnia swoją wartość. Komendy obsługują zapisy z jawnie wyrażoną intencją. Zapytania obsługują odczyty i są optymalizowane niezależnie. Praktyczne korzyści:

  • Dodawanie cache do zapytań bez wpływu na komendy
  • Testowanie logiki biznesowej w pełnej izolacji
  • Wymiana infrastruktury (na przykład przejście z SQL Server na PostgreSQL) bez ruszania logiki domenowej

Ta separacja zaczęła się opłacać niemal od razu, gdy musieliśmy zoptymalizować wydajność odczytów bez ryzyka dla integralności operacji zapisu.

Faza 3: nowe możliwości, a nie tylko nowa technologia

W dyskusjach o modernizacji często umyka coś kluczowego: prawdziwa wartość nie leży w zmianie frameworków. To potrafi każdy. Prawdziwa wartość to nowe możliwości, których system legacy nie był w stanie zapewnić.

Diagram 3: Integracja webhooków SuperDispatch w czasie rzeczywistym. Możliwość nieosiągalna w starej architekturze Breeze.js/OData.

Z biegiem lat biznes się rozwijał, a klient zaczął w dużym stopniu polegać na SuperDispatch, zewnętrznej platformie do zarządzania zleceniami transportu pojazdów. To stworzyło realne wyzwanie: potrzebowali integracji z SuperDispatch przez webhooki, żeby otrzymywać aktualizacje zleceń w czasie rzeczywistym. W systemie legacy wymagałoby to poważnej gimnastyki architektonicznej. Breeze.js zaprojektowano do zapytań klient-serwer, a nie do obsługi zdarzeń między serwerami - webhooków po prostu nie było w jego słowniku. W nowej architekturze dodaliśmy endpoint dla webhooków, napisaliśmy handler i w ciągu jednego dnia aktualizacje zleceń płynęły w czasie rzeczywistym.
To właśnie takie odblokowanie nowych możliwości uzasadnia modernizację. Nie „używamy nowszej technologii”, tylko „możemy teraz robić rzeczy, które wcześniej były niemożliwe”.

Od chaosu bez typów do pewności na etapie kompilacji

Zmianę we frontendzie deweloperzy odczuwają najmocniej. Stary kod w AngularJS nie miał typów. Serwisy komunikowały się przez magiczne stringi. Zapytania Breeze.js zwracały nietypowane encje, których kształt trzeba było po prostu znać. Każda sesja refaktoryzacji oznaczała nadzieję, że nic nie zepsuje się na produkcji. Do nowego frontendu wybraliśmy Nuxt 4 (tak, Nuxt, a nie Next :) ) z Vue 3.5 i TypeScriptem:

Do warstwy UI wybraliśmy shadcn-vue i Tailwind CSS 4. Dostępne, konfigurowalne komponenty bazowe bez uzależnienia od dostawcy. Żadnego podatku od biblioteki komponentów, żadnej walki z cudzymi decyzjami projektowymi.
Korzyści pojawiły się od razu. Błędy kompilacji wyłapywały literówki, zanim trafiły na produkcję. Refaktoryzacja stała się bezpieczna: zmieniasz nazwę pola, a TypeScript wskazuje każde jego użycie. Nowi deweloperzy mogli zrozumieć kod, czytając typy, zamiast odtwarzać działanie systemu na podstawie błędów w czasie wykonania.

Ewolucja stosu technologicznego

Oto pełny obraz zmian:

Warstwa Legacy (2015) Nowoczesny stos (2025)
Framework backendu .NET Framework 4.5 .NET 9
Styl API Breeze.js + OData REST + CQRS
ORM Entity Framework 6 EF Core 9
Uwierzytelnianie OWIN + OAuth JWT + ASP.NET Core Identity
Framework frontendu AngularJS 1.x Nuxt 4 + Vue 3.5
Zarządzanie stanem Breeze.js EntityManager Vue composables + Pinia
Bezpieczeństwo typów Brak TypeScript 5.9
CSS Bootstrap 3 + własne style Tailwind CSS 4
Build Ręczne bundlowanie Vite

Szczegóły, które mają znaczenie

To tu, w gąszczu szczegółów, projekty modernizacyjne naprawdę odnoszą sukces albo ponoszą porażkę.

Jest coś bardzo satysfakcjonującego w obserwowaniu, jak ukryte zawiłości wychodzą na powierzchnię, i w poprawnym radzeniu sobie z nimi. Dobrze pokazują to dwa przykłady.

Zagadka zarchiwizowanych zleceń

API SuperDispatch zwraca status: "new" dla zarchiwizowanych zleceń. Delikatnie mówiąc, wbrew intuicji. Faktyczny stan archiwizacji jest zapisany w osobnej fladze logicznej: is_archived: true. System legacy miał na to obejścia zakopane w logice kontrolerów. Nieeleganckie, to pewne, ale zrozumienie, dlaczego te obejścia istniały, było kluczowe. Bez tego wprowadzilibyśmy subtelne regresje, które wyszłyby na jaw dopiero wtedy, gdy użytkownicy zaczęliby się skarżyć na „brakujące” zlecenia.

UWAGA: Przez lata nauczyłem się, że NIGDY nie powinniśmy obwiniać Inżyniera za jego decyzje. Kod to tylko ten element układanki, który widzimy.

Niewidzialna kolejność sortowania

Stary dashboard wyświetlał zlecenia w określonej kolejności. Nikt nie udokumentował dlaczego. Nikt nie napisał specyfikacji. Ale użytkownicy polegali na tej kolejności od lat. Ich pamięć mięśniowa była do niej przyzwyczajona. API SuperDispatch zwraca dane w innej kolejności. Oczekiwania użytkowników są wymaganiami, nawet jeśli nikt ich nie spisał. Dodaliśmy znacznik czasu ChangedAt, żeby lokalnie śledzić zmiany zleceń, a potem uzupełniliśmy istniejące rekordy danymi z API. Drobny szczegół o dużym wpływie na doświadczenie użytkownika.
To nie są efektowne problemy. Nie znajdziesz ich na blogach o architekturze ani w prezentacjach konferencyjnych. Ale to one decydują o tym, czy modernizacja odniesie sukces na produkcji, czy poniesie porażkę.

Wyniki biznesowe

Co więc faktycznie dała cała ta praca? Oto wskaźniki, które mają znaczenie:

  • Tempo dostarczania funkcji: 3x szybszy rozwój (dni zamiast tygodni)
  • Pewność wdrożeń: zautomatyzowane CI/CD zastąpiło ręczne sesje RDP
  • Możliwości integracji: synchronizacja z SuperDispatch w czasie rzeczywistym, wcześniej niemożliwa
  • Czas wdrożenia nowych osób: nowi inżynierowie są produktywni po kilku dniach, a nie tygodniach
  • Dostępność: zero przestojów przez całą migrację

Może Cię zaskoczyć, że stary system w AngularJS nadal działa. Używamy go do nietypowych raportów, których migracja się nie opłacała. To nie porażka. To pragmatyzm. Przeniesiemy te ekrany, gdy uzasadni to rachunek biznesowy, a nie dlatego, że ktoś zdecydował, że wszystko musi działać na nowym stosie.

Pięć zasad modernizacji systemów legacy

Jeśli masz zapamiętać z tego wpisu tylko jedno, niech to będzie tych pięć zasad. Kierowaliśmy się nimi w każdej udanej modernizacji, jaką prowadziliśmy.

Pięć zasad, które prowadzą do udanej modernizacji systemów legacy
  1. Kod legacy to nie zły kod. To kod, który przetrwał.
    Podchodź do niego z ciekawością, a nie z pogardą. Zapytaj „dlaczego to istnieje?”, zanim zapytasz „jak to zastąpić?”. Odpowiedzi uchronią Cię przed powtarzaniem historii.
  2. Oplataj stary system, zamiast go przepisywać.
    Rozbudowuj nowy system wokół starego. Utrzymuj oba w działaniu. Pozwól nowemu systemowi sprawdzić się na produkcji, zanim zaczniesz myśleć o wyłączeniu starego.
  3. Najpierw wspólna baza danych, potem rozdzielenie.
    Zadbaj o spójność danych od pierwszego dnia. Wydzielaj osobne bazy danych później, gdy nowy system będzie stabilny, a Ty zrozumiesz rzeczywiste granice danych. Nie optymalizuj architektury przedwcześnie.
  4. Dawaj nowe możliwości, a nie tylko nową technologię.
    Wartością nie jest zmiana frameworka. Każdy potrafi zaktualizować zależności. Prawdziwa wartość to to, co umożliwia nowa architektura, a czego stara nie była w stanie zapewnić. Jeśli nie potrafisz nazwać tej różnicy, nie modernizujesz - przepisujesz!
  5. Wartość biznesowa, a nie czystość techniczna.
    Celem jest szybsze dostarczanie funkcji i przewaga konkurencyjna - nie ładniejszy kod i nie architektoniczna astronautyka. Rozwiązuj prawdziwe problemy prawdziwych użytkowników.

Podejście Rebels

W Rebels specjalizujemy się w takich transformacjach. Niezależnie od tego, czy chodzi o firmware IoT, infrastrukturę chmurową, czy modernizację oprogramowania dla firm, zasady pozostają te same:

  • Zrozum to, co istnieje, zanim to zmienisz
  • Weryfikuj założenia za pomocą działającego oprogramowania
  • Pilotuj zmiany z prawdziwymi użytkownikami
  • Skaluj to, co działa, i poprawiaj to, co nie działa

Twój system legacy to nie obciążenie. To fundament sprawdzony w boju przez lata, w którym zapisana jest wiedza biznesowa, jakiej nie uchwyciłaby żadna dokumentacja. Pytanie nie brzmi, czy modernizować - tylko czy masz właściwe podejście, żeby budować na tym fundamencie, zamiast go spalić.

  • Chmura
  • Oprogramowanie
  • Case study