Net-Base Magazyn

09.06.2026

REST API z RemObjects SDK: czyste wersjonowanie i debugowanie punktów końcowych JSON (Delphi fragmenty kodu źródłowego)

Jak zbudować za pomocą RemObjects SDK w Delphi REST API, które nie zawiedzie w eksploatacji: stabilne kontrakty JSON, wersjonowanie bez dzikiego rozrostu URL‑ów, Correlation‑ID przenikające wszystkie warstwy, centralne mapowanie błędów, snapshot‑logging dla trudnych przypadków debugowania oraz praktyczne wskazówki

09.06.2026

Od tematu magazynowego do praktyki projektowej

Pasujące strony usługowe i techniczne do artykułu

Dlaczego „REST API z RemObjects SDK” w praktyce często rozstrzyga się na obrzeżach

Rzadko REST API z RemObjects SDK ocenia się po „Hello World” — decydują miejsca, w których operacje, rozwiązania legacy i integracja się ścierają: wersjonowanie bez przestojów, jednolite zachowanie w przypadku błędów na wszystkich endpointach, odtwarzalne debugowanie w łańcuchach proxy oraz zdolność jednoznacznego skorelowania żądań w sytuacjach awaryjnych.

RemObjects SDK dostarcza wiele podstawowej infrastruktury: serwisy, formaty komunikatów, serializację, hosting (np. jako Windows- i Linux-Services lub za IIS/Reverse Proxy) oraz wyraźne miejsca do centralnego obsługiwania błędów. W rozrastających się środowiskach biznesowego oprogramowania często brakuje jednak konsekwentnie wdrożonego kontraktu: które pola JSON są stabilne? Jak sygnalizujemy błędy? Jak rozpoznać żądanie, które przeszło przez Load Balancer, TLS-termination i wiele warstw backendu?

Następujące podejście (wraz ze Delphi-snipami) przedstawia wyraźną i odporną linię dla RemObjects SDK: wersjonowanie kontraktów JSON, wymuszenie Correlation-ID (Request-ID do śledzenia), tłumaczenie Exceptions na statusy HTTP i obiekty błędów JSON przy jednoczesnym niezestawianiu debugowania przeciw operacji. Dodatkowo omawiamy przypadki brzegowe, które w rzeczywistych środowiskach występują regularnie: wielowątkowość serwera, dostęp do bazy danych przy BDE-Ablösung z natywnym podłączeniem, nagłówki proxy, timeouty oraz „brudne” payloady klientów.

Decyzja architektoniczna: wersjonowanie przez Media Type zamiast URL

Wiele API wersjonuje się przez ścieżki typu /v1/. To pragmatyczne, ale w długotrwałych integracjach (np. przy podłączeniach ERP/DMS/CRM) często prowadzi do duplikacji URL, podwójnych tras, podwójnych testów i pytania „Której wersji właściwie używamy?” w dokumentacji operacyjnej.

Alternatywą jest wersjonowanie przez Media Type (Content Negotiation). Klient wysyła np. Accept: application/vnd.company.order+json;v=2. Serwer odczytuje wersję deterministycznie i dostosowuje zachowanie kontraktu/DTO. Działa to w łańcuchach proxy i cache, jeśli nagłówki są poprawnie przekazywane. Dla administratorów jest to też dobrze weryfikowalne: żądanie można odtworzyć przez Curl/Postman bez zmiany URL.

RemObjects SDK nie jest „REST-purystyczny”, lecz pragmatycznym frameworkiem serwisowym. Dlatego wariant oparty na typie mediów ma sens: można zachować stabilne endpointy i jednocześnie rozwijać kontrakty. Ważne jest, aby wersję zawsze analizować, decydować centralnie w jednym miejscu i przekazywać wynik do kontekstu serwisu.

Kiedy wariant oparty na nagłówku Accept zawodzi?

W praktyce istnieją trzy typowe punkty krytyczne, które warto zaadresować uprzednio:

  • Proxy-Policies: Niektóre Reverse Proxies/reguły WAF normalizują lub filtrują nagłówek Accept. W takim wypadku API cicho wraca do wartości domyślnej. Rozwiązanie: sprawdzić reguły proxy eksplicytnie, ewentualnie użyć pola X-Api-Version jako obejścia.
  • Client-Libraries: Niektóre biblioteki HTTP ustawiają własne nagłówki Accept i nadpisują wartości. Rozwiązanie: wspierać wersję kontraktu też jako opcjonalny parametr query (tylko jako fallback) lub tolerancyjnie parsować Accept po stronie serwera.
  • Caching: Wenn Response-Caching im Spiel ist, muss der Cache nach Accept variieren (Vary: Accept), sonst liefert er Version 1 an Version-2-Clients. Lösung: Vary bewusst setzen, oder Caching auf API-Ebene deaktivieren.

Fragment źródłowy: Request-Context, Correlation-ID, wersja i mapowanie błędów

Kod jest zaprojektowany tak, aby można go było zintegrować z istniejącymi projektami serwerowymi RemObjects: niewielka warstwa kontekstu, parser wersji API (z nagłówka Accept), mechanizm Correlation-ID oraz centralne mapowanie wyjątków. Pojęcia:

  • Correlation-ID: Unikalny identyfikator dla żądania, który pojawia się również w odpowiedzi i jest odwoływany w logach.
  • Exception-Mapping: Tłumaczenie wewnętrznych Delphi-wyjątków na stabilne, przez klienta przetwarzalne obiekty błędów (w tym status HTTP).
  • Contract-Version: Wersja kontraktu JSON, która steruje zachowaniem i polami.
Delphi
unit Api.Infrastructure;

interface

uses
  System.SysUtils, System.Classes, System.StrUtils, System.Generics.Collections,
  System.JSON;

type
  EApiError = class(Exception)
  private
    FHttpStatus: Integer;
    FCode: string;
    FCorrelationId: string;
  public
    constructor Create(const AHttpStatus: Integer; const ACode, AMessage, ACorrelationId: string);
    property HttpStatus: Integer read FHttpStatus;
    property Code: string read FCode;
    property CorrelationId: string read FCorrelationId;
  end;

  TApiContext = record
    CorrelationId: string;
    ContractVersion: Integer;
    RemoteIp: string;
    UserAgent: string;
    class function New: TApiContext; static;
  end;

  TApiVersion = record
    class function FromAcceptHeader(const AAccept: string; const ADefault: Integer = 1): Integer; static;
  end;

  TApiErrorMapper = class
  public
    class function ToErrorJson(const E: Exception; const ACorrId: string): TJSONObject; static;
    class function ToHttpStatus(const E: Exception): Integer; static;
    class function SafeMessage(const E: Exception): string; static;
  end;

implementation

{ EApiError }

constructor EApiError.Create(const AHttpStatus: Integer; const ACode, AMessage, ACorrelationId: string);
begin
  inherited Create(AMessage);
  FHttpStatus := AHttpStatus;
  FCode := ACode;
  FCorrelationId := ACorrelationId;
end;

{ TApiContext }

class function TApiContext.New: TApiContext;
begin
  Result.CorrelationId := '';
  Result.ContractVersion := 1;
  Result.RemoteIp := '';
  Result.UserAgent := '';
end;

{ TApiVersion }

class function TApiVersion.FromAcceptHeader(const AAccept: string; const ADefault: Integer): Integer;
// Erwartet z.B.: application/vnd.company.order+json;v=2
var
  Parts: TArray<string>;
  P: string;
  V: string;
  I: Integer;
begin
  Result := ADefault;
  if AAccept.Trim.IsEmpty then
    Exit;

  Parts := AAccept.Split([';', ',']);
  for P in Parts do
  begin
    V := Trim(P);
    if StartsText('v=', V) then
    begin
      if TryStrToInt(Copy(V, 3, MaxInt), I) and (I > 0) and (I < 100) then
        Exit(I);
    end;
  end;
end;

{ TApiErrorMapper }

class function TApiErrorMapper.SafeMessage(const E: Exception): string;
// Im Betrieb keine internen Details, keine SQL, keine Pfade.
// Für Debug/Stage kann man das über Konfiguration erweitern.
begin
  if E is EApiError then
    Exit(E.Message);

  if E is EArgumentException then
    Exit('Ungültige Parameter.');

  Exit('Interner Fehler.');
end;

class function TApiErrorMapper.ToHttpStatus(const E: Exception): Integer;
begin
  if E is EApiError then
    Exit(EApiError(E).HttpStatus);

  if E is EArgumentException then
    Exit(400);

  Exit(500);
end;

class function TApiErrorMapper.ToErrorJson(const E: Exception; const ACorrId: string): TJSONObject;
var
  Code: string;
  Status: Integer;
  Msg: string;
begin
  Status := ToHttpStatus(E);
  Msg := SafeMessage(E);

  if E is EApiError then
    Code := EApiError(E).Code
  else if E is EArgumentException then
    Code := 'bad_request'
  else
    Code := 'internal_error';

  Result := TJSONObject.Create;
  Result.AddPair('error', TJSONObject.Create
    .AddPair('code', Code)
    .AddPair('message', Msg)
    .AddPair('httpStatus', TJSONNumber.Create(Status))
    .AddPair('correlationId', ACorrId));
end;

end.

Cel: Stabilny kontekst żądania zamiast „gdzieś w threadlocal”

Fragment celowo rozdziela: TApiContext to minimalny stan, który należy przekazywać. W RemObjects SDK wiele odbywa się w kontekście serwera/kanalu. W heterogenicznych projektach (np. dodatkowe wątki robocze, kolejka DB, zadania w tle) jawne przekazywanie kontekstu jest często bardziej odporne niż ukryte threadlocale, ponieważ sprawia, że współbieżność i zmiany kontekstu są bardziej przejrzyste.

Warunki: Wariant z nagłówkiem Accept zakłada, że reverse proxy (nginx, IIS ARR, Traefik) przekazuje nagłówek bez zmian. W niektórych środowiskach „nietypowe” nagłówki Accept są filtrowane lub scalane.

Pułapki: Wersjonowanie przez Accept jest tak dobre, jak Twoje testy. Jeśli biblioteki klienckie nadpisują Accept, API może niespodziewanie wrócić do wartości domyślnej. Dla klientów legacy sensowny jest domyślny fallback, ale musi być widoczny w monitoringu (np. ostrzeżenie w logu „Version defaulted”).

Alternatywy: Jeśli wolisz wersjonowanie przez X-Api-Version: parser jest identyczny, zmienia się tylko źródło — inny nagłówek. Z punktu widzenia gatewayów czasem łatwiej to kontrolować.

Integracja w RemObjects SDK: Correlation-ID i mapowanie wyjątków na wejściu serwisu

Rzeczywisty efekt uzyskuje się, gdy mechanikę stosuje się konsekwentnie na brzegu serwera: raz przy wejściu żądania odczytać z nagłówków, raz przy wyjściu wyjątków przetłumaczyć na stabilną odpowiedź. W zależności od hostingu (np. RO-HTTP-Server, IIS-Hosting, samodzielnie uruchamiane Windows-/Windows- i Linux-Services) konkretne punkty podczepienia będą się różnić; zasada pozostaje ta sama: zbudować kontekst, wywołać logikę biznesową, centralnie zmapować wyjątki.

W projektach RemObjects często pracuje się bezpośrednio w ramach pojedynczej metody serwisowej. Na początku to dobrze skaluje, ale w eksploatacji się psuje: każda metoda buduje logowanie i obsługę błędów inaczej. Czystym rozwiązaniem jest podstawa serwisu lub dispatcher, który to standaryzuje.

Praktyczny przebieg (świadomie krótko i zorientowany implementacyjnie)

  1. Odczytać Correlation-ID z nagłówka żądania X-Correlation-ID; jeśli brak, wygenerować po stronie serwera (np. GUID).
  2. Odczytać wersję kontraktu z Accept (lub z X-Api-Version).
  3. Zalogować start żądania: metoda, ścieżka, Correlation-ID, IP klienta, rozpocząć pomiar czasu.
  4. Wykonać logikę biznesową; dostępy do DB otoczyć — o ile to możliwe — transakcją.
  5. Przechwycić wyjątek: określić status HTTP, utworzyć JSON-obiekt błędu, ustawić w nagłówku odpowiedzi X-Correlation-ID.
  6. Zalogować koniec żądania: status, czas trwania, ew. kod błędu.

Wielowątkowość na serwerze: dlaczego Correlation-ID bez dyscypliny kontekstu jest bezwartościowy

Częsty Delphi przypadek brzegowy: metoda serwisowa uruchamia pracę asynchroniczną (np. generowanie raportu, import, push do DMS). Wtedy pierwotny wątek żądania nie jest tym samym wątkiem, który później zapisuje wpisy w logu. Jeśli Correlation-ID jest znana tylko „na początku”, śledzenie ulega rozproszeniu.

Pragmatyczna zasada: wszystko, co nie pozostaje ściśle w wątku żądania, powinno otrzymać kontekst przekazany jawnie. Nawet jeśli oznacza to dłuższe listy parametrów, opłaca się to. Alternatywnie można użyć jasno zdefiniowanego obiektu kontekstowego, który świadomie przekazuje się do workerów (zamiast globalnych zmiennych lub ukrytych singletonów).

Typowe punkty krytyczne w serwerach RemObjects-/Delphi:

  • Połączenia DB na wątek: BDE-Ablosung mit nativer Anbindung-połączenia nie są automatycznie bezpiecznie współdzielone między wątkami. Pool połączeń lub jedno połączenie na wątek jest często sensowniejsze niż „globalne połączenie”.
  • Granice transakcji: Jeśli w ramach jednego requestu występuje kilka kroków należących do tej samej operacji, transakcja musi pozostać w tej samej logicznej jednostce. Prace asynchroniczne nie mogą „przypadkowo” kontynuować się w tej samej transakcji.
  • Anulowanie (Cancellation): Gdy klient przerywa połączenie (timeout proxy, zamknięcie przeglądarki), serwer często działa dalej. Zastanów się świadomie, czy praca w tle ma wtedy sens.

Dostęp do danych i kody błędów: 409 to nie „też 500”

W projektach integracyjnych uporządkowane mapowanie błędów to więcej niż kosmetyka. Decyduje o tym, czy partner (ERP-Connector, ETL-Job, portal klienta) może poprawnie zareagować. Kilka praktycznych wytycznych, które sprawdziły się w środowiskach Delphi/RemObjects:

  • 400 Bad Request: walidacja, brakujące/nieprawidłowe parametry, JSON nieparsowalny. Ważne: odpowiedź powinna być stabilna, nawet jeśli treść żądania jest uszkodzona.
  • 401/403: Rozdziel uwierzytelnianie i autoryzację. 401 oznacza „brak/nieprawidłowa tożsamość”, 403 „tożsamość ok, ale zabronione”.
  • 404: Zasób nie istnieje. Uwaga w kontekście bezpieczeństwa: nie zawsze ujawniać, czy coś istnieje.
  • 409 Conflict: konflikt biznesowy (np. konflikt wersji, „status nie pozwala na tę akcję”, naruszenie unikatowego klucza, jeśli ma znaczenie biznesowe).
  • 422 Unprocessable Content: gdy składnia jest poprawna, lecz walidacja biznesowa nie przechodzi (nie każdy zespół używa 422, ale często jest to jaśniejsze niż 400).
  • 500: wszystko, czego nie da się jednoznacznie sklasyfikować. Należą do tego także „DB down”, „Timeout”, „Unhandled Exception”.

Trik specyficzny dla Delphi: wiele błędów DB pojawia się jako genericzne wyjątki. Warto w warstwie dostępu do danych specjalnie rozpoznawać znane sytuacje i przekładać je na EApiError. Ważne: nie przenosić fragmentów SQL ani nazw wewnętrznych tabel/kolumn do komunikatu dla klienta. Te szczegóły należą do logu, nie do odpowiedzi.

Sposób debugowania: odtwarzalne błędy dzięki „Contract Snapshot”

Nietypowe, ale w eksploatacji niezwykle pomocne: zapisz przy błędach (lub celowo dla określonych Correlation-IDs) „snapshot” z nagłówków żądania + treści żądania do pliku debug-spool. To nie jest stałe logowanie (ochrona danych/objętość), lecz kontrolowane narzędzie do odtwarzania trudnych do reprodukcji przypadków blisko produkcji.

Ważne: snapshot nigdy nie powinien bez filtrowania zapisywać nagłówków autoryzacji, tokenów ani danych osobowych. W praktyce oznacza to: redakcję (maskowanie) i włączanie tylko przez feature-flag lub whitelistę (np. tylko dla określonych Correlation-IDs, w krótkich oknach czasowych).

Czyste wdrożenie w praktyce: maskowanie zamiast pomijania

W prawdziwych integracjach „krytyczne” pola często są tymi, które potrzebne są do debugowania (np. identyfikatory). Zamiast ogólnego pomijania lepsze jest maskowanie: częściowa zamiana tokenów, pozostawienie w adresie e-mail tylko domeny, IBAN tylko z ostatnimi cyframi. Dzięki temu przypadek pozostaje odtwarzalny, bez rozprzestrzeniania niepotrzebnych danych w systemie plików. Dodatkowo snapshot powinien być wyraźnie oznaczony jako artefakt debugowy i mieć określony okres przechowywania.

Bezpieczeństwo i eksploatacja: przekazywanie nagłówków, łańcuchy proxy i limity czasu

Interfejs REST rzadko kończy się bezpośrednio przy kliencie. Typowe są łańcuchy z reverse proxy, TLS-termination, WAF lub API-Gateway. Stąd wynikają praktyczne uwagi:

  • Zdalne IP: Nie polegaj ślepo na X-Forwarded-For. Przyjmuj je tylko od zaufanych proxy, w przeciwnym razie użyj bezpośredniego adresu gniazda. W podręcznikach operacyjnych powinno być określone, które hopy są „trusted”.
  • Limity czasu: Jeśli proxy ma 30 sekund, a Twoje backendy potrzebują 2 minut, wygenerujesz żądania widmo. Ustal limity czasu spójnie wzdłuż całego łańcucha i podejmij decyzję: synchroniczne żądanie czy wzorzec job (202 Accepted + punkt końcowy statusu).
  • Correlation-ID: Umieszczaj Correlation-ID w nagłówkach odpowiedzi, aby administratorzy mogli je powiązać z logami i po stronie klienta. Jeśli gateway używa własnych Request-ID: loguj i odwzorowuj obie ID.
  • Teksty błędów: W środowisku produkcyjnym nie ujawniaj szczegółów wewnętrznych. Szczegóły debugowania tylko kontrolowanie (Stage / Feature-Flag) i w razie wątpliwości tylko w logu.

Kontekst: Dlaczego RemObjects SDK może tutaj mieć przewagę

W ekosystemach Delphi serwery REST-Server są często budowane przy użyciu lżejszych frameworków (np. minimalistyczne routery HTTP). RemObjects SDK ujawnia swoje zalety, gdy już masz lub potrzebujesz architektury wielowarstwowej:

  • Jasne granice usług: Metody serwisowe są jawne, kontrakty można wersjonować.
  • Transporty i serializacja: Możesz używać JSON, ale też innych formatów wiadomości (w zależności od konfiguracji), bez mieszania logiki domenowej z warstwą transportu.
  • Eksploatacja: Opcje hostingu i integracja z istniejącymi Windows- i Linux-Services są możliwe do zaplanowania, łącznie z uporządkowanymi rolloutami.

Przedstawione podejście uzupełnia to o elementy, których często brakuje w praktyce: jednolite obiekty błędów, deterministyczne wersjonowanie i logowanie umożliwiające korelację. W szczególności przy indywidualnym oprogramowaniu korporacyjnym o długim cyklu życia oszczędza to czas przy aktualizacjach i integracji systemów zewnętrznych.

Wniosek: Czy wysiłek się opłaca — i kiedy podejście przestaje być sensowne?

Wartość dodana pojawia się, gdy Twoje REST-API nie tylko „działa”, lecz jest trwałe w eksploatacji: stabilne kontrakty JSON, wersjonowanie bez rozrostu URL, zrozumiałe błędy i debugowanie bez zgadywania. W tych obszarach podejście z kontekstem, Correlation-ID i centralnym mapowaniem wyjątków w RemObjects SDK jest szczególnie silne.

Granice zastosowania: Jeśli masz tylko pojedynczy, krótkotrwały endpoint bez partnerów integracyjnych, wersjonowanie przez Media-Type szybko stanie się przewymiarowaniem. Logowanie snapshotów ma sens tylko, jeśli konsekwentnie wdrożysz maskowanie (redaction) i mechanizmy aktywacji. I: jeśli Twój stos proxy „optymalizuje” lub usuwa nagłówki, najpierw musisz uporządkować infrastrukturę — inaczej będziesz debugować niewłaściwą warstwę.

Jeżeli modernizujesz istniejącą infrastrukturę serwerową Delphi lub musisz czysto zintegrować rozwiązanie procesowe z ERP/DMS/CRM, to właśnie te mechanizmy często decydują o różnicy między „działa w teście” a „działa w produkcji”.

W obszarze merytorycznym ważną rolę odgrywają także Delphi REST-API i REST-serwer oraz Remobjects Sdk Delphi, gdy integracje, przepływy danych i dalszy rozwój muszą współdziałać w sposób uporządkowany.

Omów projekt lub przedsięwzięcie modernizacyjne z Net-Base.

Następny krok

Gdy temat stanie się rzeczywistym projektem, architekturę, stan istniejący i eksploatację należy wcześnie rozpatrywać wspólnie.

Wspieramy nie tylko w pojedynczych zagadnieniach, lecz także wtedy, gdy z fragmentów kodu źródłowego, kwestii związanych z systemami legacy lub koncepcji portalu ma powstać solidny projekt dla przedsiębiorstwa.

  • Stan istniejący, obraz docelowy i ryzyka techniczne są oceniane łącznie.
  • REST, dostęp do danych, portale i Rollout nie są odkładane na później.
  • Wcześnie widzą Państwo, która droga jest ekonomicznie opłacalna i operacyjnie wykonalna.

Udostępnij wpis

Udostępnij ten wpis bezpośrednio

LinkedIn, X, XING, Facebook, WhatsApp i e‑mail są natychmiast dostępne. Dla Instagrama przygotowujemy od razu link i krótki tekst.

E-mail

Instagram otwiera się w nowej karcie. Link i krótki tekst są wcześniej kopiowane do schowka.