api-surface.md 4.8 KB

The three API tiers

xly does not have one API — it has three, hosted in three independent Spring Boot services with different audiences and different operational contracts. Knowing which one you're talking to is the first step in any integration conversation.

Tier Service Context path Audience
Internal xlyEntry /xlyEntry The xly SPA itself (BACK + FROUNT). Not a stable contract for external callers.
External xlyApi /xlyApi Tenants and integrators calling xly from outside. The data-driven public API.
Inbound webhooks xlyInterface /xlyInterface Third-party systems pushing events into xly. Carries Swagger.

Each service builds to its own WAR and runs in its own JVM. They share nothing in process; they share the database, and that shared database is what makes their separation work — internal-API writes show up to external-API reads automatically because both run against the same schema.

Why three tiers (and what splitting them costs)

Each tier was originally split off to answer a different question:

  • Internal is large (universal CRUD over all metadata-driven modules), volatile (changes when the framework changes), and intentionally untyped (the SPA decides what to ask for, server obeys).
  • External is curated (only the endpoints integrators are allowed to use), versioned by sApiCode, and authenticated with bearer tokens.
  • Inbound webhooks receive untrusted bodies from third-party systems and route them to xly handlers. The Swagger UI lives here because that audience benefits most from interactive documentation.

The split has real costs that the wiki should not gloss over:

  • Three WARs to deploy, monitor, and version-pin. A new release has to ship coordinated builds of xlyEntry, xlyApi, and xlyInterface. Mismatches (e.g., a schema change in xlyEntry that xlyApi hasn't picked up) are silent until the call path hits them.
  • Duplicate code. RequestAddParamUtil exists in both xlyPersist (for xlyEntry) and xlyApi (near-identical 56-vs-57 line copy). InterfaceController exists in both xlyApi and xlyInterface with overlapping /interfaceDefine/callthirdparty/* endpoints. Keeping the two halves in sync is operational discipline, not a compile-time guarantee.
  • No shared session. A user authenticated in BACK has no session in xlyApi — external callers fetch a separate bearer token. This is correct for external integrators but means internal cross-WAR calls (rare in practice, common in temptation) have to go through the public token flow.
  • Three context-paths means three reverse-proxy entries. The mapping from BACK=:8597 and FROUNT=:8598 to the actual WARs lives in nginx config that isn't in this repo. Misconfigured proxies are a common failure mode the codebase can't catch.

Could the split have been a single deployable with internal package boundaries? Yes — Spring Boot supports it. The benefit of that alternative would be: one build, one set of dependencies, one session story, no duplicate utility classes. The cost: harder to scale tiers independently, harder to rate-limit external callers without affecting the SPA. xly chose the deployment-time isolation; the wiki's job is to acknowledge what that choice traded away.

What each tier looks like at runtime

  • Internal — see the five-key read. One endpoint (/business/getModelBysId) returns the entire form layout; another (/business/addUpdateDelBusinessData) writes any row in any table the metadata names. Few endpoints, generic shapes.
  • External — most calls go through /api/invoke/{sApiCode}. The sApiCode is a row in the sysapi metadata table that names the SQL template, parameters, auth requirement, and target. New external APIs are registered as data, not as code — the same data-driven thesis the framework applies to its own forms.
  • Inbound webhooks/interfaceDefine/invoke/{interfaceInvoke} receives a payload, looks up the matching handler in metadata, runs the configured SQL or stored procedure. Plus a few hard-coded receivers (/Push, /Pull, /send/sendQw) for specific partners.

Where to look next