Jak połączyć system legacy z nowym produktem?
Integracja systemu legacy z nową aplikacją nie psuje się zwykle na samym połączeniu. Psuje się wtedy, gdy nowy produkt zaczyna dziedziczyć ograniczenia starego systemu: timeouty, nieczytelne błędy, ręczne obejścia i brak kontroli nad stanem procesu. Jeśli ktoś sprzedaje szybki adapter jako domyślnie najtańszą drogę, bardzo często sprzedaje też przyszły koszt utrzymania. Przy produkcie, który ma rosnąć, tani start bywa po prostu drogim zakupem w przebraniu.
Najczęstszy skrót na starcie brzmi rozsądnie: partner wdrożeniowy proponuje prosty adapter, bo „trzeba dowieźć szybko”. Jeśli jednak nowy produkt ma obsłużyć więcej niż jeden kanał, więcej niż jeden proces albo po prostu ma działać dłużej niż etap przejściowy, taki wybór często kończy się przeniesieniem bałaganu ze starego systemu do nowego produktu. To nie jest pragmatyzm. To kredyt techniczny z wysokim oprocentowaniem.
Powiem wprost: w projektach, które mają żyć dłużej niż jeden budżet roczny, tani adapter częściej okazuje się błędem zakupowym niż oszczędnością. Można się z tym spierać. Ja zaczynałbym ten spór dopiero wtedy, gdy na stole leży plan wyłączenia legacy z właścicielem, terminem i pieniędzmi, a nie sama estymacja wdrożenia.
Jeśli równolegle ustalacie kolejność zmian po stronie starego systemu, najpierw uporządkujcie plan modernizacji bez big bangu. Sama integracja nie naprawi złej sekwencji decyzji.
Adapter, fasada API czy model asynchroniczny
Gdy kontrakt legacy jest niestabilny albo zespół starego systemu zmienia zachowanie bez porządnej kontroli wersji, model synchroniczny szybko zaczyna karać nowy produkt za każdy błąd po drugiej stronie. W takim układzie adapter lub fasada mają sens tylko przy naprawdę małym zakresie i krótkim horyzoncie życia. Jeśli proces toleruje opóźnienie, asynchroniczność zwykle lepiej izoluje od chaosu niż elegancko opisane, ale kruche wywołanie HTTP.
| Podejście | Kiedy wybrać | Główne ryzyko |
|---|---|---|
| Adapter | Jeden lub dwa proste procesy, stabilny kontrakt, krótki horyzont wyłączenia legacy | Logika integracyjna zaczyna żyć w kodzie produktu |
| Fasada API | Nowa aplikacja potrzebuje własnego API i odpowiedzi synchronicznych | Ładny interfejs ukrywa niestabilność starego systemu |
| Model asynchroniczny | Proces toleruje opóźnienie, a ważniejsza jest odporność niż natychmiastowa odpowiedź | Większy koszt operacyjny i trudniejszy rollout |
Reguły biznesowe powinny zostać po stronie kontrolowanej warstwy integracyjnej albo domeny produktu, a nie być rozsmarowane między ekranem, adapterem i klientem legacy. Gdy mapowanie, walidacja i kompensacja lądują przypadkiem w nowej aplikacji, produkt zaczyna pełnić rolę nieformalnej platformy integracyjnej. To zwykle kończy się źle, bo zespół produktowy optymalizuje pod doświadczenie użytkownika, a nie pod trwałość kontraktu między systemami.
Drugi filtr jest prosty i dość brutalny: czy integracja ma przeżyć zmianę dostawcy, kanału albo procesu? Jeśli tak, granica musi być projektowana jak element architektury, a nie jak tymczasowy skrypt. Widziałem wdrożenia, w których „tymczasowy” adapter działał trzy lata i blokował każdą kolejną zmianę, bo nikt nie chciał ruszać miejsca, gdzie wymieszały się wyjątki biznesowe, retry i mapowanie danych.
Kiedy adapter naprawdę wystarczy
Adapter do systemu legacy ma sens tylko wtedy, gdy pozostaje mały i lokalny. Jedna ścieżka odczytu, jeden ograniczony zapis, brak ambicji budowania wspólnej warstwy dla wielu kanałów. Jeśli już dziś wiadomo, że dojdzie portal klienta, aplikacja mobilna, partnerzy albo automatyzacja back-office, adapter zwykle przestaje być adapterem i zaczyna być przypadkową platformą integracyjną.
Jeżeli ten sam komponent ma w ciągu kilku miesięcy obsłużyć portal klienta, panel operacyjny i integrację partnerską, koszt zmian zacznie rosnąć szybciej niż koszt wydzielenia warstwy integracyjnej. Wtedy trzymanie integracji w produkcie nie jest prostotą. To tylko odłożenie przebudowy na moment, w którym będzie droższa i bardziej ryzykowna.
Nie ma za to sensu budować warstwy pośredniej tylko dlatego, że brzmi dojrzale architektonicznie. Gdy legacy ma stabilny interfejs, zakres jest mały, a termin wyłączenia systemu ma właściciela i budżet, adapter bywa najlepszą decyzją. Problem w tym, że takie przypadki są rzadsze, niż zespoły zakładają na początku.
Dobry adapter ma też twarde ograniczenia techniczne. Nie powinien przechowywać własnego stanu procesu poza minimalnym cache, nie powinien implementować rozbudowanej orkiestracji i nie powinien stać się miejscem, w którym zespół „na szybko” dopisuje kolejne wyjątki. Jeśli po dwóch sprintach pojawia się potrzeba osobnych retry, dead-letter queue, translacji wielu modeli danych i własnych uprawnień, adapter przestał być mały. Trzeba to nazwać i przebudować, zanim koszt utrzymania wyprzedzi koszt refaktoryzacji.
Kiedy lepsza jest fasada API
Fasadę API wybierasz wtedy, gdy nowy produkt potrzebuje własnego kontraktu, ale użytkownik nadal czeka na odpowiedź w tym samym przebiegu. To typowy układ dla portalu klienta, panelu operacyjnego albo aplikacji B2B, która musi pobrać dane, sprawdzić status lub wykonać prostą operację bez przechodzenia w tryb oczekiwania na przetworzenie w tle.
Fasada ma jedną dużą zaletę: odcina frontend i logikę produktu od pól, kodów błędów i dziwnych ograniczeń starego systemu. Dzięki temu połączenie starego systemu z nową aplikacją nie zamienia się w kopiowanie jego wewnętrznego modelu danych do nowego świata.
Jest też pułapka. Jeśli legacy ma skoki opóźnień, blokady albo zależy od ręcznych obejść, fasada nie rozwiązuje problemu. Tylko go elegancko opakowuje. Zespoły regularnie mylą estetykę API z poprawą architektury. To nie to samo.
Po stronie kontraktu trzymaj się jawnego opisu w OpenAPI i spójnej semantyki HTTP zgodnej z RFC 9110. Opis kontraktu ma ułatwić decyzje o wersjonowaniu, podziale operacji synchronicznych i odpowiedzialności między zespołem produktu, integracji oraz supportem. Bez tego każda zmiana kończy się dyskusją, czy problem leży w kliencie, fasadzie czy samym legacy.
Fasada API wymaga jeszcze jednego elementu, o którym często zapomina się w estymacji: normalizacji błędów. Legacy potrafi zwracać kody, które nic nie mówią użytkownikowi ani supportowi. Jeśli fasada ma mieć sens, musi tłumaczyć błędy techniczne na stabilny model domenowy: walidacja, konflikt stanu, brak uprawnień, chwilowa niedostępność, timeout. Bez tego nowa aplikacja nadal będzie reagować na wewnętrzne kaprysy starego systemu, tylko pod inną nazwą.
Przy większym ruchu dochodzi kwestia ochrony legacy przed nowym produktem. Rate limiting, timeouty, circuit breaker i cache odpowiedzi referencyjnych nie są dodatkiem. To podstawowe zabezpieczenia. Dokumentacja Google SRE od lat pokazuje, że brak kontroli przeciążenia kończy się kaskadową awarią szybciej, niż zespoły zakładają. Jeśli fasada ma przyjąć ruch z wielu kanałów, musi umieć powiedzieć „nie” w kontrolowany sposób.
Kiedy przejść na model asynchroniczny
Asynchroniczna integracja systemów legacy wygrywa wtedy, gdy proces może zakończyć się po chwili, a nie natychmiast. Synchronizacja statusów, aktualizacja danych referencyjnych, część zapisów, notyfikacje i propagacja zmian zwykle dobrze znoszą taki model. Użytkownik dostaje potwierdzenie przyjęcia operacji, a system domyka resztę poza ścieżką interaktywną.
Przewaga pojawia się przy konkretnych warunkach operacyjnych: gdy legacy ma nocne okna serwisowe, gdy odpowiedzi synchroniczne regularnie wpadają w timeout pod skokami ruchu albo gdy pojedyncza awaria backendu nie może blokować przyjęcia zlecenia. W takim układzie kolejka i przetwarzanie w tle odcinają ścieżkę użytkownika od chwilowej niedostępności starego systemu. To nie jest wybór „nowocześniejszej” architektury. To sposób, żeby backlog incydentów nie rósł po każdym przeciążeniu.
Nie każdy zespół powinien jednak iść w tę stronę. Jeśli organizacja nie ma właściciela kontraktu, procedury rollbacku i monitoringu zgodności danych, kolejka szybko staje się miejscem, do którego trafiają nierozliczone problemy. Po kilku tygodniach nikt nie monitoruje DLQ, support nie ma widoku statusu komunikatów, a operacje zaczynają rozliczać błędy ręcznie na podstawie zgłoszeń użytkowników.
Wtedy koszt przestaje być abstrakcyjny. Rośnie backlog komunikatów do ręcznego rozliczenia, support nie wie, czy operacja zniknęła, czy tylko czeka, a przy incydencie nikt nie ma jasnej odpowiedzialności za decyzję: ponowić, skompensować czy zamknąć sprawę ręcznie. Taki model nie zwiększa odporności. On tylko przenosi awarię z ekranu użytkownika do zespołu operacyjnego.
Model asynchroniczny wymaga też decyzji o semantyce dostarczenia. W praktyce nie projektujesz systemu „bez błędów”, tylko system odporny na duplikaty, opóźnienia i zmianę kolejności komunikatów. Dlatego idempotencja jest ważniejsza niż sama kolejka. Jeśli zapis zamówienia albo aktualizacja statusu nie potrafi bezpiecznie przyjąć tego samego komunikatu drugi raz, pierwsza większa awaria zamieni się w ręczne czyszczenie danych.
Przy pierwszym wdrożeniu obowiązkowe są trzy mechanizmy: klucz idempotencji do wykrywania duplikatów, status operacji widoczny dla supportu oraz DLQ z realną procedurą obsługi. Bez nich zespół nie odróżni opóźnienia od utraty komunikatu, a każda większa awaria skończy się ręcznym dochodzeniem, co naprawdę zostało zapisane. Dopiero potem dokładacie bardziej rozbudowane ponowienia, kompensację i dodatkową orkiestrację.
Jak postawić granicę integracji, żeby nowa aplikacja nie dziedziczyła chaosu
Nowy produkt powinien rozmawiać z jednym kontrolowanym kontraktem. Nie z katalogiem wyjątków starego systemu. Jeśli frontend zna pola typu cust_no, stare statusy albo techniczne identyfikatory z legacy, granica została postawiona źle. Wtedy warstwa integracyjna istnieje tylko na diagramie.
Granica jest gotowa operacyjnie dopiero wtedy, gdy ktoś odpowiada za kontrakt end-to-end, zmiany niekompatybilne są wersjonowane, monitoring obejmuje zgodność danych, a rollback dotyczy procesu zamiast samej wersji aplikacji. Bez tych czterech warunków integracja może działać na testach i jednocześnie regularnie psuć produkcję po wdrożeniu.
Jeśli część przepływu zostaje synchroniczna, opis kontraktu w OpenAPI zwykle wystarcza. Jeśli część przechodzi na zdarzenia, sens ma AsyncAPI albo równoważny standard. Chodzi o precyzję odpowiedzialności, nie o produkcję dokumentów.
Wzorzec Strangler Fig bywa użyteczny, ale tylko wtedy, gdy nowa granica jest stabilniejsza niż stara. Jeśli zespół nie potrafi utrzymać własnego kontraktu, dokładanie kolejnej warstwy zwiększa tylko powierzchnię awarii.
Granica integracji powinna też rozdzielać model domenowy produktu od modelu technicznego legacy. To nie są te same rzeczy. Produkt operuje pojęciami zrozumiałymi dla użytkownika: konto, zlecenie, status realizacji, limit. Legacy często operuje tabelami, kodami, skrótami i historycznymi wyjątkami. Mieszanie tych warstw jest wygodne na początku, ale potem każda zmiana w starym systemie staje się zmianą w doświadczeniu użytkownika.
Najprostszy sensowny podział wygląda tak: wejście przyjmuje kontrakt produktu, warstwa translacji mapuje pola i reguły walidacji, klient legacy rozmawia językiem starego systemu, a odpowiedź wraca już bez przecieków technicznych. To nie jest akademicka czystość. Dzięki temu można wymienić system legacy albo zmienić sposób integracji bez przepisywania frontendu i logiki biznesowej nowego produktu.
Przepływ danych, walidacja i zgodność stanu
Najwięcej problemów nie bierze się z samego transportu danych, tylko z różnicy znaczeń. To, że dwa systemy mają pole status, nie znaczy jeszcze, że opisują ten sam stan procesu. Jeden system może uznawać operację za zakończoną po zapisaniu rekordu, drugi dopiero po przejściu ręcznej weryfikacji. Jeśli zespół nie rozpisze semantyki stanów, integracja będzie technicznie poprawna i biznesowo błędna.
Przed implementacją trzeba rozdzielić trzy poziomy walidacji: wejście po stronie nowej aplikacji, kontrakt integracyjny na granicy oraz reguły legacy, których nie da się przenieść bez zmiany procesu. To porządkuje odpowiedzialność. Użytkownik nie powinien dostawać komunikatu z legacy o błędzie pola, którego nigdy nie widział, a zespół operacyjny musi wiedzieć, czy problem wynika z danych wejściowych, czy z ograniczeń starego systemu.
Przy synchronizacji danych referencyjnych, takich jak klienci, cenniki, słowniki czy uprawnienia, kluczowe jest ustalenie źródła prawdy. Bez tego oba systemy zaczynają nadpisywać się nawzajem. To jeden z tych błędów, które przez kilka tygodni wyglądają niewinnie, a potem kończą się serią incydentów i ręcznym uzgadnianiem rekordów.
Jedna encja, jedno źródło prawdy, jeden właściciel zmiany. Jeśli ta zasada nie jest jawna, integracja prędzej czy później zacznie produkować konflikty stanu.
W systemach z większą liczbą wyjątków dobrze działa też rejestr decyzji integracyjnych. Nie rozbudowany proces architektoniczny, tylko krótki zapis: które pole jest kanoniczne, gdzie mapujemy status, co robimy przy braku odpowiedzi, kto zatwierdza zmianę kontraktu. Bez tego po kilku miesiącach nikt nie pamięta, dlaczego dana reguła istnieje, a każda awaria zamienia się w archeologię.
Koszt wdrożenia, koszt utrzymania i sygnały złego projektu
W integracji systemu legacy z nową aplikacją koszt startowy jest zwykle przeceniany, a koszt utrzymania zaniżany. Adapter wygląda tanio, bo ma mało kodu. Po kilku miesiącach okazuje się drogi, bo każda zmiana dotyka produktu, integracji i często samego legacy. Dlatego cienki adapter nad niestabilnym systemem bywa najdroższym skrótem w całym wdrożeniu.
Fasada API kosztuje więcej na wejściu, ale szybciej się broni, gdy rośnie liczba kanałów i zmian kontraktu. Model asynchroniczny wymaga większej dyscypliny operacyjnej, za to lepiej amortyzuje awarie i skoki obciążenia. Nie ma sensu udawać, że te warianty są równorzędne w każdej sytuacji. W rosnących produktach najczęściej nie przepłaca się za architekturę. Najczęściej przepłaca się za zbyt długie trzymanie się adaptera.
Szacując czas wdrożenia, nie patrz tylko na implementację połączenia. Policz też inwentaryzację wyjątków, uzgodnienie kontraktu, testy zgodności danych, rollout i rollback. To właśnie te elementy rozwalają harmonogram częściej niż samo wystawienie endpointu.
Zły projekt integracyjny da się rozpoznać wcześnie. Zmiana w legacy wymusza zmianę w frontendzie. Nikt nie umie wskazać właściciela kontraktu. Support nie odróżnia błędu biznesowego od technicznego. Rollback oznacza ręczne poprawianie danych po obu stronach. A gdy dostawca proponuje zapis bezpośrednio do bazy legacy, trzeba zapalić czerwone światło.
Ten ostatni punkt zasługuje na ostrzeżenie. Odczyt z bazy bywa uzasadniony przy raportowaniu albo porównaniach migracyjnych. Zapis prawie nigdy. Jeśli ktoś sprzedaje go jako szybkie połączenie, kupujesz przyszłe incydenty, ukryte zależności od triggerów i ręczne korekty danych.
Do kosztu utrzymania trzeba doliczyć jeszcze coś, czego budżety często nie pokazują: koszt diagnostyki. Jeżeli po incydencie nie da się w kilka minut odpowiedzieć, gdzie utknęła operacja, jaki miała identyfikator i który system zwrócił błąd, support i zespół techniczny zaczynają pracować na ślepo. To nie jest miękki problem organizacyjny. To realny koszt operacyjny, który rośnie wraz z liczbą kanałów i wolumenem transakcji.
Rollout bez chaosu: od czego zacząć
Najbezpieczniej zacząć od odczytu, a dopiero potem przejmować zapis. Taka kolejność pozwala sprawdzić mapowanie, kontrakt i zgodność danych bez ryzyka podwójnych operacji biznesowych. Dopiero gdy odczyt działa stabilnie, ma sens uruchomienie pierwszej ścieżki zapisu.
Najpierw spisz procesy i wyjątki razem z właścicielami biznesowymi oraz ograniczeniami legacy. Potem zaprojektuj kontrakt tak, by nowa aplikacja nie znała wewnętrznych pól starego systemu. Następnie uruchom odczyt i porównuj zgodność danych po obu stronach granicy. Dopiero później dodaj jeden ograniczony zapis, najlepiej ważny, ale nie krytyczny finansowo, i przełączaj ruch stopniowo przez flagi funkcjonalne albo routing per kanał.
Jeden konkretny przykład dobrze pokazuje, gdzie naprawdę pojawia się tarcie. W firmie logistycznej obsługującej kilka magazynów rollout nowego portalu operacyjnego rozbił się nie o samo API, tylko o okna niedostępności starego ERP i ręczne procedury po stronie zespołu operacyjnego. Odczyt statusu zlecenia został synchroniczny przez fasadę API, bo operator musiał dostać odpowiedź od razu. Sam zapis trafił do kolejki, bo inaczej wdrożenie blokowało pracę w godzinach szczytu. Koszt nie wynikał tu z większej liczby endpointów, tylko z potrzeby monitoringu zgodności danych i przygotowania sensownego rollbacku procesu.
Przy rolloutcie dobrze działa podział na fale z jasnym kryterium przejścia. Nie „wdrożone na testach”, tylko konkret: zgodność danych powyżej ustalonego progu, brak nierozliczonych komunikatów starszych niż określony czas, akceptowalny poziom błędów biznesowych, gotowość supportu do obsługi nowego przepływu. Bez takich warunków przełączenie ruchu staje się decyzją polityczną, a nie techniczną.
Jeśli integracja obejmuje zapis, uruchom najpierw shadow mode albo zapis kontrolowany dla małej grupy użytkowników. W tym trybie nowa aplikacja wykonuje operację równolegle lub częściowo, ale wynik nie staje się jeszcze jedynym źródłem prawdy. To pozwala porównać zachowanie systemów bez pełnego ryzyka biznesowego. Nie zawsze da się to zrobić, ale gdy proces jest krytyczny finansowo, taka faza zwykle zwraca się szybciej niż późniejsze ręczne korekty.
Monitoring, obserwowalność i procedury awaryjne
Integracja bez obserwowalności jest loterią. Sam monitoring infrastruktury nie wystarczy, bo serwis może działać technicznie poprawnie i jednocześnie produkować błędne stany biznesowe. Potrzebujesz trzech warstw widoczności: zdrowie techniczne, przepływ procesu i zgodność danych.
Zamiast rozbudowanej listy metryk lepiej pilnować kilku, które naprawdę prowadzą do decyzji operacyjnej: czasu odpowiedzi i timeoutów dla wywołań synchronicznych, liczby oczekujących komunikatów oraz wieku najstarszego komunikatu w modelu asynchronicznym, odsetka błędów biznesowych po mapowaniu na kontrakt produktu, zgodności rekordów między systemami dla kluczowych encji i czasu rozliczenia operacji od przyjęcia do finalnego stanu. Jeśli tych wskaźników nie widać na jednym ekranie, support i operacje będą reagować za późno.
Do tego dochodzi śledzenie korelacyjne. Każda operacja powinna mieć identyfikator, który przechodzi przez frontend, warstwę integracyjną, kolejkę i legacy. Bez tego analiza incydentu zamienia się w ręczne sklejanie logów. W systemach rozproszonych to już nie luksus, tylko warunek utrzymania.
{
"correlationId": "8f2c1d7a-4b1e-4d8a-9a11-2c7f6d3e9b10",
"operationId": "order-update-10245",
"sourceSystem": "new-product",
"targetSystem": "legacy-erp",
"eventType": "OrderStatusChanged"
}Bez ustalonej procedury awaryjnej incydent bardzo szybko zamienia się w koszt operacyjny: ktoś z operacji blokuje ruch na wszelki wypadek, finanse nie wiedzą, które transakcje uznać za rozliczone, support zaczyna odpowiadać ręcznie na zgłoszenia, a zespół techniczny przeszukuje logi zamiast przywracać proces. Wtedy rośnie nie tylko czas niedostępności. Rośnie też liczba operacji do ręcznego uzgodnienia po incydencie.
Jeśli równolegle rozważacie, czy dalej poprawiać stary kod, czy przenosić odpowiedzialność do nowych usług, sprawdźcie też dylemat refaktoryzacja czy przepisanie kodu. Integracja jest pomostem, nie decyzją o docelowej architekturze.
Na etapie wyboru wykonawcy najpierw sprawdź trzy rzeczy: kto odpowiada za kontrakt, jak wygląda rollback dla zapisu i które procesy wejdą do pierwszej fali. Dopiero potem ma sens rozmowa o modernizacji systemów legacy. Bez tych odpowiedzi nawet sprawny zespół może szybko dostarczyć rozwiązanie tanie na starcie i drogie przez kolejne lata.