Stabilna integracja API w Laravel z ERP, e-commerce i systemem B2B opiera się na kilku elementach: każda operacja wymiany danych jest osobnym jobem w kolejce, ma ograniczoną liczbę prób z rosnącym odstępem (backoff), jest idempotentna, czyli jej powtórzenie nie tworzy duplikatu, zostawia ślad w logach i dzienniku operacji, a całość jest monitorowana i pokryta testami kontraktowymi. Taki zestaw sprawia, że chwilowa awaria ERP albo limit zapytań u partnera nie kończy się zgubionym zamówieniem. W tym artykule pokazujemy, jak to zbudować w praktyce w aktualnych wersjach frameworka (Laravel 12 i 13, PHP 8.3/8.4).
Architektura: nic nie dzieje się w żądaniu użytkownika
Pierwsza zasada: kontroler przyjmujący zamówienie z portalu nie woła API ERP. Zapisuje zamówienie w bazie i wysyła job do kolejki. Przykładowy przepływ:
- klient składa zamówienie – rekord zapisuje się w transakcji,
- po zatwierdzeniu transakcji do kolejki trafia job `PushOrderToErp` (dispatch z `afterCommit`, żeby worker nie wystartował przed zapisem danych),
- worker wysyła zamówienie do ERP przez klienta HTTP,
- odpowiedź (numer dokumentu w ERP) zapisuje się przy zamówieniu, a status zmienia na „przekazane”,
- przy błędzie job wraca do kolejki z opóźnieniem albo – po wyczerpaniu prób – trafia do tabeli nieudanych jobów i generuje alert.
Synchronizacje w drugą stronę (stany, ceny, statusy) uruchamia scheduler jako komendy cykliczne. Każdą warto oznaczyć `withoutOverlapping()`, żeby długi import nie nakładał się na kolejny, a na kilku serwerach – `onOneServer()`.
Kolejki i joby
Do integracji najlepiej sprawdza się kolejka na Redisie z Horizonem, który daje podgląd obciążenia, czasów wykonania i nieudanych jobów. Kilka praktyk:
- osobne kolejki dla różnych priorytetów – zamówienia nie mogą czekać za nocnym importem dziesiątek tysięcy cen,
- małe joby – zamiast jednego joba „zsynchronizuj cały katalog” lepiej wiele jobów po paczce rekordów, spiętych w `Bus::batch()` z obsługą zakończenia i błędu,
- timeout joba krótszy niż `retry_after` kolejki – inaczej ten sam job może zostać uruchomiony równolegle drugi raz,
- przekazywanie identyfikatorów, nie całych obiektów – job pobiera aktualny stan z bazy w momencie wykonania.
Retry i backoff
Laravel pozwala określić w jobie liczbę prób (`$tries`), odstępy między nimi (`$backoff`, także jako tablicę rosnących wartości) albo termin, po którym job przestaje być ponawiany (`retryUntil()`). Ważne jest rozróżnienie typów błędów:
- błędy przejściowe (timeout, 502/503, 429 – przekroczony limit) – ponawiamy z rosnącym odstępem,
- błędy danych (422, brak kontrahenta, nieprawidłowy indeks) – nie ponawiamy, tylko oznaczamy rekord jako wymagający poprawy i od razu kończymy job metodą `fail()`,
- błędy autoryzacji (401/403) – zatrzymujemy całą kolejkę danej integracji i alarmujemy, bo kolejne próby i tak się nie udadzą.
Przydatne są middleware jobów: `RateLimited` pilnuje limitu zapytań do API partnera, `ThrottlesExceptions` wstrzymuje próby, gdy zewnętrzny system seryjnie zwraca błędy, a `WithoutOverlapping` z kluczem, np. identyfikatorem zamówienia, zapobiega równoległemu przetwarzaniu tego samego rekordu. Sam klient HTTP ma też metodę `retry()` do krótkich ponowień w obrębie jednego wykonania.
Idempotencja
Ponowienia są bezpieczne tylko wtedy, gdy powtórzenie operacji nie zmienia wyniku. W praktyce:
- klucz idempotencji – każde zamówienie ma stały identyfikator z portalu, przekazywany do ERP (np. jako numer obcy). Przed utworzeniem dokumentu job sprawdza, czy dokument z tym numerem już istnieje,
- zapis stanu przed i po – job najpierw oznacza operację jako „w trakcie”, a po sukcesie zapisuje numer z ERP. Gdy worker padnie w połowie, kolejna próba wie, że trzeba najpierw sprawdzić stan po drugiej stronie,
- webhooki przychodzące – każde zdarzenie od partnera zapisujemy z jego identyfikatorem w tabeli z unikalnym indeksem; duplikat jest ignorowany,
- `ShouldBeUnique` – dla jobów synchronizacji jednego zasobu, żeby w kolejce nie stało kilka identycznych zadań.
Logi i dziennik integracji
Logi aplikacji (kanały Monolog) to za mało, bo służą programistom. Potrzebny jest też dziennik integracji w bazie, widoczny w panelu dla osób z obsługi:
- typ operacji, kierunek, identyfikator rekordu,
- status: oczekuje, w trakcie, sukces, błąd danych, błąd techniczny,
- liczba prób, czas ostatniej próby, treść błędu w zrozumiałej formie,
- skrócony zapis żądania i odpowiedzi (bez danych wrażliwych i tokenów),
- przycisk „ponów” i „oznacz jako rozwiązane”.
Warto dodać identyfikator korelacji przekazywany przez wszystkie kroki – od żądania w portalu, przez job, po log odpowiedzi ERP – żeby jedno zamówienie dało się prześledzić w kilka sekund.
Monitoring i alerty
- zdarzenie `JobFailed` i metoda `failed()` w jobie – powiadomienie na Slacka lub e-mail z linkiem do rekordu,
- alert na brak aktywności – jeśli od określonego czasu nie przyszła żadna synchronizacja stanów, coś jest nie tak, nawet jeśli nie ma błędów,
- metryki kolejek – długość kolejki i czas oczekiwania w Horizonie, z progiem alarmowym,
- raport dzienny – ile operacji, ile błędów danych do poprawy, ile ponowień,
- Laravel Pulse albo zewnętrzny APM do obserwacji wolnych zapytań i endpointów.
Testy kontraktowe
Integracja psuje się najczęściej wtedy, gdy druga strona zmienia format danych. Chronią przed tym:
- testy z `Http::fake()` – symulują odpowiedzi ERP, w tym błędy 429, 500 i timeouty, i sprawdzają, że job zachowa się poprawnie,
- zapisane przykładowe odpowiedzi (fixtures) z prawdziwego API, na których testujemy mapowanie,
- walidacja kontraktu – struktura odpowiedzi sprawdzana względem schematu (np. OpenAPI lub JSON Schema), jeśli partner go udostępnia,
- test dymny na środowisku testowym partnera uruchamiany cyklicznie – wykrywa zmianę API, zanim zrobi to produkcja,
- `Http::preventStrayRequests()` w testach – żaden test nie wyśle przypadkiem prawdziwego żądania.
Struktura kodu
Dobrze sprawdza się podział na warstwy: klient API (tylko komunikacja), mapper (tłumaczenie formatów), serwis (logika biznesowa, idempotencja) i job (orkiestracja, ponowienia). Dzięki temu wymiana ERP albo zmiana wersji API dotyka jednej warstwy, a nie całego systemu. Konfiguracja – adresy, klucze, limity – trafia do plików konfiguracyjnych i zmiennych środowiskowych, a nie do kodu.
Bezpieczeństwo połączeń
Klucze i tokeny do API partnerów przechowuj poza repozytorium, a tokeny OAuth odświeżaj w osobnym, zabezpieczonym blokadą mechanizmie, żeby kilka workerów nie próbowało robić tego jednocześnie. Webhooki przychodzące weryfikuj podpisem lub sekretem, a endpointy dla partnerów zabezpiecz osobnymi tokenami o ograniczonych uprawnieniach (np. przez Sanctum). W logach maskuj dane osobowe i dane uwierzytelniające.
Typowe błędy
- wywołanie API ERP bezpośrednio w kontrolerze,
- nieskończone ponawianie błędów danych,
- brak klucza idempotencji i duplikaty zamówień po timeoutach,
- jeden gigantyczny job importu, którego nie da się wznowić,
- logi bez identyfikatora rekordu.
Pułapki biznesowe, które warto sprawdzić przed kodowaniem, zebraliśmy w artykule o najczęstszych pułapkach integracji ERP z platformą B2B, a wzorce dla danych produktowych – w tekście o Laravel API dla e-commerce B2B i PIM.
Następny krok
Jeśli masz już integrację, sprawdź trzy rzeczy: czy zamówienia idą przez kolejkę, czy ponowienie może stworzyć duplikat i czy osoba z obsługi widzi błąd bez pytania programisty. Jeśli choć jedna odpowiedź brzmi „nie”, warto to poprawić przed kolejnym sezonem. Zobacz, jak budujemy Integracje API Laravel, i napisz do nas, jeśli chcesz przegląd istniejącego rozwiązania.
Najczęstsze pytania
Dlaczego nie wysyłać zamówienia do ERP bezpośrednio w kontrolerze?
Bo każda przerwa lub spowolnienie ERP zablokuje klienta albo zgubi zamówienie. Zapis w bazie i job w kolejce oddzielają działanie portalu od dostępności ERP i pozwalają bezpiecznie ponawiać operację.
Które błędy integracji warto ponawiać automatycznie?
Błędy przejściowe, takie jak timeouty, odpowiedzi 5xx i 429. Błędów danych, np. 422 czy brakującego kontrahenta, nie należy ponawiać, tylko oznaczyć rekord do poprawy.
Jak zapobiec duplikatom zamówień przy ponowieniach?
Stosuj stały klucz idempotencji, np. numer zamówienia z portalu przekazywany do ERP, i przed utworzeniem dokumentu sprawdzaj, czy już istnieje. Dla webhooków zapisuj identyfikator zdarzenia z unikalnym indeksem.
Po co testy kontraktowe w integracji z ERP?
Wykrywają zmiany formatu danych po stronie partnera, zanim zepsują produkcję. Łączy się w nich symulowane odpowiedzi, zapisane przykłady z prawdziwego API i cykliczny test na środowisku testowym partnera.
Jak przełożyć ten temat na projekt
W JiraSoft patrzymy na takie zagadnienia przez pryzmat realnego systemu: danych, backendu, integracji API, procesów B2B, widoczności w Google oraz przygotowania treści pod odpowiedzi AI. Dlatego artykuł jest punktem wyjścia do rozmowy o konkretnym workflow, a nie tylko ogólną notatką technologiczną.
