XData Request Flow

Complete walkthrough of how requests flow through the Delphi 12 + TMS XData + UniDAC + PostgreSQL stack.

Contents

Architecture Overview

The stack consists of these layers:

┌─────────────────────────────────────────┐
│  TMS WEB Core Browser Application       │
│  (TAPIDataset, TXDataWebConnection)     │
└──────────────────┬──────────────────────┘
                   │ HTTPS / JSON
┌──────────────────▼──────────────────────┐
│  TMS XData Server (Sparkle HTTP)        │
│  Service Operations + Entity Sets       │
└──────────────────┬──────────────────────┘
                   │ UniDAC
┌──────────────────▼──────────────────────┐
│  PostgreSQL Database                    │
│  Stored Procedures (schema.function)    │
└─────────────────────────────────────────┘

Request Flow Steps

Client: TAPIDataset.Load
TAPIDataset builds HTTP request with parameters, calls TXDataWebConnection
HTTP: Browser XMLHttpRequest
Request sent to XData server URL, e.g., https://api.example.com/hydrology/dashboard.getcards
Server: Sparkle HTTP Dispatcher
TSparkleHttpSysDispatcher receives request, routes to XData module
Server: XData Service Resolution
XData maps URL to service operation: dashboard.getcardsIHydrologyService.GetCards
Server: Service Implementation
Service method executes, uses Spring4D injected connection pool
Server: UniDAC Query
TUniQuery executes: SELECT * FROM dashboard.getcards(fkuserid_ := :p1)
Database: PostgreSQL
Stored procedure executes, returns result set or OUT parameters
Server: JSON Serialization
XData serializes result to JSON, returns HTTP 200 response
Client: Response Handling
TAPIDataset parses JSON, creates fields, fires AfterOpen event

Server-Side Components

Service Interface

TYPE
   [ServiceContract]
   IHydrologyService = INTERFACE(IInvokable)
      ['{GUID}']
      
      [HttpGet] 
      FUNCTION GetCards(UserID: Integer): TStream;
      
      [HttpPost]
      FUNCTION SaveCard(Card: TCardDTO): TStream;
   END;

Service Implementation

TYPE
   THydrologyService = CLASS(TInterfacedObject, IHydrologyService)
   PRIVATE
      FConnectionPool: IDBConnectionPool;  /// Spring4D injected
   PUBLIC
      FUNCTION GetCards(UserID: Integer): TStream;
   END;

FUNCTION THydrologyService.GetCards(UserID: Integer): TStream;
VAR
   Connection: IDBConnection;
   Query: TUniQuery;
BEGIN
   Connection := FConnectionPool.GetConnection;
   Query := TUniQuery.Create(NIL);
   TRY
      Query.Connection := Connection.Connection;
      Query.SQL.Text := 'SELECT * FROM dashboard.getcards(fkuserid_ := :userid)';
      Query.ParamByName('userid').AsInteger := UserID;
      Query.Open;
      Result := QueryToJSONStream(Query);
   FINALLY
      Query.Free;
   END;
END;

XData Module Registration

/// In server initialization
XDataServer.RegisterService(TypeInfo(IHydrologyService), 
   THydrologyService, 'hydrology');

Client-Side Components

TXDataWebConnection

/// Design time or code
XDataConnection.URL := 'https://api.example.com/hydrology';
XDataConnection.Connected := True;

TAPIDataset Configuration

/// Design time properties
dsCards.Connection := XDataConnection;
dsCards.EntitySetName := 'dashboard.getcards';

/// Code: Set parameters and load
dsCards.ParamByName('fkuserid_').AsInteger := CurrentUserID;
dsCards.Load(
   PROCEDURE
   BEGIN
      /// Data available here
   END);

URL Resolution

/// EntitySetName maps to URL path:
/// 'dashboard.getcards' → GET /hydrology/dashboard.getcards?fkuserid_=123

/// For complex params, uses POST with JSON body

Connection Pooling

Spring4D manages database connection pooling on the server:

Pool Configuration

/// In DI container setup
Container.RegisterType<IDBConnectionPool, TUniDACConnectionPool>
   .AsSingleton
   .DelegateTo(
      FUNCTION: IDBConnectionPool
      BEGIN
         Result := TUniDACConnectionPool.Create;
         Result.MinPoolSize := 5;
         Result.MaxPoolSize := 50;
         Result.ConnectionString := GetConnectionString;
      END);

Connection Lifecycle

/// Connection borrowed from pool
Connection := Pool.GetConnection;
TRY
   /// Use connection for queries
FINALLY
   Connection := NIL;  /// Returns to pool (reference counted)
END;

Error Handling

Server-Side Exceptions

/// XData converts exceptions to HTTP errors
TRY
   Query.Open;
EXCEPT
   ON E: Exception DO
      RAISE EXDataHttpException.Create(500, E.Message);
END;

Client-Side Error Handling

dsCards.OnLoadError := 
   PROCEDURE(Dataset: TDataset; ErrorMsg: STRING)
   BEGIN
      ShowMessage('Load failed: ' + ErrorMsg);
   END;

/// Or with callback
dsCards.Load(
   PROCEDURE
   BEGIN
      /// Success
   END,
   PROCEDURE(ErrorMsg: STRING)
   BEGIN
      /// Failure
      Console.log('Error: ' + ErrorMsg);
   END);

Common HTTP Status Codes

CodeMeaningTypical Cause
200OKSuccess
400Bad RequestInvalid parameters
401UnauthorizedMissing/invalid JWT token
404Not FoundUnknown service/entity
500Server ErrorDatabase or code exception
Debugging Tip: Enable XData server logging to see full request/response details. Check PostgreSQL logs for slow queries or procedure errors.