Sześć wzorców integracji do niezawodnego łączenia usług Node.js
Przyswoj podstawowe wzorce leżące u podstaw solidnych integracji z Node.js: model żądanie-odpowiedź, polling, webhooks, klucze API, autoryzacja JWT i OAuth, ponawianie prób z mechanizmem backoff oraz mapowanie danych.
Źródła: 6 API Integration Patterns Every Node.js Integration Engineer Must Know
Wysłanie żądania HTTP jest proste; prawdziwym wyzwaniem jest utrzymanie sprawnego działania integracji, gdy drugi system działa wolno, jest niedostępny lub ma inną strukturę. Ten przewodnik omawia sześć wzorców, które pojawiają się podczas komunikacji usług Node.js z dostawcami płatności, systemami CRM, ERP oraz ze sobą nawzajem, a także pułapki, które odróżniają demonstrację od niezawodnej integracji.
1. Żądanie–odpowiedź: synchroniczna podstawa
W modelu żądanie–odpowiedź klient wysyła żądanie, serwer wykonywa pracę, a odpowiedź wraca przez tę samą łącze. Obejmuje on większość codziennych operacji: logowanie, tworzenie lub aktualizację rekordów, pobieranie informacji o klientach, usuwanie zasobów.
app.get("/users/:id", async (req, res) => {
const user = await db.findUser(req.params.id);
res.json(user);
});
Jest łatwy do debugowania i stanowi standard dla operacji CRUD. Jego wadą są zadania trwające długo: duży raport lub duży plik mogą przekroczyć limity czasowe klienta lub proxy. Należy również zauważyć, że ten obsługa zwraca null przy stanie 200, gdy użytkownik nie istnieje; prawdziwy endpoint powinien zwracać 404.
2. Polling: pytanie do momentu zakończenia zadania
Gdy wynik nie może zostać wygenerowany natychmiast, serwer może przyjąć zadanie, zwrócić identyfikator i pozwolić klientowi sprawdzać jego stan. Typowymi przypadkami są oczekiwanie na zatwierdzenie płatności, wygenerowanie raportu, konwersję wideo lub wysyłkę zamówienia.
Rozpoczęcie zadania zwraca jego ID:
POST /reports
Response:
{
"jobId": "abc123"
}
Klient następnie sprawdza endpoint stanu w określonych odstępach czasu:
GET /reports/abc123/status
do momentu, aż zadanie poinformuje o swoim zakończeniu:
{
"status": "completed"
}
Polling jest prosty i nie stanowi problemu dla firewalli, dlatego wiele systemów korporacyjnych nadal na nim polega. Kosztem jest marnowany ruch sieciowy, ponieważ większość sprawdzeń zwraca wartość „jeszcze nie”. Należy rozrzedzać żądania (przestrzegając pola Retry-After, jeśli jest ono wysyłane), ustalić ogólny termin realizacji oraz obsłużyć zarówno przypadki failed, jak i completed.
3. Webhooki: umożliwienie drugiej stronie kontaktu z tobą
Webhooki zmieniają kierunek komunikacji. Zamiast twoja usługa pytać wielokrotnie, dostawca wysyła żądanie HTTP do twojego endpointu, gdy coś się wydarzy: transakcja płatności została zrealizowana, zamówienie zostało złożone, nadeszła wiadomość, klient się zarejestrował lub zmieniła się ilość towaru. Stripe, Slack, GitHub i Shopify w dużej mierze opierają się na tym modelu.
Minimalny odbiornik w Express:
app.post("/webhook/payment", (req, res) => {
const event = req.body;
if (event.type === "payment.success") {
console.log("Payment received!");
}
res.sendStatus(200);
});
To pokazuje podstawową strukturę, ale odbiornik produkcyjny wymaga czegoś więcej:
- Weryfikuj nadawcę. Każdy może publikować treści na publicznej adrese URL. Usługodawcy zazwyczaj podpisują każdą przesyłaną informację, więc musisz sprawdzić ten podpis (zwykle w porównaniu z surowym treścią żądania), zanim zaufasz danemu wydarzeniu.
- Oczekuj duplikatów. Przesyłanie danych jest zazwyczaj powtarzane, dopóki nie zostanie potwierdzone, więc to samo wydarzenie może dotrzeć kilka razy. Przechowuj zidentyfikowane ID wydarzeń i upewnij się, że mechanizmy obsługi są idempotentne.
- Potwierdzaj szybko. Natychmiast zwróć odpowiedź typu 2xx i odłóż wolniejsze operacje do kolejki; w przeciwnym razie usługodawca może wykonać timeout i wysłać dane ponownie.
Integracja oparta na wydarzeniach jest coraz częściej preferowana, ponieważ eliminuje ruch związany z pollingiem.
4. Autoryzacja: potwierdzenie tożsamości nadawcy
Każda integracja zaczyna się od danych uwierzytelniających. Trzy podejścia obejmują większość przypadków.
Klucze API
Najprostszy schemat polega na wysyłaniu wspólnego sekretu w nagłówku:
GET /users
x-api-key: abc123xyz
W Node.js z użyciem axios klucz powinien pochodzić z konfiguracji, a nie z kodu źródłowego:
axios.get(url, {
headers: {
"x-api-key": process.env.API_KEY
}
});
Klucze API nadają się do narzędzi wewnętrznych oraz prostych integracji serwer-serwer. Same w sobie nie zawierają informacji o terminie ważności ani tożsamości użytkownika, dlatego należy je regularnie rotować i ograniczać ich zakres tak ściśle, jak to pozwala dostawca.
JSON Web Tokens
JWT umożliwiają autoryzację bez przechowywania stanu. Zwykła sekwencja działania:
- Użytkownik się loguje.
- Serwer wydaje podpisany token.
- Klient przechowuje ten token.
- Każda późniejsza żądanie zawiera go w nagłówku:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Wydawanie takiego tokena w Node.js:
const token = jwt.sign(
{ userId: 1 },
process.env.JWT_SECRET
);
Każda usługa posiadająca klucz weryfikacyjny może walidować token bez potrzeby przechowywania sesji, co jest przydatne w mikrousługach. Problemem jest odwołanie uprawnień: token pozostaje ważny do momentu wygaśnięcia, dlatego zawsze należy ustawić expiresIn, czego ten fragment nie robi.
OAuth 2.0
OAuth 2.0 umożliwia delegowane dostępy: „Zaloguj się przez Google”, logowanie na Facebooku, Microsoft Enterprise Single Sign-On, łączniki CRM oraz dowolne aplikacje third-party działające w imieniu użytkownika. W typowym procesie kodu autoryzacji:
- Użytkownik zatwierdza żądany dostęp u dostawcy.
- Dostawca przekierowuje użytkownika z krótkotrwałym kodem autoryzacji.
- Twój serwer wymienia ten kod na token dostępu.
- Zapytania API są wysyłane przy użyciu tego tokena dostępu.
Warto zgłębić: tokeny dostępu i odnowienia, schemat Authorization Code, schemat Client Credentials do połączeń maszyna-maszyna, PKCE oraz OpenID Connect – warstwę tożsamości budowaną na bazie OAuth.
5. Odporność: projektowanie z myślą o awariach
Sieci mogą wykonać timeout, a zależności przestać działać. Niezawodne integracje zakładają, że tak się może zdarzyć.
Ponawianie nieudanych prób
Bazowy cykl ponawiania:
for (let i = 0; i < 3; i++) {
try {
return await axios.get(url);
} catch (error) {
console.log("Retrying...");
}
}
W obecnej formie próba jest ponawiana natychmiast, bez przerwy, a jeśli wszystkie trzy próby zawiodą, zwraca się undefined zamiast rzucić ostatni błąd, więc wywołujący nie wie, co poszło nie tak. Należy rzucić błąd ponownie po ostatniej próbie i ponawiać tylko tymczasowe awarie (błędy sieci, timeouty, kod 5xx, 429), a nie błędy 4xx, które zawsze powodują ten sam problem. Uważaj przy ponawianiu nieidempotentnych żądań, takich jak płatności, chyba że API obsługuje klucze idempotencji.
Czas wygaśnięcia
Zapytanie bez ustawionego czasu wygaśnięcia może pozostać w trakcie realizacji nieograniczenie długo, blokując przy tym zasoby. Zawsze ustaw taki czas:
axios.get(url, {
timeout: 5000
});
Eksponencjalne opóźnianie prób
Powtarzanie prób w stałych odstępach czasu prowadzi do ciągłego obciążania usługi, która ma problemy z działaniem:
1s
1s
1s
1s
Podwajanie czasu oczekiwania za każdym razem daje usłudze szansę na odzyskanie sprawności:
1s
2s
4s
8s
16s
Obliczenie odbywa się w jednej linii kodu:
const delay = attempt =>
Math.pow(2, attempt) * 1000;
Ustal maksymalny czas opóźnienia oraz dodaj losowe odchylenia, aby wiele klientów awaryjnych nie próbowało ponownie w tym samym momencie.
Bezpieczniki obwodowe
Bezpiecznik obwodowy liczy niepowodzenia w komunikacji z usługą poniżej w łańcuchu i, po przekroczeniu określonego progu, zaprzestaje do niej wysyłać żądania na określony czas chłodzenia, zanim wyśle kolejne próbne żądanie. Chroni to przed kaskadowymi awariami, wyczerpaniem zasobów oraz przerwami w całym systemie. Opossum jest powszechnie używanym rozwiązaniem w Node.js; Resilience4j pełni tę samą funkcję w Javie, natomiast Hystrix występuje w starszych systemach.
6. Transformacja danych: konwersja między systemami
Dwa systemy rzadko zgadzają się co do struktury danych, więc duża część pracy integracyjnej polega na ich konwersji.
Mapowanie pól
Jeden system używa notacji camelCase:
{
"firstName": "John"
}
inny oczekuje notacji snake_case:
{
"first_name": "John"
}
Niewielki konwerter łączy te dwa formaty:
function transform(user) {
return {
first_name: user.firstName
};
}
Czyste funkcje konwertera są łatwe do testowania jednostkowego i pozwalają uniknąć wpływu specyficznych cech dostawców na logikę biznesową.
JSON i XML
Wiele starszych systemów nadal używa formatu XML, podczas gdy nowsze usługi generują JSON, więc warstwa integracyjna często przekształca dane pomiędzy tymi formatami w obu kierunkach.
Data
Jedna ze stron może wysyłać dane w formacie ISO:
2026-06-26
podczas gdy druga oczekuje notacji z dniem na początku:
26/06/2026
Konwertuj dane wyraźnie i bądź precyzyjny co do stref czasowych, gdy wartości zawierają godziny; data przesunięta po północy to klasyczny błąd integracyjny.
Ilości walutowe
Jeden serwis raportuje kwotę z dziesiętnym ułamkiem:
{
"amount": 10.50
}
Drugi wymaga całkowitych jednostek ułamkowych:
{
"amount_cents": 1050
}
Wartości zmiennoprzecinkowe nie mogą dokładnie reprezentować każdej dziesiętnej liczby, dlatego podczas konwersji należy je zaokrąglić (na przykład Math.round(amount * 100)) i wewnętrznie używać całkowitych wartości w centach.
Jak wzorce łączą się w praktyce
Typowa architektura przedsiębiorstwa umieszcza bramkę Node.js przed kilkoma backendami:
Frontend
|
Node.js API Gateway
|
+-- Authentication Service
+-- Payment Service
+-- CRM Service
+-- ERP Systems
+-- Notification Service
Te połączenia łączą powyższe wzorce: API typu żądanie-odpowiedź, JWT i OAuth, webhooki, mechanizmy ponawiania prób i ochrony przed awariami, warstwy transformacji, a także monitorowanie i logowanie. Dla inżynierów przechodzących do pracy z integracjami największe korzyści przynoszą API REST, webhooki, JWT, OAuth 2.0, transformacja danych i projektowanie oparte na zdarzeniach, a następnie techniki ponawiania prób i odporności systemów, wreszcie mechanizmy pollingu i ochrony przed awariami.
Główne wnioski
- Używaj modelu żądanie-odpowiedź do szybkich operacji; przejdź na polling lub webhooks, gdy praca zajmuje więcej czasu.
- Weryfikuj podpisy webhooków, usuwaj duplikaty zdarzeń i szybko potwierdzaj odbiór.
- Zachowuj dane uwierzytelniające w konfiguracji, ustaw termin ważności tokenów i dokładnie poznaj mechanizmy OAuth.
- Próbuj ponownie tylko w przypadku tymczasowych błędów, z użyciem timeoutów oraz ograniczonego eksponencjalnego backoffu i jittera.
- Izoluj mapowanie danych w przetestowanych funkcjach czystych i zwracaj szczególną uwagę na daty oraz kwoty pieniężne.