# Déléguer une décision à un service

> Objectif : laisser un service de son SI décider d'une requête (score de risque, droit métier) ou l'enrichir d'un en-tête - sans greffon dans la passerelle.

Guide - Depuis la version 1.2.0 - Flexway v1.6.3 - https://flexway.io/docs/operations-external-call/

> Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la capacité côté produit : [guide produit](https://flexway.io/docs/product-overview/) ; l'appeler : [guide développeur](https://flexway.io/docs/developer-overview/).

**Objectif** : laisser un service de son SI décider d'une requête (score de risque, droit métier) ou
l'enrichir d'un en-tête - sans greffon dans la passerelle.

**Commande** - le service se déclare comme un amont, la politique le nomme :

```yaml
upstreams:
  risk: { url: http://risk.internal:8080 }       # pool, cloisonnement, coupe-circuit : les siens
policies:
  definitions:
    risk-check:
      type: external-call
      phase: request                             # request : avant l'amont ; response : après sa réponse
      config:
        upstream: risk                           # un amont déclaré, jamais une URL
        path: /score
        timeout: 150ms                           # obligatoire, au plus le budget de l'opération
        on_failure: fail-closed                  # obligatoire : 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" }
```

**Vérif** : un `POST /orders` que le service refuse reçoit `403` `application/problem+json`
(`code: risk_refused`) sans que l'amont le voie ; accepté, l'amont reçoit `X-Risk-Score` ;
`flexway_policy_external_calls_total{policy="risk-check"}` compte chaque appel par `outcome` (`ok`,
`denied`, `failed_open`, `failed_closed`) ; la trace montre un span `external-call risk-check` sous
celui de la requête.

| Règle | Ce qu'elle veut dire |
| --- | --- |
| Service déclaré | `upstream` nomme un amont de `upstreams` (HTTP, sans `token_exchange`), `path` un chemin littéral : aucune URL construite depuis la requête. L'appel est un `POST` |
| Ce qui part | Les en-têtes nommés par `send.headers`, pris dans la requête préparée pour l'amont (déclarés, tenant écrit par la passerelle) ; jamais `Authorization` ni un en-tête protégé. `send.body` : le corps de la requête, lu dans `server.limits.max_body` |
| Ce qu'il décide | `deny` (`when` sur la réponse, statut `4xx`, code déclaré) ou `set_headers` (expressions CEL sur `response.status` et `response.body`, la réponse **du service**) - rien d'autre |
| Panne | Pas de réponse à temps, service injoignable, coupe-circuit ouvert, `5xx`, réponse illisible pour une condition : `fail-open` continue sans la politique, `fail-closed` refuse (`504`, `503`, `502`) - jamais un défaut |
| Budget | L'appel **consomme** le budget de l'opération (`x-flexway-timeout`) : un service lent laisse moins de temps à l'amont, jamais plus de temps au total |
| Coupe-circuit | Le service est un amont : en panne, son circuit s'ouvre (`Flexway-Degraded: circuit-open; upstream=risk`) et la passerelle cesse de l'appeler |
| Refusé au chargement | Sans `timeout` ni `on_failure` ; `timeout` au-delà du budget ; un amont non déclaré ; un en-tête protégé dans `set_headers` ; `send.body` sur un flux ou en phase `response` ; gRPC, l'inférence - le message nomme l'opération et la politique |
| Simulation | `/simulate` n'appelle jamais le service : la politique n'y est pas appliquée |

Exemple complet : [Appel externe](https://flexway.io/docs/operations-examples-gateway/#appel-externe).
