Hello friends,
Reading Benjamin's list made me think. Not because the projects aren't impressive β they are. But because most of us have built similar things, separately, and never connected the dots.
So instead of another list of projects, here is a pattern that came out of the last few months of work. Something everyone here can actually use.
---
The core idea: one adapter, one service
Most of us are connecting FiveWin to external systems β Oracle, SAT, ContPAQ, REST APIs of all kinds. And most of us are solving that problem differently every single time.
What if we had a common base?
The architecture is simple:
FiveWin (UI + Business Logic)
β
Universal JSON Adapter
β
Microservice (your own small Harbour server)
β
External systems: Oracle, SAT, APIs, DB...Two parts. Clear responsibilities.
---
Part 1: The JSON Adapter (FiveWin side)
The adapter has one job: talk JSON, know endpoints, nothing else.
// -----------------------------------------------------------------------
// Universal JSON Adapter β FiveWin side
// -----------------------------------------------------------------------
FUNCTION ApiRequest( cEndpoint, hData )
LOCAL hConfig, cUrl, cResponse
// Load endpoint config from JSON file
hConfig = LoadEndpoint( cEndpoint )
cUrl = hConfig["base_url"] + hConfig["path"]
// HTTP call (WinHttp / Curl / whatever you use)
cResponse = HttpPost( cUrl, hb_jsonEncode( hData ), hConfig["headers"] )
RETURN hb_jsonDecode( cResponse )
RETURN NIL
// -----------------------------------------------------------------------
// Example: Create an order
// -----------------------------------------------------------------------
FUNCTION CreateOrderInERP()
LOCAL hOrder, hResponse, cOrderId
hOrder = hb_Hash()
hOrder["order_number"] = "INV-2026-0001"
hOrder["customer_id"] = 12345
hOrder["total"] = 299.99
hOrder["currency"] = "EUR"
hOrder["items"] = { ;
{ "sku" => "A100", "qty" => 1, "price" => 199.99 }, ;
{ "sku" => "B200", "qty" => 2, "price" => 50.00 } ;
}
hResponse = ApiRequest( "orders_create", hOrder )
IF ! Empty( hResponse )
cOrderId = hb_HGet( hResponse, "id" )
MsgInfo( "Order created: " + cOrderId, "Success" )
ELSE
MsgStop( "Transfer failed", "Error" )
ENDIF
RETURN NILFiveWin knows nothing about Oracle. Nothing about SAT. Just: send this, get that back.
---
Part 2: The Endpoint configuration
Instead of hardcoding URLs and auth into your code, define each endpoint in a simple JSON file:
{
"name": "orders_create",
"base_url": "http://localhost:8080",
"path": "/api/orders",
"method": "POST",
"headers": { "Authorization": "Bearer YOUR_TOKEN" },
"timeout": 30
}Three practical options for managing these configs:
Hash in code β fast to start, not scalable. Fine for version 1.
JSON files (recommended to start) β one file per endpoint:
Database β dynamic, multi-user, more complex. A topic for phase 2.
---
Part 3: The Microservice (Harbour server side)
This is where things get interesting. The microservice is not a big system. It is a small Harbour server that exposes clean endpoints and handles all the complexity that does not belong in FiveWin.
// -----------------------------------------------------------------------
// Mini Harbour Microservice β example endpoints
// -----------------------------------------------------------------------
FUNCTION HandleRequest( cPath, cMethod, cBody )
LOCAL hData := hb_jsonDecode( cBody )
DO CASE
CASE cPath == "/api/health"
RETURN JsonOk( { "status" => "ok" } )
CASE cPath == "/api/orders" .AND. cMethod == "POST"
RETURN ProcessOrder( hData )
CASE cPath == "/api/diot" .AND. cMethod == "POST"
RETURN ProcessDiot( hData )
OTHERWISE
RETURN JsonError( "Unknown endpoint" )
ENDCASE
RETURN NIL
// -----------------------------------------------------------------------
FUNCTION ProcessOrder( hData )
// Here: connect to Oracle, ContPAQ, whatever you need
// FiveWin never sees this complexity
LOCAL hResult := hb_Hash()
hResult["id"] = "ORD-001"
hResult["status"] = "created"
RETURN JsonOk( hResult )
// -----------------------------------------------------------------------
FUNCTION JsonOk( hData )
hData["success"] = .T.
RETURN hb_jsonEncode( hData )
FUNCTION JsonError( cMsg )
RETURN hb_jsonEncode( { "success" => .F., "error" => cMsg } )The microservice handles: business logic, API integration, authentication, data transformation. FiveWin handles none of that. It just calls
---
Why this matters β and why Functions, not Classes
Notice what is not here: no class hierarchy, no factory, no inheritance chain. Just functions with clear inputs and outputs.
That is intentional. For this kind of integration work, functions are the right tool:
- You see exactly what goes in and comes out
- AI generates them reliably and correctly
- You can open the code two years later and still understand it
- The microservice pattern works because each function does one thing
Classes have their place. But for connecting systems, moving data, and building integrations β functions win every time.
---
The connection to AI
This architecture works especially well with AI assistance because:
Ask ChatGPT or Claude: "generate a JSON endpoint config for Oracle order API" β you get a working file in seconds.
Ask ChatGPT or Claude: "write a Harbour function that processes a DIOT request" β clean, testable function, immediately usable.
The adapter stays the same. Only the endpoint config and the service logic change per project. That is the real productivity gain β not magic, just a reusable structure.
---
One more thought β where this architecture leads
Once the adapter and microservice are in place, something interesting becomes possible.
Right now, FiveWin calls a service and gets data back. Clean, simple, useful. But the next logical step in this architecture is not just getting data β it is getting reasoning.
Instead of:
FiveWin β asks for data β gets answerimagine:
FiveWin β describes a situation β service thinks β returns decisionThe microservice does not just fetch an Oracle record. It reasons about it. Checks availability, cross-references bookings, weighs options β and returns a structured answer that FiveWin can act on directly.
The technical foundation for this already exists in the community. The jump from "adapter calls API" to "adapter calls reasoning engine" is smaller than it looks β and the same JSON structure handles both.
For our kind of business software β hotel systems, ERP, inventory, billing β this would change what "integration" means entirely.
Worth a separate thread when we have the adapter basics sorted.
---
What I would find interesting to discuss here
We all have experience with pieces of this. Oracle connections, SAT integrations, REST calls. Nobody has put it together as a shared base yet.
Some open questions worth discussing:
- How are others handling authentication (Bearer tokens, OAuth) in Harbour?
- JSON file vs. database for endpoint configs β what works in practice?
- How does this connect to the reasoning loop work already happening in the community?
Happy to share more code if there is interest.
Drop your experiences below β sharing the patterns behind them benefits all of us, especially by learning from each otherβs experience.
Best regards,
Otto