FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour This is the code behind that community post. You know the one.
Posts: 7020
Joined: Fri Oct 07, 2005 07:07 PM

This is the code behind that community post. You know the one.

Posted: Thu Mar 26, 2026 07:54 AM

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.

Code (harbour): Select all Collapse
// -----------------------------------------------------------------------
// 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 NIL

FiveWin 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:

Code (json): Select all Collapse
{
  "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: oracle_orders.json, sat_diot.json, oxxo.json. Easy to change without recompile. AI can generate them directly.

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.

Code (harbour): Select all Collapse
// -----------------------------------------------------------------------
// 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 ApiRequest() and gets an answer.


---

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 answer

imagine:

FiveWin β†’ describes a situation β†’ service thinks β†’ returns decision

The 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

Posts: 0
Joined: Wed Aug 26, 2026 09:41 AM

Re: This is the code behind that community post. You know the one.

Posted: Sat Aug 29, 2026 10:39 AM

Hey Otto,

Great write-up. The adapter + microservice split is exactly the pattern that scales well when you start hitting multiple backends. Clean separation β€” FiveWin stays focused on UI/logic, the Harbour microservice handles all the messy integrations.

A few thoughts that might help others implementing this:


1. The HttpPost abstraction matters more than it looks

Since FiveWin has different HTTP options (WinHttp, hb_httpOpen/hb_httpSend from libcurl, or even THttpDocument), wrapping that in a single function is key. Here's a portable starting point using hb_httpSend:

FUNCTION HttpPost( cUrl, cBody, hHeaders )
   LOCAL cResponse := ""
   LOCAL oHttp := nil
   // Example using harbour's built-in HTTP (libcurl backend)
   cResponse := hb_httpSend( ;
      "POST", ;
      cUrl, ;
      cBody, ;
      { "Content-Type: application/json" } )
   RETURN cResponse

Adapt it to whatever your stack supports β€” but the point is: this is the only place you change when switching HTTP engines.


2. On the config management options you listed

You got cut off, but here's my take on the three:

Approach Pros Cons
Hash in code Zero dependencies, fast dev Recompile for any change
JSON files Easy to version, AI-friendly, no DB needed Multi-user sync issues, file locking
Database Dynamic, role-based access, auditable Extra dependency, more infra

My suggestion: start with JSON files, but design the LoadEndpoint() function to accept a generic interface so you can swap to DB later without touching ApiRequest():

FUNCTION LoadEndpoint( cEndpoint )
   // Start with files, later swap to:
   // RETURN LoadFromDB( cEndpoint )
   RETURN hb_jsonLoad( "endpoints/" + cEndpoint + ".json" )

3. One addition: error handling + retry

External systems (SAT, Oracle) are unreliable. Consider adding a retry wrapper at the adapter level:

FUNCTION ApiRequestRetry( cEndpoint, hData, nRetries )
   LOCAL nAttempt, hResponse
   DEFAULT nRetries TO 3
   FOR nAttempt := 1 TO nRetries
      hResponse := ApiRequest( cEndpoint, hData )
      IF ! Empty( hResponse ) .AND. hb_HGetDef( hResponse, "status", 0 ) == 200
         RETURN hResponse
      ENDIF
      hb_idleSleep( nAttempt * 2 )  // simple backoff
   NEXT
   RETURN NIL

4. On the microservice side

If you're building that Harbour microservice (Part 2 of your diagram), mod_harbour + Apache/Nginx works great for this. Each endpoint becomes a small Harbour function that talks to Oracle/SAT/etc. The FiveWin app never touches those drivers directly.

This is essentially what you described β€” just wanted to make it explicit.


Looking forward to seeing the rest of the config section and how you're handling auth token rotation. That's where the JSON file approach usually gets tricky.

+1 on this pattern. More people should adopt it.

Continue the discussion