Net-Base списание

09.06.2026

REST API со RemObjects SDK: конзистентно версионирање и дебагирање на JSON крајни точки (Delphi исечок од изворниот код)

Како со RemObjects SDK во Delphi да изградите REST API која во работа нема да се распадне: стабилни JSON-контракти, верзионирање без неконтролирана експанзија на URL-адресите, Correlation-ID низ сите слоеви, централизирано мапирање на грешки, Snapshot-логирање за тешки случаи при дебагирање и практични насоки...

09.06.2026

Од тема во магазинот до проектна пракса

Соодветни страници за услуги и технички информации поврзани со објавата

Зошто „REST API со RemObjects SDK“ во пракса често одлучува на периферијата

Една REST API со RemObjects SDK ретко зависи од „Hello World“-сервисот, туку од точките каде што експлоатацијата, наследниот софтвер и интеграцијата се судираат: верзионирање без застој, конзистентно однесување при грешки на сите крајни точки, репродуцибилно дебагирање при прокси-синџири и способноста јасно да се корелираат барањата во случај на проблем.

RemObjects SDK обезбедува многу инфраструктура за тоа: Services, Message-Formate, Serialisierung, Hosting (z. B. als Windows- und Linux-Services oder hinter IIS/Reverse Proxy) und definierte Stellen, um Fehler zentral zu behandeln. Но, во зрели бизнис-софтверски пејзажи често недостасува еден консеквентно применет договор: Кои JSON-полиња се стабилни? Како сигнализираме грешки? Как ќе го препознаеме барањето ако поминало низ Load Balancer, TLS-Termination и повеќе бекенд-слоеви?

Следниот пристап (вкл. Delphi-кодни исечоци) прикажува робусна линија за RemObjects SDK: Верзионирање на JSON-договори, Correlation-ID (Request-ID за следење) да се принуди, претворање на Exceptions во HTTP-статус и JSON-објекти за грешки и при тоа да не се става дебагирањето против експлоатацијата. Дополнително гледаме и на работни случаи што редовно се појавуваат во реални околини: мултитрединг на серверот, пристапи до база со BDE-замена со нативна поврзаност, Proxy-хедери, Timeouts и „валкани“ клиентски payload-и.

Архитектонска одлука: Верзионирање преку медија-тип наместо URL

Многу API верзионираат преку патеки како /v1/. Тоа е прагматично, но во долгорочни интеграции (на пр. ERP/DMS/CRM-врски) тоа често води до дуплирање на URL-ови, дупли рути, дупли тестови и „Која верзија всушност ја користиме?“ во оперативните прирачници.

Алтернатива е верзионирање преку Media Type (Content Negotiation). Клиентот испраќа на пр. Accept: application/vnd.company.order+json;v=2. Серверот детерминистички ја чита верзијата и го прилагодува однесувањето на Contract/DTO. Тоа функционира во прокси- и кеш-синџири ако хедерите се пренесуваат чисто. За администраторите е исто така лесно за проверка: едно барање може да се репродуцира преку Curl/Postman без разлика во URL-ите.

RemObjects SDK не е „REST-пуристички“, туку прагматичен сервис-фрејмворк. Точно поради тоа вреди варијантата со медија-тип: можете да ги задржите стабилните крајни точки и сепак да ги развивате договорите. Важно е дека верзијата треба секогаш да се извади, да се одлучи централизирано и резултатот да се преземе во вашиот сервис-конекст.

Кога пропаѓа варијантата со Accept-хедер?

Во пракса има три типични точки на неуспех кои треба да се адресираат однапред:

  • Proxy-Политики: Некои Reverse Proxies/WAF-правила го нормализираат или филтрираат Accept-хедерот. Тогаш вашето API тивко паѓа на подразбирана вредност. Решение: експлицитно проверете ги правилата на прокси, евентуално преминете на X-Api-Version како алтернатива.
  • Клиентски библиотеки: Некои HTTP-клиенти поставуваат сопствени Accept-хедери и ги презапишуваат вредностите. Решение: поддржете ја верзијата на договорот и како опционален query-параметар (само како фалбек), или парсирајте го Accept-хедерот на серверот толерантно.
  • Caching: Ако се користи Response-Caching, кешот мора да варира според Accept (Vary: Accept), инаку ќе испорачува верзија 1 на клиенти кои очекуваат верзија 2. Решение: намерно поставете Vary, или деактивирајте кеширањето на ниво на API.
  • Исечок од изворен код: Request-Context, Correlation-ID, Version и Error-Mapping

    Кодот е свесно структуриран така што може да се интегрира во постојни RemObjects-серверски проекти: еден мал Context-слој, парсер за API-верзијата (од Accept), механизам за Correlation-ID и централно Exception-Mapping. Термини:

    • Correlation-ID: Единствена ID за секое барање (Request), која се враќа во Response и се реферира во логовите.
    • Exception-Mapping: Превод на внатрешни Delphi-Exceptions во стабилни, од клиентот обработливи објекти на грешки (вкл. HTTP-статус).
    • Contract-Version: Верзија на JSON-договорот која го контролира однесувањето и полињата.
    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.

    Цел: Стабилен контекст на барањето наместо „некаде во Threadlocal“

    Овој исечок намерно прави разделба: TApiContext е минималната состојба што сакате да ја пренесете. Во RemObjects SDK многу работи поминуваат преку сервер-/канален контекст. Во хетерогени проекти (на пр. дополнителни worker-нишки, DB-queue, заднински работи) експлицитното пренесување често е покомпактно и поотпорно од имплицитните Threadlocal-ови, затоа што со тоа ги правите паралелизмот и смените на контекст повидливи.

    Услови: Варијантата со Accept-Header претпоставува дека вашиот Reverse Proxy (nginx, IIS ARR, Traefik) го пренесува header-от непроменет. Во некои околини „невообичаени“ Accept-Header-и се филтрираат или се комбинираат.

    Потенцијални погрешки: Верзионирањето преку Accept е толку добро колку што се вашите тестови. Ако клиенти користат библиотеки кои го презапишуваат Accept, API-то може изненадно да се врати на подразбирано. За legacy-клиенти подразбираниот fallback е разумен, но тој мора да биде видлив во мониторингот (на пр. лог-предупредување „Version defaulted“).

    Варијанти: Ако сакате да правите верзионирање преку X-Api-Version: парсерот е идентичен, само изворот е друг header. Од гледна точка на gateways тоа понекогаш е полесно за контрола.

    Интеграција во RemObjects SDK: Correlation-ID и Exception-Mapping при влезот на сервисот

    Суштинскиот ефект се постигнува ако механиката ја примените конзистентно на периферијата на вашиот сервер: еднаш при влезот на Request да читате од header-ите, и еднаш при излезот кај Exceptions да ги транслирате во стабилни Responses. Во зависност од хостингот (на пр. RO-HTTP-Server, IIS-Hosting, самостојно опериран Windows-/Windows- и Linux-Services) конкретните Hook-поинти се различни; принципот останува исти: градете Context, повикајте бизнис-логиката, централизирајте мапирањето на Exceptions.

    Во RemObjects-проекти често се работи директно по сервис-метод. Тоа на почетокот добро скалира, но при оперативна работа се крши: секоја метода гради логирање и обработка на грешки на различен начин. Чист сеч е една Service-Basis или еден Dispatcher што стандардизира.

    Практична постапка (свесно кратко и близу до имплементацијата)

    1. Читајте Correlation-ID од Request-Header X-Correlation-ID; ако недостасува, генерирајте ја на серверска страна (на пр. GUID).
    2. Читајте Contract-Version од Accept (или од X-Api-Version).
    3. Логирајте почеток на Request: метода, патека, Correlation-ID, Remote IP, започнете мерење на времетраење.
    4. Извршете бизнис-логиката; DB-пристапите обвиткајте ги колку што е можно транзакциски.
    5. Фатете Exception: одредете HTTP-статус, создадете JSON-објект за грешка, сетирајте го Response-Header X-Correlation-ID.
    6. Логирајте крај на Request: статус, времетраење, евентуално код на грешка.

    Трединг на серверот: Зошто Correlation-ID без дисциплина на контекстот станува бескорисна

    Чест Delphi-краен случај: сервис-методата предизвикува асинхрона работа (на пр. генерирање на извештаи, импорт, push во едно DMS). Тогаш оригиналната Request-нишка повеќе не е таа што подоцна ќе пишува лог-линиите. Ако Correlation-ID е познат само „на почетокот“, следливоста се распаѓа.

    Прагматично правило: Сè што не останува строго во Request-нишката, добива експлицитно предаден Context. Иако тоа изгледа како подолги листи со параметри, тоа се исплати. Алтернативно, може да се работи со јасно дефиниран објект на контекст што намерно се предава на worker-ите (вместо глобални променливи или сокриени singleton-и).

    Типични точки на кршење во RemObjects-/Delphi-сервери:

    • DB-врски по нишка: BDE-Ablosung mit nativer Anbindung-Verbindungen не можат автоматски да се делат безбедно помеѓу нишки. Connection-Pool или по една врска на нишка често се попрактични од „eine globale Connection“.
    • Граници на трансакции: Ако во рамки на еден Request имате повеќе чекори кои припаѓаат заедно, трансакцијата мора да остане во истата логичка единица. Асинхрона работа не смее „aus Versehen“ да продолжи да се извршува во истата трансакција.
    • Откажување: Кога клиентот прекинува (Proxy timeout, Browser closed), серверот често продолжува да работи. Прецизно проценете дали тогаш има смисла да се врши позадинска работа.

    Пристап до податоци и кодови на грешки: 409 не е „исто како 500“

    Во интеграциски проекти, чисто мапирање на грешки е повеќе од козметика. Тоа ја одлучува дали спротивната страна (ERP-Connector, ETL-Job, портал за клиенти) може правилно да реагира. Неколку практични насоки кои се покажале во Delphi/RemObjects-окружувања:

    • 400 Bad Request: Валидација, недостасни/невалидни параметри, JSON не може да се парсира. Важно: Одговорот треба да остане стабилен, дури и ако телото е оштетено.
    • 401/403: Разликувајте автентикација и авторизација. 401 значи „нема/невалидна идентификација“, 403 „идентификацијата е во ред, но забрането е“.
    • 404: Ресурсот не постои. Внимание за безбедност: Не секогаш откривајте дали нешто постои.
    • 409 Conflict: Функционален/бизнис конфликт (на пр. конфликт на верзии, „статусот не дозволува оваа акција“, прекршување на уникатен клуч ако тоа е релевантно од бизнис аспект).
    • 422 Unprocessable Content: Кога синтаксата е во ред, но не успева функционалната валидација (не секој тим користи 422, но често е појасно од 400).
    • 500: Сè што не можете јасно да класифицирате. Тоа вклучува и „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-специфичен трик: Многу DB-грешки излегуваат како генераички Exceptions. Исплати се во слојот за пристап до податоци целно да се проверат познати ситуации и да се претворат во EApiError. Важно: Немојте да пренесувате SQL-фрагменти или внатрешни имиња на табели/колони во порака кон клиентот. Тие детали припаѓаат во лог, не во одговорот.

    Трик за дебаг: репродуцирачки грешки со „Contract Snapshot“

    Необично, но во оперативна работа исклучително корисно: При грешки (или селективно за одредени Correlation-IDs) зачувајте еден „Snapshot“ од Request-Headerи + Request-Body во датотека за Debug-Spool. Ова не е постојано логирање (заштита на податоци/волумен), туку контролирано средство за повторно воспоставување тешко репродуцирачки случаи во близина на продукцијата.

    Важно: Snapshot никогаш не смее да перзистира нефилтрирани Auth-Header, Tokens или лично-поврзани податоци. Во пракса тоа значи: Redaction (маскирање) и активирање само преку Feature-Flag или Whitelist (на пр., само за одредени Correlation-IDs, за кратки временски прозорци).

    Чиста имплементација во пракса: Маскирање наместо отстранување

    Во вистински интеграции токму „критичните“ полиња често се оние што ви требаат за дебаг (на пр., идентификатори). Наместо општо отстранување, подобро е маскирање: делумно заменете Tokens, задржете само доменот од е-поштата, прикажете IBAN само со последните цифри. Така случајот останува репродуцирачки, без да се шири непотребни податоци во датотечниот систем. Дополнително, Snapshot-от треба јасно да биде означен како debug-артефакт и да има дефиниран рок на чување.

    Безбедност и оперативност: пренесување на заглавија, ланци на прокси и таймаути

    Една REST API ретко завршува директно кај клиентот. Типично се ланци од reverse proxy, TLS-terminacija, WAF или API-Gateway. Од тоа произлегуваат практични поенти:

    • Далечна IP-адреса: Не се потпирајте слепо на X-Forwarded-For. Прифаќајте го само од доверливи проксита и во спротивно користете ја директната сокет-IP. Во оперативните прирачници треба да е наведено кои хопови се „trusted“.
    • Таймаути: Ако проксито има 30 секунди, а вашето бекенд бара 2 минути, ќе генерирате Ghost-Requests. Поставете ги таймаутите конзистентно низ целата ланец и одлучете: синхронско барање или Job-Pattern (202 Accepted + статус-ендпоинт).
    • Correlation-ID: Поставете Correlation-ID во response-заглавија, за администраторите да може да ја поврзат од логови и од клиентската страна. Ако gateway-то користи свои Request-IDs: логирајте и прикажувајте и двете ID.
    • Текстови за грешки: Во продукциска експлоатација нема внатрешни детали. Debug-детали само контролирано (stage/feature-flag) и по потреба само во лог.

    Евалуација: Зошто RemObjects SDK овде може да има предност

    Во Delphi-екосистеми REST-сервери често се прават со полесни фрејмворци (на пр. минималистички HTTP-роутери). RemObjects SDK ја покажува својата предност кога веќе имате или ви треба повеќеслојна архитектура:

    • Јасни граници на сервисите: Методите на сервисите се експлицитни, контрактите можат да се верзионираат.
    • Транспорт и серијализација: Можете да зборувате JSON, но и други формати на пораки (зависно од подесувањето), без да ја измешате бизнис-логиката.
    • Оперативност: Опции за хостинг и интеграција во постоечки Windows- и Linux-сервиси се планираат, вклучувајќи прецизни распоредувања.

    Прикажаниот пристап го пополнува делот што често недостига во секојдневната работа: унифицирани објекти за грешки, детерминистичка верзионирање и корелирачко логирање. Особено кај индивидуална корпоративна софтверска опрема со долг животен циклус, тоа ви штеди време при надградувања и при интеграција на екстерни системи.

    Заклучок: Дали се исплатува напорот — и кога пристапот губи смисла?

    Додадената вредност се јавува кога вашата REST-интерфејс не само што „работи“, туку е долгорочно управлива: стабилни JSON-договори, верзионирање без дивеење на URL-ови, разбирливи грешки и дебагирање без шпекулации. Токму тука пристапот со контекст, Correlation-ID и централно мапирање на исклучоци во RemObjects SDK е силен.

    Граници на примена: Ако имате само еден единечен, краткотраен ендпоинт без партнери за интеграција, Media-Type-верзионирањето брзо изгледа како overengineering. Исто така, snapshot-логирањето има смисла само ако дисциплинирано имплементирате редакција (Redaction) и активација. И: ако вашиот proxy-stack „оптимизира“ или отстранува заглавија, прво мора да ја средите инфраструктурата, инаку ќе дијагностицирате погрешен слој.

    Ако модернизирате постоечка Delphi-серверска околина или треба да интегрирате процесно-блиску софтверско решение чисто во ERP/DMS/CRM, токму овие механизми често се разликата помеѓу „работи во тест“ и „работи во производство“.

    Во стручниот контекст, Delphi REST-API и REST-сервер и Remobjects Sdk Delphi исто така играат важна улога кога интеграциите, протоките на податоци и понатамошниот развој треба да функционираат тесно поврзано.

    Разговарајте за проект или за план за модернизација со Net-Base.

    Следен чекор

    Кога темата ќе прерасне во реален проект, архитектурата, постоечката средина и експлоатацијата треба рано да се разгледаат заедно.

    Не поддржуваме само при поединечни прашања, туку и кога од исечоци од изворен код, legacy-теми или идеи за портали треба да прерасне во робустен корпоративен проект.

    • Постоечката состојба, целната слика и техничките ризици се проценуваат заедно.
    • REST, пристапот до податоци, порталите и Rollout не се одложуваат како подоцнежни последици.
    • Уште рано идентификувате кој пат е економски и оперативно одржлив.

    Сподели објава

    Споделете го овој пост директно.

    LinkedIn, X, XING, Facebook, WhatsApp и е-пошта се веднаш достапни. За Instagram директно подготвуваме линк и краток текст.

    Е-пошта

    Instagram се отвора во нов таб. Линкот и краткиот текст претходно се копираат во меѓуспремникот.