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.




