flexway
FRENTalk to an expert

Delegating a decision to a service

GuideOperationsSince version 1.2.0

Markdown

Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.

Goal: let a service of one's own system decide on a request (a risk score, a business right) or enrich it with a header - without a plugin in the gateway.

Command - the service is declared as an upstream, the policy names it:

upstreams:
  risk: { url: http://risk.internal:8080 }       # pool, bulkhead, circuit breaker: its own
policies:
  definitions:
    risk-check:
      type: external-call
      phase: request                             # request: before the upstream; response: after its answer
      config:
        upstream: risk                           # a declared upstream, never a URL
        path: /score
        timeout: 150ms                           # required, at most the operation's budget
        on_failure: fail-closed                  # required: fail-open | fail-closed
        send: { headers: [X-Tenant], body: true }
        deny: { when: "response.status == 403", status: 403, code: risk_refused }
        set_headers: { X-Risk-Score: "response.body.score" }

Check: a POST /orders the service refuses gets 403 application/problem+json (code: risk_refused) without the upstream seeing it; accepted, the upstream receives X-Risk-Score; flexway_policy_external_calls_total{policy="risk-check"} counts each call by outcome (ok, denied, failed_open, failed_closed); the trace shows an external-call risk-check span under the request's.

Rule What it means
Declared service upstream names an upstream of upstreams (HTTP, without token_exchange), path a literal path: no URL built from the request. The call is a POST
What is sent The headers send.headers names, taken from the request prepared for the upstream (declared ones, the tenant the gateway wrote); never Authorization nor a protected header. send.body: the request body, read within server.limits.max_body
What it decides deny (when on the answer, a 4xx status, a declared code) or set_headers (CEL expressions on response.status and response.body, the service's answer) - nothing else
Failure No answer in time, service unreachable, circuit open, a 5xx, an answer a condition cannot read: fail-open goes on without the policy, fail-closed refuses (504, 503, 502) - never a default
Budget The call consumes the operation's budget (x-flexway-timeout): a slow service leaves the upstream less time, never more time in total
Circuit breaker The service is an upstream: failing, its circuit opens (Flexway-Degraded: circuit-open; upstream=risk) and the gateway stops calling it
Refused at load No timeout or no on_failure; a timeout past the budget; an undeclared upstream; a protected header in set_headers; send.body on a stream or in the response phase; gRPC, inference - the message names the operation and the policy
Simulation /simulate never calls the service: the policy is not applied there

Full example: External call.

Next steps