Hello friends,
Is anyone already using Harbour/FiveWin this way — with SQL moved behind a microservice?
From what I can see, this approach seems to simplify the client side significantly by removing SQL client DLLs, network/TLS handling, and tight database coupling from the UI process.
---
A short note on performance (before architecture)
When the UI, the service, and the SQL database run on the same machine, local IPC (socket / JSON / HTTP over localhost) is very fast.
Based on practical measurements and typical setups, the service layer usually adds:
- 1–2 ms per request for JSON encode/decode and local IPC
A simple SQL query itself typically takes:
- 1–10 ms (often more, depending on indexes and data size)
In other words:
The service layer usually adds less than 10–20% of the total request time.
The SQL query itself remains the dominant cost.
In practice, the performance impact is often negligible,
while the gain in stability and isolation is very noticeable.
---
Why this matters
In the past, embedding SQL directly into the Harbour application was often unavoidable:
- no permanently running web servers
- no lightweight local services
- no clean separation between UI, business logic, and data access
In short, the client process itself effectively had to be the server.
---
What has changed
Today the situation looks very different:
- services and web servers run continuously
- local IPC is cheap and reliable
- SQL servers already run in their own processes
- expectations for stability and fault isolation are much higher
This opens a new option:
Use SQL without embedding SQL clients into the UI process.
---
The idea in one sentence
The client talks JSON to a service.
The service talks SQL to the database.
As a result:
- the client loads no SQL DLLs
- the client does not know table schemas
- SQL connectivity is no longer part of the UI runtime
---
From DBF thinking to service thinking
Classic DBF-style access
USE kunden
GO TOP
DO WHILE !EOF()
? name, ort
SKIP
ENDDOService-based access
hRes := ServiceCall( "kunden_list", { "limit" => 10 } )
FOR EACH hRow IN hRes["rows"]
? hRow["name"], hRow["ort"]
NEXTSame usage pattern.
Different responsibility.
---
Minimal client-side code
FUNCTION ServiceCall( cAction, hData )
LOCAL hReq := { ;
"action" => cAction, ;
"data" => hData ;
}
RETURN hb_jsonDecode( SendToService( hb_jsonEncode( hReq ) ) )FUNCTION SendToService( cJson )
LOCAL oSock := hb_socketOpen()
hb_socketConnect( oSock, "127.0.0.1", 9001 )
hb_socketSend( oSock, cJson + hb_eol() )
LOCAL cRes := hb_socketRecv( oSock, 8192 )
hb_socketClose( oSock )
RETURN cResThis is the entire client-side SQL interface.
---
What runs where
Client (Harbour / FiveWin)
- UI
- business orchestration
- JSON in / JSON out
Service
- SQL execution
- prepared statements
- transactions
- error handling
Database
- SQLite / PostgreSQL / MySQL
SQL lives behind the service boundary.
---
What disappears from the client process
With a service-based approach, the following no longer need to be loaded into the UI process:
Database client DLLs
libmysql.dll /libmysqlclient.dll - MariaDB client libraries
libpq.dll (PostgreSQL)- ODBC drivers (
odbc32.dll and vendor-specific drivers)
Crypto / TLS libraries
libssl*.dll libcrypto*.dll - parts of
schannel.dll /bcrypt.dll (DB-related)
Runtime and ABI dependencies
- additional
vcruntime*.dll required by DB clients - extra C/C++ runtime layers pulled in by SQL libraries
Helper libraries
zlib1.dll - character set / ICU libraries (depending on the DB client)
What remains in the client:
- Harbour runtime
- FiveWin UI
- minimal socket/HTTP code
- JSON handling
---
Why this is not overengineering
- no SQL DLLs in the client
- no OpenSSL, ODBC, or DB runtimes in the UI process
- fewer ABI and runtime conflicts
- crashes are isolated by process boundaries
This does not add complexity —
it moves complexity out of the wrong place.
---
Why this fits Harbour well
- Harbour works naturally with hashes and JSON
- DBF-style access maps well to service calls
- SQLite feels similar to DBF (single file, easy backup)
- migration can be done step by step
For example:
USE kunden → ServiceCall( "kunden_list" )---
About the classic all-in-one client approach
The classic all-in-one client approach solved SQL at a time when the client effectively had to be the server and service-based isolation was hard or unavailable.
That constraint is largely gone today.
What remains is an opportunity to separate concerns more clearly:
- UI and orchestration in the client
- SQL and connectivity in a service
---
One-sentence summary
The all-in-one approach already solves SQL —
but today we finally have the option to solve it somewhere else.
That option simply did not exist when these integrations were originally designed.
---
Question to the community
Is anyone already using Harbour/FiveWin this way in production?
If so, I’d be very interested in hearing about real-world experiences — both positive and negative.
Best regards,
Otto