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 mit RemObjects SDK“ w praktyce często rozstrzyga się na brzegach

Implementacja REST API mit RemObjects SDK rzadko oceniana jest po „Hello World” — decydują miejsca, w których spotykają się eksploatacja, systemy dziedziczone i integracja: wersjonowanie bez przestojów, spójne zachowanie przy błędach na wszystkich endpointach, powtarzalne debugowanie w łańcuchach proxy oraz zdolność jednoznacznego kojarzenia żądań w sytuacjach awaryjnych.

RemObjects SDK dostarcza wiele infrastruktury: usługi, formaty wiadomości, serializację, hosting (np. jako Windows- und Linux-Services lub za IIS/Reverse Proxy) oraz zdefiniowane miejsca do centralnej obsługi błędów. W dojrzałych środowiskach oprogramowania biznesowego często brakuje jednak konsekwentnie wdrożonego kontraktu: które pola JSON są stabilne? Jak sygnalizujemy błędy? Jak ponownie rozpoznać żądanie, gdy przeszło przez load balancer, TLS-termination i kilka warstw backendu?

Poniższe podejście (w tym fragmenty kodu z Delphi) przedstawia solidną linię dla RemObjects SDK: wersjonowanie kontraktów JSON, wymuszenie Correlation-ID (Request-ID do śledzenia), tłumaczenie wyjątków na statusy HTTP i obiekty błędów JSON oraz nieprzeciwstawianie sobie debugowania i eksploatacji. Dodatkowo omówimy przypadki brzegowe, które w rzeczywistych środowiskach występują regularnie: wielowątkowość po stronie serwera, dostęp do bazy danych przy BDE-Ablösung z natywnym podłączeniem, nagłówki proxy, time-outy i „schmutzige“ Client-Payloads.

Decyzja architektoniczna: wersjonowanie przez Media Type zamiast URL

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

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

RemObjects SDK nie jest „REST-puristisch“, lecz pragmatycznym frameworkiem usług. Właśnie dlatego wariant z typem mediów się opłaca: można utrzymać stabilne endpointy i jednocześnie rozwijać kontrakty. Ważne jest, aby wersję zawsze odczytywać, podejmować decyzję centralnie w jednym miejscu i przenosić wynik do kontekstu serwisu.

Kiedy wariant z Accept-Headerem zawodzi?

W praktyce istnieją trzy typowe punkty awarii, które warto wcześniej zaadresować:

  • Proxy-Policies: Niektóre reguły Reverse Proxy/WAF normalizują lub filtrują nagłówek Accept. Wtedy API cicho wraca do wartości domyślnej. Rozwiązanie: explicite sprawdzić reguły proxy, w razie potrzeby przejść na X-Api-Version jako obejście.
  • Client-Libraries: Niektóre biblioteki HTTP ustawiają własne Accept-Header i nadpisują wartości. Rozwiązanie: wspierać wersję kontraktu także jako opcjonalny parametr zapytania (tylko jako fallback) lub tolerancyjnie parsować Accept-Header po stronie serwera.
  • Buforowanie: 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: kontekst żądania, Correlation-ID, wersja i mapowanie błędów

Kod został świadomie zorganizowany tak, aby dało się go zintegrować z istniejącymi projektami serwera RemObjects: niewielka warstwa kontekstowa, parser wersji API (z nagłówka Accept), mechanizm Correlation-ID oraz centralne mapowanie wyjątków. Pojęcia:

  • Correlation-ID: Unikalny identyfikator dla każdego żądania, który pojawia się w odpowiedzi i jest odnotowywany w logach.
  • Exception-Mapping: Tłumaczenie wewnętrznych Delphi-wyjątków na stabilne, przez klienta przetwarzalne obiekty błędu (w tym status HTTP).
  • Contract-Version: Wersja kontraktu JSON, która określa zachowanie i pola.
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 „irgendwo im Threadlocal”

Fragment celowo rozdziela: TApiContext to minimalny stan, który chcesz przekazywać. W RemObjects SDK wiele działa przez kontekst serwera/kanłu. W heterogenicznych projektach (np. dodatkowe Worker-Threads, DB-Queue, zadania w tle) jawne przekazywanie często jest bardziej odporne niż ukryte Threadlocale, ponieważ uwidacznia współbieżność i przełączanie kontekstu.

Randbedingungen: Wariant z nagłówkiem Accept zakłada, że Twój reverse proxy (nginx, IIS ARR, Traefik) przekaże nagłówek bez zmian. W niektórych środowiskach „niezwykłe” nagłówki Accept są filtrowane lub agregowane.

Stolperfallen: Wersjonowanie przez Accept jest warte tyle, ile Twoje testy. Jeżeli klienci używają bibliotek, które nadpisują Accept, API może nagle wrócić do domyślnej wersji. Dla klientów legacy sensowny jest domyślny fallback, ale musi on być widoczny w monitoringu (np. ostrzeżenie w logu „Version defaulted”).

Varianten: Jeśli wolisz wersjonować przez X-Api-Version: parser jest identyczny, zmienia się tylko źródło — inny nagłówek. Z perspektywy bramek czasem łatwiej to kontrolować.

Integration in RemObjects SDK: Correlation-ID und Exception-Mapping am Service-Einstieg

Prawdziwy efekt osiąga się, gdy mechanikę stosujesz konsekwentnie na brzegach 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- und Linux-Services) konkretne punkty haków będą się różnić; zasada pozostaje ta sama: zbudować kontekst, wywołać logikę biznesową, centralnie mapować wyjątki.

W projektach RemObjects często pracuje się bezpośrednio w obrębie każdej metody serwisowej. Początkowo to skaluje, ale w eksploatacji zawodzi: każda metoda buduje logowanie i obsługę błędów inaczej. Czystym rozgraniczeniem jest bazowa klasa serwisu lub dispatcher, który standaryzuje.

Praktischer Ablauf (bewusst kurz und implementierungsnah)

  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, adres IP klienta, rozpocząć pomiar czasu.
  4. Wykonać logikę biznesową; dostęp do DB opakować możliwie transakcyjnie.
  5. Przechwycić wyjątek: określić status HTTP, wygenerować obiekt błędu w JSON, ustawić w nagłówku odpowiedzi X-Correlation-ID.
  6. Zalogować koniec żądania: status, czas trwania, ewentualnie kod błędu.

Threading im Server: Warum Correlation-ID ohne Kontext-Disziplin wertlos wird

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

Pragmatyczna reguła: wszystko, co nie pozostaje ściśle w wątku żądania, otrzymuje kontekst jawnie przekazany. Nawet jeśli wygląda to na dłuższe listy parametrów, to się opłaca. Alternatywnie można pracować z jasno zdefiniowanym obiektem kontekstu, który świadomie przekazywany jest do workerów (zamiast zmiennych globalnych 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 bezpieczne do współdzielenia między wątkami. Connection-pool lub jedno połączenie na wątek jest często sensowniejsze niż „globalne połączenie”.
  • Granice transakcji: Jeśli w ramach jednego żądania masz kilka kroków, które do siebie należą, transakcja musi pozostać w tej samej jednostce logicznej. Praca asynchroniczna nie może „przypadkowo” kontynuować się w tej samej transakcji.
  • Anulowanie: Jeśli klient przerywa (przekroczenie czasu proxy, zamknięcie przeglądarki), serwer często kontynuuje pracę. Należy świadomie rozważyć, czy praca w tle ma wtedy jeszcze sens.

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

W projektach integracyjnych czyste mapowanie błędów to więcej niż kosmetyka. Decyduje, czy partner (ERP-Connector, ETL-Job, portal klienta) potrafi 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. Ostrożność w kwestii bezpieczeństwa: nie zawsze ujawniać, czy coś istnieje.
  • 409 Conflict: Konflikt merytoryczny (np. konflikt wersji, „status nie pozwala na tę akcję”, naruszenie unikalnego klucza, jeśli ma znaczenie merytoryczne).
  • 422 Unprocessable Content: Gdy składnia jest poprawna, ale walidacja merytoryczna nie przechodzi (nie każdy zespół używa 422, ale często jest to jaśniejsze niż 400).
  • 500: Wszystko, czego nie potrafisz jednoznacznie sklasyfikować. Obejmuje to także „DB down”, „Timeout”, „Unhandled Exception”.

Delphi-specyficzna sztuczka: Wiele błędów bazodanowych pojawia się jako ogólne wyjątki. Warto na warstwie dostępu do danych celowo rozpoznawać znane sytuacje i przekładać je na EApiError. Ważne: nie przenosić fragmentów SQL ani wewnętrznych nazw tabel/kolumn do komunikatu dla klienta. Te szczegóły należą do logu, nie do odpowiedzi.

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

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

Ważne: Snapshot nigdy nie powinien bez filtrowania zapisywać nagłówków uwierzytelniających, tokenów ani danych osobowych. W praktyce oznacza to: Redaction (maskowanie) i aktywacja tylko przez feature-flag lub whitelistę (np. tylko dla określonych Correlation-IDs, krótkie okna czasowe).

Czysta implementacja w praktyce: maskowanie zamiast pomijania

W prawdziwych integracjach właśnie „krytyczne” pola często są tymi, których potrzebujesz do debugowania (np. identyfikatory). Zamiast ogólnego pomijania lepsze jest maskowanie: częściowe zastępowanie tokenów, pozostawienie w adresie e-mail tylko domeny, IBAN tylko ostatnie cyfry. Dzięki temu przypadek pozostaje odtwarzalny, bez rozprzestrzeniania zbędnych danych w systemie plików. Dodatkowo snapshot powinien być wyraźnie oznaczony jako artefakt debugowy i mieć zdefiniowany okres przechowywania.

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

API REST rzadko kończy się bezpośrednio przy kliencie. Typowe są łańcuchy składające się z reverse proxy, TLS-termination, WAF lub API-Gateway. Z tego wynikają praktyczne wskazania:

  • Remote IP: Nie ufaj ślepo nagłówkowi X-Forwarded-For. Przyjmuj go tylko od zaufanych proxy, w przeciwnym razie użyj bezpośredniego adresu IP gniazda. W dokumentacji operacyjnej powinno być określone, które hopy są „trusted”.
  • Timeouts: Jeśli proxy ma limit 30 sekund, a Twoje backendy potrzebują 2 minut, wygenerujesz tzw. ghost-requests. Ustalaj limity czasu spójnie wzdłuż całego łańcucha i podejmij decyzję: zapytanie synchroniczne czy wzorzec job (202 Accepted + endpoint statusu).
  • Correlation-ID: Umieść Correlation-ID w nagłówkach odpowiedzi, aby administratorzy mogli powiązać ją z logami i po stronie klienta. Jeśli gateway używa własnych identyfikatorów żądań: loguj obie ID i odwzoruj ich relację.
  • Błędy i treść komunikatów: W środowisku produkcyjnym nie ujawniać wewnętrznych szczegółów. Szczegóły debugowania tylko kontrolowane (stage/feature-flag) i w razie wątpliwości wyłącznie w logu.

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

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

  • Wyraźne granice usług: Metody serwisowe są jawne, kontrakty można wersjonować.
  • Transporty i serializacja: Możesz używać JSON, ale także innych formatów wiadomości (w zależności od konfiguracji), bez mieszania logiki domenowej z infrastrukturą.
  • Eksploatacja: Opcje hostingu i integracja z istniejącymi Windows- i Linux-Services są przewidywalne, włącznie z uporządkowanymi wdrożeniami.

Przedstawione podejście uzupełnia to o elementy, których w codziennej eksploatacji często brakuje: zunifikowane obiekty błędów, deterministyczne wersjonowanie i korelowalne logowanie. W szczególności w przypadku indywidualnego oprogramowania firmowego 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 mieć sens?

Wartość dodana pojawia się, gdy Twoje API REST nie tylko „działa”, lecz jest trwałe w eksploatacji: stabilne kontrakty JSON, wersjonowanie bez chaotycznego mnożenia URL, przejrzyste błędy i debugowanie bez zgadywania. To właśnie tam 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 oparte na Media-Type szybko będzie wyglądać jak nadmierne przeinżynierowanie. Logowanie snapshotów ma sens tylko wtedy, gdy zdyscyplinowanie wdrożysz mechanizmy redakcji i 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śli modernizujesz istniejącą infrastrukturę serwerową Delphi lub musisz starannie zintegrować rozwiązanie programowe blisko procesu z ERP/DMS/CRM, to właśnie te mechanizmy często decydują o różnicy między „działa w testach” a „działa w produkcji”.

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

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

Następny krok

Jeżeli temat stanie się rzeczywistym projektem, architektura, stan istniejący i eksploatacja powinny być rozpatrywane razem na wczesnym etapie.

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 będą przesuwane na później.
  • Wcześnie widzą Państwo, która droga jest ekonomicznie 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 bezpośrednio link i krótki tekst.

E-mail

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