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.
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ä.
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)
- Lue Correlation-ID pyynnön headerista
X-Correlation-ID; jos se puuttuu, luo palvelinpuolella (esim. GUID). - Lue sopimusversio
Accept-headerista (taiX-Api-Version-headerista). - Kirjaa pyynnön aloitus lokiin: metodi, polku, Correlation-ID, etä-IP, aloita keston mittaus.
- Suorita liiketoimintalogiikka; kapseloi tietokantakutsut mahdollisuuksien mukaan transaktionaalisesti.
- Tartu poikkeuksiin: määrää HTTP-tila, luo JSON-virheobjekti, aseta Response-header
X-Correlation-ID. - 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ä.