API Gateway dla Gminy Dopiewo: Otwarte dane publiczne

Gmina Dopiewo potrzebowała nowoczesnej bramy API, która otwierałaby dostęp do danych publicznych, ale jednocześnie chroniłaby backend przed atakami i przeciążeniem. Chodziło o to, żeby system był szybki, bezpieczny i gotowy na kolejne źródła danych – bo przecież systemy IT w tego typu instytucjach to często mozaika różnych rozwiązań.
Punkt wyjścia: Drupal i wyzwanie
W zakresie strony informacyjnej gmina korzysta z CMS Drupal, który udostępnia dane przez JSON:API. Problem? Bezpośrednie wystawianie Drupala na internet to proszenie się o kłopoty. No i te odpowiedzi JSON:API potrafią być naprawdę rozbudowane – czasem za dużo jak na potrzeby mobilnej apki czy szybkiej strony przeznaczonej do wąskiego zastosowania.
Dlatego postawiliśmy na API Gateway – warstwę pośrednią, która robi całą brudną robotę. FastAPI jako serce aplikacji (Python), Nginx z OpenResty do obsługi ruchu i Lua do walidacji requestów, plus Redis jako pamięć podręczna. Wszystko w Dockerze, żeby łatwo się stawiało i zarządzało.
Bezpieczeństwo na kilku poziomach
Nie wystarczy postawić firewall i mieć nadzieję na najlepsze. Zaaplikowaliśmy obronę warstwową.
Rate limiting zanim request w ogóle dotrze do aplikacji – Lua w Nginx sprawdza, ile kto wysłał zapytań w ciągu minuty. Domyślnie 100 requestów. Przekroczysz? Dostajesz HTTP 429 z informacją, kiedy spróbować ponownie. Liczniki trzymamy w Redis, więc działa to błyskawicznie i można skalować poziomo.
Walidacja requestów też na poziomie Nginx – zanim zapytanie trafi do FastAPI, Lua przegląda je pod kątem SQL injection, XSS, path traversal i innych klasycznych ataków. Blokuje podejrzane wzorce, loguje próby, zwraca czytelny błąd. To oszczędza zasoby aplikacji i redukuje powierzchnię ataku.
Klucze API z sensownym cyklem życia – generujemy je kryptograficznie, hashujemy (SHA-256 z solą), trzymamy w Redis z metadanymi. Każdy klucz ma swój limit requestów, datę ważności, informacje o ostatnim użyciu. Administrator może je rotować, cofać dostęp, sprawdzać statystyki. Środowisko testowe dostaje klucze z prefiksem dk_test_, produkcja to dk_live_.
Circuit breaker pilnuje backendu – jeśli Drupal zaczyna “produkować błędy” (próg: 5 kolejnych niepowodzeń), gateway przestaje do niego rozmawiać na 30 sekund. W tym czasie zwracamy dane z cache’a albo informujemy, że serwis chwilowo niedostępny. Jak backend wróci do zdrowia, powoli wznawiamy ruch. To chroni przed kaskadowymi awariami.
Cache – serce wydajności
Redis robi tutaj dużo roboty. Każdy endpoint ma swój czas życia w cache’u – aktualności trzymamy 10 minut, pojedyncze artykuły pół godziny, miejsca na mapie godzinę, a dashboard ze zbiorczymi danymi tylko 3 minuty (bo tam są świeże rzeczy).
Kluczowe wyzwanie? Zapobieganie “cache stampede” – sytuacji, gdy cache wygaśnie i nagle sto requestów leci po te same dane do Drupala. Używamy rozproszonych locków w Redis. Pierwszy request blokuje klucz, pobiera dane, zapisuje w cache’u. Reszta czeka. Proste, ale skuteczne.
Statystyki mówią same za siebie – pod normalnym obciążeniem mamy ponad 80% trafień w cache. To oznacza, że Drupal dostaje raptem 20% ruchu, jaki widzimy na API Gateway. A czasy odpowiedzi? Z cache’a to często poniżej 50 ms. Bez – od 200 do 500 ms, bo trzeba pójść do Drupala, przetworzyć JSON:API, spakować odpowiedź.
Kompresja i pooling – nie marnujemy zasobów
Odpowiedzi kompresujemy gzipem (stosunek 70-80% dla JSON-ów), więc użytkownicy na słabszym łączu też mają szybko. Nginx obsługuje to automatycznie na podstawie nagłówka Accept-Encoding.
Połączenia z Drupalem trzymamy w poolach – HTTP/1.1 keep-alive, maksymalnie 100 połączeń, 20 w trybie czuwania. Zamiast otwierać nowe połączenie TCP przy każdym requeście, wykorzystujemy te same. To redukuje latencję i obciążenie sieciowe.
Jak Drupal zwróci błąd 5xx (problemy po jego stronie), próbujemy jeszcze dwa razy z rosnącymi przerwami (1s, 2s, 4s). Błędów 4xx (np. nie znaleziono zasobu) nie ma sensu ponawiać, więc je pomijamy. Mądre retries chronią przed chwilowymi problemami, nie zwiększając niepotrzebnie obciążenia.
Agregacja i wyszukiwanie – dane jakich potrzebujesz
Gateway nie tylko przepuszcza requesty dalej. Potrafi robić rzeczy, których Drupal sam nie zrobi łatwo.
Dashboard to jeden endpoint, który zbiera dane z czterech źródeł równolegle – najnowsze artykuły, nadchodzące wydarzenia, ostatnie pytania do wójta, statystyki. Dla klienta to jeden request zamiast czterech. Dla nas – asyncio załatwia sprawę równolegle, więc czekamy na najwolniejszy z nich, nie na sumę wszystkich.
Wyszukiwanie pełnotekstowe przeszukuje wszystkie typy treści naraz. Zapytanie trafia do articles, events, places, questions – gateway łączy wyniki, punktuje po trafności (tytuł waży najwięcej, potem streszczenie, potem treść), sortuje i zwraca w jednym pakiecie. Plus facets – ile wyników w każdym typie, żeby użytkownik wiedział, gdzie szukać dalej.
Geolokalizacja – znajdź miejsca w promieniu X kilometrów od punktu. Używamy wzoru Haversine (uwzględnia krzywiznę Ziemi), liczymy odległości dla wszystkich miejsc z współrzędnymi, filtrujemy, sortujemy od najbliższych. Działa błyskawicznie nawet dla setek punktów.
Powiązane artykuły – dla danego artykułu znajdujemy inne z tych samych kategorii. Proste, ale użyteczne – czytelnicy zostają dłużej, bo mają co klikać.
Gotowość na kolejne źródła danych
Tu jest sedno elastyczności. Gateway od początku projektowaliśmy tak, żeby nie był przyklejony do Drupala.
Architektura to: klient → Nginx/Lua (walidacja, rate limit) → FastAPI (logika, cache) → serwisy zewnętrzne (Drupal, ale może być więcej). Dodawanie nowego źródła? Piszesz kolejny klient w app/services/, definiujesz transformacje w app/services/transformer.py, dodajesz endpoint w routerach. Cache i circuit breaker dostaniesz w pakiecie.
Transformacje to oddzielna warstwa. JSON:API z Drupala ma swoją specyfikę (relacje, atrybuty). Gateway to wszystko spłaszcza do prostych struktur – id, tytuł, data, URL. Jak pojawi się nowe źródło (powiedzmy API pogody albo dane z systemu wydawania dokumentów), piszemy dla niego transformator i gotowe. Dla klienta wszystko wygląda spójnie.
Cache wspiera różne TTL-e per endpoint. Możesz mieć dane z Drupala cache’owane na godzinę, a z systemu zgłoszeń na minutę. Konfiguracja w zmiennych środowiskowych.
Circuit breaker działa per backend. Jak Drupal padnie, gateway dalej może serwować dane z innych źródeł. Każdy backend ma swój stan (zamknięty, otwarty, pół-otwarty), swoje progi, swoje timeouty.
Produkcja i monitoring
Wszystko to nie ma sensu bez obserwacji. Gateway wystawia metryki dla Prometheusa – ile requestów, jakie czasy odpowiedzi, hit rate cache’a, stan circuit breakerów, błędy backendu. Logi strukturalne w JSON, łatwe do parsowania przez narzędzia typu ELK.
Healthchecks dla każdego komponentu: /health (aplikacja), /health/redis (cache), /health/drupal (backend), /health/all (wszystko naraz). Monitoring zewnętrzny może je odpytywać co minutę i alarmować, jak coś nie gra.
Testy obciążeniowe pokazały, że gateway obsługuje ponad 1000 requestów na sekundę na przeciętnym serwerze. Rate limiter wyrzuca nadmiar (HTTP 429), circuit breaker chroni backend, cache redukuje obciążenie do minimum. System jest odporny.
Co dalej?
Projekt przeszedł przez 5 faz – podstawy, bezpieczeństwo, cache, transformacje danych, agregacja i wyszukiwanie. Teraz pracujemy nad fazą 6 (dokumentacja OpenAPI, przykłady integracji) i fazą 7 (Prometheus, Grafana, logi).
Ważniejsze? Gateway jest już w produkcji, obsługuje ruch, chroni Drupala, daje szybkie odpowiedzi. I jest gotowy na to, co przyniesie przyszłość – nowe systemy, nowe źródła danych, większy ruch.
Kontakt z nami
Jeśli jesteś zainteresowany/-a wdrożeniem pośredników API dla swojej organizacji, napisz do nas.
