Replace Self-invocation with Particular Module Invocation for vert.x Modules

Replace Self-invocation with Particular Module Invocation for vert.x Modules

Context

Some FOLIO modules call their own public APIs via the gateway or module sidecar to reuse functionality already available behind an endpoint. This is usually called module self-invocation.

Example pattern:

mod-circulation code -> HTTP call to /circulation/rules/request-policy -> sidecar routes the request -> request returns to mod-circulation -> same module executes the rules endpoint

This pattern was found in mod-circulation. In CIRC-2199, the request policy lookup internally used the public /circulation/rules/request-policy endpoint. The fix in mod-circulation PR #1522 replaced the self-call to the endpoint with a direct call to CirculationRulesProcessor.

How Sidecar Handles This

folio-module-sidecar can technically route self-invocation, but it is not a good application design pattern.

The sidecar routing chain checks routes in this order:

module entitlement handler -> ingress routes for the current module -> static egress routes for required modules -> optional dynamic egress routes -> optional gateway fallback -> 404

If a module calls an endpoint it provides, the sidecar matches the request as ingress first and forwards it back to the same module. It does not use normal egress module-to-module behavior for that request.

Important consequences:

  • The request goes through HTTP routing even though the target code is in the same module.

  • Egress token injection is not used because the request is treated as an ingress request.

  • Authorization behavior depends on propagated request headers and sidecar signature handling.

  • The sidecar self-request filter is not a general-purpose bypass for arbitrary module self-calls.

  • A self-request without a token can pass in some sidecar flows, but a request with an invalid token can still fail token parsing.

  • Behavior can differ between local, gateway-routed, sidecar-routed, and dynamic-routing deployments.

Why Self-Invocation Is Bad Practice

Self-invocation couples internal business logic to public HTTP routing. This creates avoidable failure modes:

  • It adds network, serialization, routing, and timeout overhead for code that could run in-process.

  • It depends on proxy-specific request headers and token propagation.

  • It can accidentally bypass normal authorization if sidecar self-request headers are copied incorrectly.

  • It consumes the module’s own HTTP connection pool and can create back-pressure under load.

  • It makes unit testing harder because tests need a fake gateway or sidecar behavior to route the request back to the same module.

  • It hides the real dependency: the module does not need another module; it needs its own service/domain logic.

Recommended Approach

Do not call the module’s own public API from inside the same module.

Instead:

  • Extract the business logic behind the endpoint into a service, processor, repository, or domain component.

  • Let the HTTP resource/controller call that internal component.

  • Let other internal code call the same component directly.

  • Keep public APIs as transport boundaries, not as internal reuse mechanisms.

  • Use HTTP clients only for real module-to-module calls.

  • If a call must target another module explicitly, declare the required interface/module permission and use the sidecar’s normal module-to-module route.

  • Do not rely on sidecar signature headers as an application-level mechanism.

Good Example

Before, mod-circulation effectively did this:

clients.circulationRequestRules() .applyRules(loanTypeId, locationId, materialTypeId, patronGroupId);

That's called:

GET /circulation/rules/request-policy

which is an endpoint provided by mod-circulation itself.

After the fix, it calls the internal rules processor directly:

var params = new RulesExecutionParameters( loanTypeId, locationId, materialTypeId, patronGroupId, null); return circulationRulesProcessor.getRequestPolicyAndMatch(params);

This keeps the API behavior unchanged while removing the internal HTTP loop.