Net-Base Lehti

09.06.2026

REST API RemObjects SDK:n kanssa: JSON-päätepisteiden selkeä versiointi ja virheenkorjaus (Delphi Source-koodikatkelmat)

Kuinka rakentaa RemObjects SDK:lla Delphi-ympäristössä REST API, joka ei hajoa tuotannossa: vakaat JSON-sopimukset, versiointi ilman URL-osoitteiden hallitsematonta kasvua, Correlation-ID kaikkien kerrosten läpi, keskitetty virheiden kartoitus, Snapshot-lokinointi vaikeisiin debug-tapauksiin sekä käytännönläheisiä ohjeita...

09.06.2026

Lehden aiheesta projektikäytäntöön

Artikkeliin liittyvät palvelu- ja tekniikkasivut

Miksi „REST API RemObjects SDK:n kanssa” ratkaisee käytännössä usein reunatapauksissa

Yksi REST API RemObjects SDK:n kanssa ei yleensä nouse tai kaadu „Hello World” -palvelun perusteella, vaan niissä kohdissa, joissa operointi, legacy ja integraatio törmäävät: versiointi ilman käyttökatkoja, yhtenäinen virhekäyttäytyminen kaikissa päätepisteissä, toistettava debuggaus proxyketjuissa ja kyky yksiselitteisesti korreloida pyynnöt ongelmatilanteissa.

RemObjects SDK tarjoaa tähän paljon infrastruktuuria: palvelut, viestiformaatit, serialisointi, hosting (esim. Windows- und Linux-Services tai IIS/Reverse Proxyn takana) ja määritellyt paikat virheiden keskitettyyn käsittelyyn. Kasvaneissa liikeohjelmisto-ympäristöissä kuitenkin usein puuttuu johdonmukaisesti läpiviety sopimus: mitkä JSON-kentät ovat vakaita? Miten ilmoitamme virheistä? Miten tunnistamme pyynnön uudelleen, kun se on kulkenut kuormantasaajan, TLS-terminoinnin ja useiden backend-kerrosten läpi?

Seuraava lähestymistapa (sisältäen Delphi-snipselin) näyttää jämäkän linjan RemObjects SDK:lle: JSON-sopimusten versiointi, Correlation-ID (Request-ID jäljittämistä varten) pakottaminen, poikkeusten kääntäminen HTTP-statukseen ja JSON-virheobjekteiksi ja samalla debuggausta ja operointia ei aseteta vastakkain. Lisäksi käsittelemme reunatapauksia, jotka esiintyvät todellisissa ympäristöissä säännöllisesti: serverin threading, tietokantakutsut ja BDE-korvaus natiiviliitännällä, proxy-headerit, aikakatkaisut ja „likaiset” client-payloadit.

Arkkitehtuuripäätös: versiointi mediatyypin kautta URLin sijaan

Monet API:t versioivat polkujen kuten /v1/ kautta. Se on pragmaattista, mutta pitkään jatkuvissa integraatioissa (esim. ERP/DMS/CRM-liitännät) se johtaa usein URL-duplikaatioihin, kahdennettuihin reitteihin, tuplatesteihin ja „Mitä versiota me oikeastaan käytämme?” -ongelmaan käyttöohjeissa.

Vaihtoehto on versiointi mediatyypin kautta (content negotiation). Asiakas lähettää esimerkiksi Accept: application/vnd.company.order+json;v=2. Palvelin lukee version deterministisesti ja sovittaa contract/DTO-käytöksen sen mukaan. Tämä toimii proxyn ja välimuistoketjujen kanssa, kun headerit välittyvät oikein. Adminien kannalta se on myös hyvin todennettavissa: pyyntö voidaan toistaa curlilla/Postmanilla ilman, että URLit eroavat.

RemObjects SDK ei ole „REST-puristinen”, vaan pragmaattinen palvelukehys. Juuri siksi mediatyypin vaihtoehto kannattaa: voit säilyttää vakaat päätepisteet ja kehittää sopimuksia eteenpäin. Tärkeää on, että versio aina luetaan, päätös tehdään keskitetysti yhdessä paikassa ja tulos siirretään palvelukontekstiin.

Milloin Accept-Header-vaihtoehto pettää?

Käytännössä on kolme tyypillistä katkokohtaa, jotka tulisi käsitellä etukäteen:

  • Proxy-politiikat: Jotkut reverse-proxyt/WAF-säännöt normalisoivat tai suodattavat Accept-otsikon. Tällöin API laskee hiljaisesti takaisin oletukseen. Ratkaisu: tarkista proxy-säännöt erikseen, tarvittaessa turvaudu X-Api-Version -otsikkoon.
  • Asiakaskirjastot: Jotkut HTTP-asiakkaat asettavat omia Accept-otsikoitaan ja ylikirjoittavat arvot. Ratkaisu: tue sopimusversiona myös valinnainen query-parametri (vain fallbackina), tai jäsennä Accept-otsikko palvelinpuolella tolerantisti.
  • Välimuisti: Jos response-caching on käytössä, välimuistin on vaihdeltava Accept-otsakon mukaan (Vary: Accept), muuten se palauttaa version 1 version 2 -asiakkaille. Ratkaisu: aseta Vary tarkoituksellisesti tai poista välimuisti API-tasolla käytöstä.
  • Lähdekoodipätkä: Request-Context, Correlation-ID, versio ja Error-Mapping

    Koodi on tarkoituksellisesti jäsennelty niin, että se on helppo integroida olemassa oleviin RemObjects-serveriprojekteihin: pieni kontekstikerros, API-version parseri (Accept-otsakkeesta), Correlation-ID-mekanismi ja keskitetty Exception-Mapping. Termit:

    • Correlation-ID: Yksilöllinen ID jokaista pyyntöä varten; se palautetaan vastauksessa ja siihen viitataan lokeissa.
    • Exception-Mapping: Sisäisten Delphi-poikkeusten kääntäminen vakaiksi, asiakasohjelman käsiteltäviksi virheobjekteiksi (mukaan lukien HTTP-status).
    • Contract-Version: JSON-sopimuksen versio, joka ohjaa käyttäytymistä ja kenttiä.
    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.

    Tarkoitus: Vakaa pyyntökonteksti sen sijaan, että se olisi „jossain threadlocalissa“

    Koodikatkelma erottaa tietoisesti: TApiContext on minimilää tila, jonka haluat välittää. RemObjects SDK:ssa paljon kulkee palvelin-/kanavakontekstin kautta. Heterogeenisissa projekteissa (esim. lisätyöt käynnissä erillisissä workereissa, DB-jonot, taustatyöt) eksplisiittinen välitys on usein robustimpi kuin implisiittiset säikekohtaiset muuttujat, koska se tekee rinnakkaisuuden ja kontekstinvaihdokset näkyvämmiksi.

    Reunaehdot: Accept-header -vaihtoehto edellyttää, että teidän Reverse Proxy (nginx, IIS ARR, Traefik) välittää headerin muuttumattomana. Joissain ympäristöissä „epätavallisia“ Accept-headereja suodatetaan tai yhdistetään.

    Varoitukset: Versiointi Acceptin kautta on yhtä hyvä kuin testinne. Jos asiakkaat käyttävät kirjastoja, jotka ylikirjoittavat Acceptin, API voi yllättäen pudota oletukseen. Legacy-asiakkaille oletuspaluun tarjoaminen on järkevää, mutta sen on oltava näkyvissä monitoroinnissa (esim. loki-varoitus „Version defaulted“).

    Vaihtoehdot: Jos haluatte mieluummin versionhallinnan X-Api-Version-headerin kautta: parseri on identtinen, vain lähde on eri header. Gatewayn näkökulmasta tämä on joskus helpommin hallittavissa.

    Integraatio RemObjects SDK:ssa: Correlation-ID ja poikkeuskartoitus palvelun sisäänmenossa

    Varsinainen vaikutus syntyy, kun mekanismia sovelletaan johdonmukaisesti palvelimen reunalla: kerran pyynnön alussa luetaan headerit, kerran poikkeuksien lopussa käännetään ne vakaaksi vastaukseksi. Hostingista riippuen (esim. RO-HTTP-Server, IIS-hosting, itse ylläpidetty Windows-/Windows- ja Linux-Services) konkreettiset hook-pisteet vaihtelevat; periaate on sama: rakenna Context, kutsu liiketoimintalogiikka, mappi poikkeukset keskitetysti.

    RemObjects-projekteissa tyypillisesti työstetään suoraan per palvelumetodi. Se skaalautuu aluksi hyvin, mutta käytössä se kääntyy helposti ongelmaksi: jokainen metodi rakentaa lokituksen ja virheenkäsittelyn eri tavalla. Selkeä rajapinta on palvelun perusluokka tai Dispatcher, joka standardisoi käytännöt.

    Käytännön kulku (tietoisesti tiivis ja toteutusläheinen)

    1. Lue Correlation-ID pyynnön headerista X-Correlation-ID; jos se puuttuu, luo palvelinpuolella (esim. GUID).
    2. Lue sopimusversio Accept-headerista (tai X-Api-Version-headerista).
    3. Kirjaa pyynnön aloitus lokiin: metodi, polku, Correlation-ID, etä-IP, aloita keston mittaus.
    4. Suorita liiketoimintalogiikka; kapseloi tietokantakutsut mahdollisuuksien mukaan transaktionaalisesti.
    5. Tartu poikkeuksiin: määrää HTTP-tila, luo JSON-virheobjekti, aseta Response-header X-Correlation-ID.
    6. Kirjaa pyynnön lopetus lokiin: status, kesto, tarvittaessa virhekoodi.

    Säikeiden hallinta palvelimella: miksi Correlation-ID ilman kontekstikuria menettää merkityksensä

    Yksi yleinen Delphi-reunatapaus: palvelumetodi laukaisee asynkronista työtä (esim. raportin generointi, tuonti, push DMS:ään). Tällöin alkuperäinen pyyntösäie ei enää välttämättä ole se, joka myöhemmin kirjoittaa lokirivejä. Jos Correlation-ID tunnetaan vain „alussa“, jäljitettävyys hajoaa.

    Pragmaattinen sääntö: kaikki, mikä ei pysy tiukasti pyyntösäikeessä, saa kontekstin välitettynä nimenomaisesti. Vaikka se vaikuttaisi lisäävän parametrilistoja, se kannattaa. Vaihtoehtoisesti voi työskennellä selkeästi määritellyn konteksti-olion kanssa, joka välitetään tietoisesti workereille (globaalien muuttujien tai piilotettujen singletonien sijaan).

    Tyypilliset kääntöpisteet RemObjects-/Delphi-palvelimilla:

    • DB-yhteydet per säie: BDE-Ablosung mit nativer Anbindung-yhteyksiä ei voi automaattisesti jakaa säieturvallisesti. Yhteyspooli tai yksi yhteys per säie on usein järkevämpi vaihtoehto kuin „yksi globaali yhteys“.
    • Transaktiorajat: Jos pyynnön sisällä on useita toisiinsa kuuluvia vaiheita, transaktion on pysyttävä samassa loogisessa yksikössä. Asynkronista työtä ei saa „vahingossa“ jatkaa samassa transaktiossa.
    • Peruutus: Jos asiakas keskeyttää (proxy timeout, selain suljettu), palvelin usein jatkaa suoritusta. Pohdi tietoisesti, onko taustatyöllä tällöin vielä merkitystä.

    Tietojen käyttö ja virhekoodit: 409 ei ole „myös 500“

    Integraatiohankkeissa selkeä virheiden kartoitus on enemmän kuin kosmetiikkaa. Se ratkaisee, voiko vastaava osapuoli (ERP-Connector, ETL-job, asiakasportaali) reagoida oikein. Muutama käytännönläheinen ohjenuora, jotka ovat toimineet Delphi/RemObjects-ympäristöissä:

    • 400 Bad Request: Validointi, puuttuvat/virheelliset parametrit, JSON ei jäsennettävissä. Tärkeää: vastaus tulee pitää ennustettavana, vaikka pyynnön runko olisi rikki.
    • 401/403: Erota autentikointi ja valtuutus. 401 tarkoittaa „ei tunnistautumista/virheellinen identiteetti“, 403 „identiteetti kunnossa, mutta kielletty“.
    • 404: Resurssia ei ole olemassa. Turvallisuus huomioon: ei aina paljasteta, onko jokin olemassa.
    • 409 Conflict: Liiketoiminnallinen konflikti (esim. versiokonflikti, „tila ei salli tätä toimenpidettä“, yksilöllisen avaimen rikkoontuminen, kun sillä on liiketoiminnallinen merkitys).
    • 422 Unprocessable Content: Kun syntaksi on kunnossa mutta liiketoimintavalidaatio epäonnistuu (kaikki tiimit eivät käytä 422:ta, mutta se on usein selkeämpi kuin 400).
    • 500: Kaikki, mitä et osaa järkevästi luokitella. Tähän kuuluu myös „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-spesifinen vinkki: Monet tietokantavirheet nousevat ylös geneerisinä poikkeuksina. Kannattaa datan käyttökerroksessa tunnistaa tunnettuja tilanteita ja muuntaa ne EApiError-muotoon. Tärkeää: älä siirrä SQL-fragmentteja tai sisäisiä taulu-/sarakenimiä asiakkaalle näkyvään viestiin. Nämä tiedot kuuluvat lokiin, eivät vastaukseen.

    Debuggausvinkki: toistettavat virheet „Contract Snapshot“ -menetelmällä

    Epätavanomainen mutta operoinnissa äärimmäisen hyödyllinen ratkaisu: tallenna virhetilanteissa (tai valikoidusti tietyille Correlation-ID:ille) „snapshot“ koostettuna Request-Headereista + Request-Body debug-spool-tiedostoon. Tämä ei ole jatkuvaa lokitusta (tietosuoja/tilavuus), vaan kontrolloitu työkalu vaikeasti toistettavien tapausten selvittämiseen tuotantoympäristöstä käsin.

    Tärkeää: snapshot ei saa koskaan tallentaa suodattamattomia Auth-header‑kenttiä, tokeneita tai henkilötietoja. Käytännössä tämä tarkoittaa redactionia (maskaus) ja aktivointia ainoastaan feature-flagin tai whitelistin kautta (esim. vain tietyille Correlation-ID:ille, lyhyet aikavälit).

    Käytännön toteutus: maskaus poistamisen sijaan

    Aidoissa integraatioissa juuri ne „kriittiset“ kentät ovat usein niitä, joita debugatessa tarvitaan (esim. identifikaattorit). Yleispäätös jättää kentät pois heikentää jäljitettävyyttä; parempi on maskaus: korvaa tokenit osittain, säilytä sähköpostista vain domain, näytä IBAN:ista vain viimeiset numerot. Näin tapaus pysyy toistettavana ilman, että tarpeettomia tietoja levitetään tiedostojärjestelmään. Lisäksi snapshot tulee selkeästi merkitä debug-artefaktiksi ja sille tulee määrittää säilytysaika.

    Turvallisuus ja käyttö: otsakevälitys, proxy-ketjut ja aikakatkaisut

    Yksittäinen REST-API harvoin päättyy suoraan asiakkaaseen. Tyypillisesti käytössä on ketjuja, joissa on käänteinen välityspalvelin, TLS-terminointi, WAF tai API-gateway. Tästä seuraa käytännön huomioita:

    • Etä-IP: Älä luota sokeasti X-Forwarded-For-otsakkeeseen. Hyväksy se vain luotettavista proxyeista, muuten käytä suoraa socketin IP-osoitetta. Käyttöohjeisiin tulee merkitä, mitkä hopit ovat luotettavia.
    • Aikarajat: Jos proxyn aikaraja on 30 sekuntia mutta backendisi tarvitsee 2 minuuttia, syntyy ghost-pyyntöjä. Aseta aikarajat johdonmukaisesti koko ketjulle ja päätä: synkroninen pyyntö vai job-pattern (202 Accepted + status-päätepiste).
    • Correlation-ID: Lisää Correlation-ID vastausotsakkeisiin, jotta ylläpitäjät voivat yhdistää sen lokeista ja asiakaspuolelta. Jos gateway käyttää omia request-ID:itä, kirjaa molemmat ID:t lokiin ja yhdistä ne toisiinsa.
    • Virhetekstit: Tuotantokäytössä älä paljasta sisäisiä tietoja. Debug-tiedot vain kontrolloidusti (esim. stage-ympäristö tai feature-flag) ja tarvittaessa ainoastaan lokeihin.

    Sijoitus: Miksi RemObjects SDK voi olla tässä etuna

    Delphi-ekosysteemeissä rakennetaan usein REST-palvelimia kevyemmillä kehyksillä (esim. minimalistiset HTTP-reitittimet). RemObjects SDK hyödyntää vahvuutensa silloin, kun tarvitsette tai teillä on jo monikerroksinen arkkitehtuuri:

    • Selkeät palvelurajat: Palvelumenetelmät ovat eksplisiittisiä, rajapintasopimukset ovat versioitavissa.
    • Kuljetus ja serialisointi: Voitte käyttää JSONia, mutta myös muita viestimuotoja (asennuksesta riippuen) ilman, että toiminnallinen logiikka sotkeutuu.
    • Käyttö: Isännöintivaihtoehdot ja integraatio olemassa oleviin Windows- ja Linux-palveluihin ovat ennakoitavissa, mukaan lukien siistit rolloutit.

    Näytetty lähestymistapa täydentää tätä niillä osilla, joita arjessa usein puuttuu: yhtenäiset virheobjektit, deterministinen versionointi ja korreloitavissa oleva lokitus. Erityisesti yksilöllisessä yritysohjelmistossa, jolla on pitkä elinkaari, tämä säästää aikaa päivityksissä ja ulkoisten järjestelmien integraatiossa.

    Johtopäätös: Kannattaako vaiva — ja missä kohtaa lähestymistapa ei enää ole perusteltu?

    Lisäarvo syntyy, kun REST-rajapintanne ei vain „toimi“, vaan on pitkäaikaisesti ylläpidettävissä: stabiilit JSON-sopimukset, versionointi ilman URL-hajontaa, jäljitettävät virheet ja debuggaus ilman arvailua. Juuri tässä konteksti, Correlation-ID ja keskitetty poikkeusten kartoitus RemObjects SDK:ssa ovat vahvoja.

    Käyttörajoitukset: Jos teillä on vain yksittäinen, lyhytikäinen päätepiste ilman integraatiokumppaneita, media-tyypin versionointi voi nopeasti tuntua liialliselta. Myös snapshot-lokitus on järkevää vain, jos toteutatte tietojen peittelyn ja aktivoinnin kurinalaisesti. Ja: jos proxy-pinonne optimoi tai poistaa otsakkeita, korjatkaa ensin infrastruktuuri, muuten debuggaat väärää kerrosta.

    Jos modernisoitte olemassa olevan Delphi-palvelinympäristön tai tarvitsette prosessiläheisen ohjelmistoratkaisun siistin integraation ERP/DMS/CRM-järjestelmiin, ovat juuri nämä mekanismit usein ero „toimii testissä“ ja „toimii tuotannossa“ välillä.

    Asiantuntijaympäristössä näyttelevät myös Delphi REST-API ja REST-Server ja Remobjects Sdk Delphi tärkeää roolia, kun integraatioiden, tietovirtojen ja jatkokehityksen on toimittava saumattomasti.

    Keskustele projektista tai modernisointihankkeesta Net-Base kanssa.

    Seuraava vaihe

    Kun aiheesta tulee todellinen projekti, arkkitehtuuri, nykyinen järjestelmäkanta ja käyttö tulisi varhaisessa vaiheessa tarkastella yhdessä.

    Emme tue pelkästään yksittäiskysymyksissä, vaan myös silloin, kun lähdekoodipalasista, legacy-aiheista tai portaali-ideoista halutaan muodostaa luotettava yrityshanke.

    • Nykytila, tavoitetila ja tekniset riskit arvioidaan yhdessä.
    • REST, datan käyttö, portaalit ja käyttöönotto eivät jätetä myöhempien seurausten varaan.
    • Näette ajoissa, mikä ratkaisu on taloudellisesti ja toiminnallisesti kestävä.

    Jaa artikkeli

    Jaa tämä viesti suoraan

    LinkedIn, X, XING, Facebook, WhatsApp ja sähköposti ovat heti käytettävissä. Instagramia varten valmistelemme linkin ja lyhyen tekstin.

    Sähköposti

    Instagram avautuu uuteen välilehteen. Linkki ja lyhyt teksti kopioidaan ensin leikepöydälle.