Lehden aiheesta projektikäytäntöön
Artikkeliin liittyvät palvelu- ja tekniikkasivut
Miksi „REST API mit RemObjects SDK“ käytännössä usein ratkaisee reunaehdoissa
Harvoin kyse on „Hello World“-palvelusta; REST API mit RemObjects SDK mitätöityy tai menestyy useimmiten niissä kohdissa, joissa käytön, legacy-järjestelmien ja integraation vaatimukset törmäävät: versiointi ilman käyttökatkoja, yhtenäinen virhekäyttäytyminen kaikissa päätepisteissä, toistettava vianetsintä proxyketjuissa sekä kyky yksiselitteisesti korreloida pyynnöt ongelmatilanteissa.
RemObjects SDK tarjoaa tähän merkittävän infrastruktuurin: palvelut, viestiformaatit, serialisointi, hosting (esim. Windows- ja Linux-palvelut tai IIS/Reverse Proxyn takana) sekä määritellyt kohdat virheiden keskitettyyn käsittelyyn. Mitä kasvaneissa liiketoimintaohjelmistoympäristöissä kuitenkin usein puuttuu, on johdonmukaisesti toteutettu sopimus: mitkä JSON-kentät ovat stabiileja? Miten signaloimme virheet? Miten tunnistamme pyynnön uudelleen, kun se on kulkenut Load Balancerin, TLS-terminoinnin ja useiden backend-kerrosten läpi?
Seuraava lähestymistapa (mukaan lukien Delphi-koodinpätkä) esittää robustin linjan RemObjects SDK:lle: JSON-sopimusten versiointi, Correlation-ID (pyyntöjen jäljitettävyyden ID) pakolla, poikkeusten kääntäminen HTTP-statukseiksi ja JSON-virheobjekteiksi sekä debuggaus ja tuotanto-ajo huomioituna ilman toistensa vastakkainasettelua. Lisäksi käsittelemme reunaehtoja, jotka todellisissa käyttöönotossa esiintyvät toistuvasti: säikeiden hallinta palvelimella, tietokantakyselyt liittyen BDE-korvaukseen natiiviliitännällä, proxy-otsikot, aikakatkaisut ja „likaiset“ client-payloadit.
Arkkitehtuuripäätös: versiointi mediatyypin kautta URLin sijaan
Monet API:t versioivat polkujen kautta, kuten /v1/. Se on pragmaattista, mutta pidempiaikaisissa integraatioissa (esim. ERP/DMS/CRM-liitännät) siitä seuraa usein URL-duplikaatioita, kaksinkertaisia reittejä, kaksinkertaisia testejä ja kysymys „Mitä versiota oikeastaan käytämme?“ käyttöohjeissa.
Vaihtoehtona on versiointi mediatyypin kautta (Content Negotiation). Asiakas lähettää esimerkiksi Accept: application/vnd.company.order+json;v=2. Palvelin lukee version deterministisesti ja mukauttaa Contract/DTO-käyttäytymisen sen mukaan. Tämä toimii proxyn ja välimuistiketjujen läpi, kun headerit välittyvät oikein. Järjestelmäylläpitäjille se on lisäksi helposti tarkistettavissa: pyyntö voidaan toistaa Curlilla/Postmanilla ilman, että URLit eroavat.
RemObjects SDK ei ole „REST-puristi“, vaan pragmaattinen service-framework. Juuri siksi mediatyyppivaihtoehto kannattaa: voit säilyttää stabiilit päätepisteet ja silti kehittää sopimuksia eteenpäin. Tärkeää on, että arvioitte version aina keskitetysti yhdestä paikasta ja otatte tuloksen mukaan palvelukontekstiinne.
Milloin Accept-Header -vaihtoehto pettää?
Käytännössä on kolme tyypillistä kriittistä kohtaa, jotka kannattaa käsitellä etukäteen:
- Proxy-käytännöt: Jotkin Reverse Proxyt/WAF-säännöt normalisoivat tai suodattavat Accept-otsikon. Tällöin API putoaa hiljaisesti oletusversioon. Ratkaisu: tarkista proxy-säännöt eksplisiittisesti, tarvittaessa turvaudu
X-Api-Version-otsikkoon. - Client-kirjastot: Joissain HTTP-asiakasohjelmistoissa asetetaan omia Accept-otsikoita, jotka voivat ylikirjoittaa arvot. Ratkaisu: tue kontraktiversiota myös valinnaisena kyselyparametrina (vain fallbackina), tai jäsennä Accept-otsikko palvelinpuolella joustavasti.
Accept-otsikon mukaan (Vary: Accept), muuten se toimittaa version 1 version 2 -asiakkaille. Ratkaisu: aseta Vary tietoisesti tai poista välimuistitus API-tasolla käytöstä.Lähdekoodikatkelma: Request-Context, Correlation-ID, versio ja Error-Mapping
Koodi on tarkoituksellisesti jäsennelty siten, että se voidaan integroida olemassa oleviin RemObjects-palvelinprojekteihin: pieni context-kerros, parseri API-version lukemiseksi (otsikosta Accept), Correlation-ID-mekanismi ja keskitetty poikkeusten mappaus. Termit:
- Correlation-ID: Yksilöllinen ID jokaista pyyntöä varten, joka palautuu vastauksessa ja johon viitataan lokitiedoissa.
- Exception-Mapping: Sisäisten Delphi-poikkeusten kääntäminen vakaiksi, asiakkaan käsiteltäviksi virheobjekteiksi (mukaan lukien HTTP-status).
- Contract-Version: JSON-sopimuksen versio, joka ohjaa käyttäytymistä ja kenttiä.
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 request-konteksti sen sijaan, että se olisi „jossain threadlocalissa“
Snippi erottaa tarkoituksella: TApiContext on minimaalinen tila, jonka haluat välittää. RemObjects SDK:ssa paljon kulkee palvelin-/kanavakontekstin kautta. Heterogeenisissa projekteissa (esim. lisäworker-säikeet, DB-jonot, taustatyöt) eksplisiittinen välitys on kuitenkin usein robustimpi kuin implisiittiset threadlocalit, koska siten samanaikaisuus ja kontekstinvaihdot tulevat näkyvämmiksi.
Reunaehdot: Accept-header -variantti edellyttää, että reverse proxy (nginx, IIS ARR, Traefik) välittää otsakkeen muuttumattomana. Joissain ympäristöissä „epätavallisia“ Accept-otsakkeita suodatetaan tai yhdistellään.
Huomioitavaa: Versiointi Acceptin kautta on yhtä hyvä kuin testinne. Jos asiakkaat käyttävät kirjastoja, jotka ylikirjoittavat Accept-otsakkeen, API voi yhtäkkiä pudota oletusarvoon. Legacy-asiakkaille oletus-fallback on järkevä, mutta sen on oltava näkyvissä monitoroinnissa (esim. loki-varoitus „Version defaulted“).
Vaihtoehdot: Jos haluat mieluummin tehdä versioinnin X-Api-Version-otsakkeen kautta: parser on identtinen, vain lähde on eri otsake. Gatewayn näkökulmasta se on joskus helpommin hallittavissa.
Integrointi RemObjects SDK:hen: Correlation-ID ja Exception-mapping palvelun sisäänkäynnissä
Oikea vaikutus syntyy, kun mekanismia sovelletaan johdonmukaisesti palvelimen reunalla: kerran requestin alussa luetaan otsakkeista, kerran poikkeustilanteen lopussa käännetään vakaaksi responsseksi. Hostingista riippuen (esim. RO-HTTP-Server, IIS-hosting, itse ylläpidetty Windows-/Windows- und Linux-Services) konkreettiset hook-pisteet vaihtelevat; periaate on sama: rakenna konteksti, kutsu business-logiikkaa, muunna poikkeukset keskitetysti vakioiduiksi vastauksiksi.
RemObjects-projekteissa tyypillisesti työskennellään suoraan palvelumenetelmätasolla. Se skaalautuu aluksi hyvin, mutta kääntyy käytössä epäsuoraksi: jokainen metodi rakentaa logituksen ja virheenkäsittelyn eri tavalla. Selkeä rajapinta on palvelun perusluokka tai dispatcher, joka standardoi.
Käytännön kulku (tahallisesti tiivis ja toteutusläheinen)
- Correlation-ID luetaan request-otsakkeesta
X-Correlation-ID; jos puuttuu, generoidaan palvelinpuolella (esim. GUID). - Sopimusversio luetaan
Accept-otsakkeesta (taiX-Api-Version-otsakkeesta). - Requestin aloitus kirjataan: menetelmä, polku, Correlation-ID, etä-IP, aloitetaan suoritusajan mittaus.
- Suorita business-logiikka; kääri DB-kutsut mahdollisuuksien mukaan transaktionaalisesti.
- Kaappaa poikkeukset: määritä HTTP-status, luo JSON-virheobjekti, aseta Response-otsakkeeksi
X-Correlation-ID. - Requestin päättymisen kirjaus: status, kesto, tarvittaessa virhekoodi.
Säikeiden hallinta palvelimella: Miksi Correlation-ID ilman kontekstidielin kurinpitua menettää merkityksensä
Yksi yleinen Delphi-reunaesimerkki: palvelumenetelmä käynnistää asynkronista työtä (esim. raportin generointi, import, push DMS:ään). Tällöin alkuperäinen request-säie ei välttämättä ole se, joka myöhemmin tuottaa lokirivejä. Jos Correlation-ID tunnetaan vain „alussa“, jäljitettävyys hajoaa.
Käytännön sääntö: Kaikki, mikä ei pysy tiukasti request-säikeessä, saa kontekstin eksplisiittisesti välitettynä. Vaikka se näyttää lisäävän parametrilistoja, se maksaa itsensä takaisin. Vaihtoehtoisesti voi työskennellä selkeästi määritellyn kontekstiobjektin kanssa, joka tarkoituksella annetaan workerille (sen sijaan, että käytettäisiin globaaleja muuttujia tai piilotettuja singletoneja).
Tyypilliset kääntöpisteet RemObjects-/Delphi-palveluissa:
- DB-yhteydet per säie: BDE-Ablosung mit nativer Anbindung-yhteydet eivät ole automaattisesti säikeiden välillä jaettavissa. Yhteyspooli tai yksi yhteys per säie on usein järkevämpi kuin ”yksi globaali yhteys”.
- Transaktiorajat: Jos pyynnön sisällä on useita toisiinsa kuuluvia vaiheita, transaktion on pysyttävä samassa loogisessa yksikössä. Asynkroninen työ ei saa jatkua ”vahingossa” samassa transaktiossa.
- Peruutus: Kun asiakas katkaisee yhteyden (proxy timeout, selain suljettu), palvelin jatkaa usein suorittamista. Mieti tietoisesti, onko taustatyöllä silloin enää järkeä.
Tietojen käyttö ja virhekoodit: 409 ei ole ”myös 500”
Integraatiohankkeissa siisti virheiden luokittelu on enemmän kuin kosmetiikkaa. Se ratkaisee, osaako vastapuoli (ERP-connector, ETL-jobi, Asiakasportaali) reagoida oikein. Muutama käytännön ohjenuora, jotka ovat toimineet Delphi/RemObjects-ympäristöissä:
- 400 Bad Request: Validointi, puuttuvat/virheelliset parametrit, JSON ei parseudu. Tärkeää: vastaus pitää pysyä ennustettavana, vaikka body olisi rikki.
- 401/403: Erottele autentikointi ja valtuutus. 401 tarkoittaa ”ei tunnistettavissa/virheellinen identiteetti”, 403 ”identiteetti ok, mutta kielletty”.
- 404: Resurssi ei ole olemassa. Turvallisuussyistä varovaisuus: ei aina paljasteta, onko jokin olemassa.
- 409 Conflict: Asioihin liittyvä ristiriita (esim. versiokonflikti, ”tila ei salli tätä toimintoa”, 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ä ei osata luokitella kunnolla. Tähän sisältyy myös „tietokanta poissa käytöstä“, „aikakatkaisu“ ja „käsittelemätön poikkeus“.
Delphi-spesifinen vinkki: Monet tietokantavirheet nousevat esiin yleisinä poikkeuksina. Kannattaa tietoisesti tarkistaa datan käyttökerroksessa tunnettuja tilanteita ja muuntaa ne EApiError-muotoon. Tärkeää: älä sisällytä SQL-fragmentteja tai sisäisiä taulu-/sarakkeen nimiä asiakasviesteihin. Nämä tiedot kuuluvat lokeihin, eivät vastaukseen.
Debuggausvinkki: toistettavat virheet ”Contract Snapshot” -menetelmällä
Epätavallinen, mutta tuotannossa erittäin hyödyllinen käytäntö: tallenna virhetilanteissa (tai valikoidusti tietyille Correlation-ID:ille) ”snapshot” koostuen Request-headereista + Request-bodysta debug-spool-tiedostoon. Tämä ei ole jatkuvaa lokitusta (tietosuoja/volyymi), vaan kontrolloitu työkalu vaikeasti toistettavien tapausten jäljentämiseksi tuotantoläheisyydestä.
Tärkeää: snapshot ei koskaan saa tallentaa suodattamattomia Authorization-Headereita, tokeneita tai henkilötietoja. Käytännössä tämä tarkoittaa: tiedon peittämistä (maskointia) ja aktivointia vain feature-flagilla tai whitelistillä (esim. vain tietyille Correlation-ID:ille, lyhyille aikaväleille).
Puhdas toteutus käytännössä: maskaus sen sijaan, että jättää pois
Aidoissa integraatioissa juuri kriittiset kentät ovat usein niitä, joita debuggauksessa tarvittaisiin (esim. tunnisteet). Yleisen poisjättämisen sijaan maskaus on parempi: korvaa tokeneista osa, säilytä sähköpostista vain domain, IBANista vain viimeiset numerot. Näin tapaus pysyy toistettavana ilman tarpeetonta datan levittämistä tiedostojärjestelmään. Lisäksi snapshot tulee selkeästi merkitä debug-artefaktiksi ja määrittää sille säilytysaika.
Turvallisuus ja käyttö: Header-weitergabe, proxyketjut ja aikakatkaisut
Yksi REST-rajapinta päättyy harvoin suoraan asiakkaaseen. Tyypillisiä ovat ketjut, joissa on reverse-proxy, TLS-terminointi, WAF tai API-gateway. Tästä seuraa käytännön huomioita:
- Remote IP: Älä luota sokeasti
X-Forwarded-For-otsikkoon. Hyväksy se vain luotettavilta proxylta ja muuten käytä suoraa socket-IP:tä. Käyttöohjeissa tulee määritellä, mitkä hyppäykset ovat luotettavia. - Timeouts: Jos proxyn aikakatkaisu on 30 sekuntia mutta backendisi tarvitsee 2 minuuttia, synnytät Ghost-Requests:ejä. Aseta aikakatkaisut ketjussa johdonmukaisesti ja päätä toimintamalli: synkroninen pyyntö vai job-malli (202 Accepted + status-päätepiste).
- Correlation-ID: Aseta Correlation-ID vastausotsikoihin, jotta ylläpitäjät voivat yhdistää sen lokien ja asiakaspuolen tietojen kanssa. Jos gateway tuottaa oman request-ID:n, kirjaa molemmat ID:t lokiin ja pidä niiden yhteys näkyvissä.
- Fehlertexte: Tuotantoympäristössä ei sisäisiä yksityiskohtia vastauksissa. Debug-tiedot sallitaan vain kontrolloidusti (stage/feature-flag) ja tarvittaessa vain lokiin.
Einordnung: Warum RemObjects SDK hier im Vorteil sein kann
Delphi-ekosysteemeissä rakennetaan usein REST-palvelimia kevyemmillä kehyksillä (esim. minimalistiset HTTP-reitittimet). RemObjects SDK näyttää vahvuutensa, kun teillä on tai tarvitsette monikerroksisen arkkitehtuurin:
- Selkeät palvelurajat: Palvelumenetelmät ovat eksplisiittisiä, sopimukset ovat versionoitavissa.
- Transporte und Serialisierung: Voitte kommunikoida JSON:lla, mutta myös muilla viestimuodoilla (asetuksista riippuen) ilman, että liiketoimintalogiikka sekoittuu.
- Betrieb: Hosting-vaihtoehdot ja integraatio olemassa oleviin Windows- ja Linux-palveluihin ovat suunniteltavissa, mukaan lukien hallitut käyttöönotot.
Esitelty lähestymistapa täydentää tätä niillä osilla, jotka arkityössä usein puuttuvat: yhtenäiset virheobjektit, deterministinen versionointi ja korreloitava lokitus. Erityisesti yksilöllisessä yritysohjelmistossa, jolla on pitkä elinkaari, säästät näin aikaa päivityksissä ja ulkoisten järjestelmien integraatiossa.
Fazit: Lohnt sich der Aufwand – und wo kippt der Ansatz?
Hyöty syntyy, kun REST-rajapintanne ei ainoastaan „toimi“, vaan on pitkäjänteisesti ylläpidettävä: vakaat JSON-sopimukset, versionointi ilman URL-sekaannusta, jäljitettävät virheet ja debuggaus ilman arvailua. Tässä kontekstinhallinta, Correlation-ID ja keskitetty poikkeusten kartoitus RemObjects SDK:ssa ovat erityisen vahvoja.
Käyttörajoitukset: Jos teillä on vain yksittäinen, lyhytikäinen päätepiste ilman integraatiokumppaneita, media-tyyppiversiointi voi tuntua nopeasti ylimitoitetulta. Myös snapshot-lokitus on järkevää vain, jos toteutatte peittelyn ja aktivoinnin kurinalaisesti. Ja: jos proxypino muokkaa tai poistaa otsikoita, teidän on ensin oikaistava infrastruktuuri, muuten debuggaat väärää kerrosta.
Jos modernisoitte olemassa olevaa Delphi-palvelinympäristöä tai tarvitsette prosessiläheisen ohjelmistoratkaisun siistin integraation ERP/DMS/CRM-järjestelmiin, nämä mekanismit ovat usein ero „toimii testissä“ ja „toimii tuotannossa“ välillä.
Toiminnallisessa ympäristössä myös Delphi REST-API ja REST-Server sekä Remobjects Sdk Delphi ovat keskeisiä, kun integraatioiden, tietovirtojen ja jatkokehityksen on toimittava saumattomasti ja hallitusti.
Keskustele projektista tai modernisointihankkeesta Net-Base kanssa.
Seuraava vaihe
Kun aiheesta muodostuu todellinen projekti, arkkitehtuuri, nykytila ja operointi on tarkasteltava yhdessä varhaisessa vaiheessa.
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, tietojen käyttö, portaalit ja käyttöönotto eivät siirry myöhempään vaiheeseen.
- Näette ajoissa, mikä vaihtoehto on taloudellisesti ja operatiivisesti kannattava.