@aventii/link (1.0.0)
Installation
@aventii:registry=npm install @aventii/link@1.0.0"@aventii/link": "1.0.0"About this package
@aventii/link
Utilitaires de communication HTTP entre les microservices Aventii.
@aventii/link fournit une couche commune pour :
- créer des clients HTTP inter-MS ;
- transporter automatiquement le token d'authentification interne ;
- transmettre un contexte utilisateur et pays de confiance ;
- recevoir et valider ce contexte côté microservice ;
- relayer les requêtes depuis un gateway vers les microservices ;
- utiliser différents transports HTTP sans coupler le reste du code à Fetch ou Axios.
Le paquet ne gère pas les secrets eux-mêmes et ne dépend pas de
pmc-keys.
Installation
npm install @aventii/link
Axios n'est nécessaire que si createAxiosTransport est utilisé :
npm install axios
Le transport Fetch repose sur l'API Fetch native de Node.js.
API publique
const {
createServiceClient,
ServiceRequestError,
createInternalContextMiddleware,
requireUserContext,
requireCountryContext,
createGatewayProxy,
createFetchTransport,
createAxiosTransport
} = require('@aventii/link');
Client inter-MS
createServiceClient(options)
Crée un client associé à un microservice donné.
Le client utilise une URL de base fixe, récupère le token interne via
getAuthToken, peut transmettre x-user-id et x-country depuis un
contexte explicitement fourni, protège ces headers contre une surcharge
directe par le consommateur, gère les query params et les corps JSON,
puis délègue l'exécution HTTP à un transport.
Exemple
const { createServiceClient, createFetchTransport } = require('@aventii/link');
const access = createServiceClient({
baseUrl: process.env.ACCESS_URL,
getAuthToken,
transport: createFetchTransport()
});
const response = await access.request({
method: 'GET',
path: '/api/user/123'
});
console.log(response.data);
Le nom du client appartient au consommateur. Plusieurs connecteurs peuvent naturellement coexister :
const access = createServiceClient({ ... });
const di = createServiceClient({ ... });
const notif = createServiceClient({ ... });
await access.request({ ... });
await di.request({ ... });
await notif.request({ ... });
client.request(options)
Envoie une requête vers le microservice associé au client.
await client.request({
method: 'POST',
path: '/api/example',
query: { active: true },
body: { value: 42 },
context: {
userId: '...',
country: 'CM'
},
headers: {
'x-custom-header': 'value'
},
timeoutMs: 5000,
stream: false
});
La réponse normalisée possède la forme :
{
status,
headers,
data
}
Une réponse HTTP hors plage 2xx provoque une ServiceRequestError.
Contexte inter-MS
Le protocole peut transporter deux informations de contexte :
x-user-id
x-country
Ces headers ne doivent pas être considérés comme fiables lorsqu'ils proviennent directement d'un client public. Ils sont supprimés puis reconstruits par les composants Aventii à partir d'un contexte de confiance.
Le pays est normalisé sous forme de code à deux lettres en majuscules,
par exemple CM, MG ou FR.
createInternalContextMiddleware(options)
Crée un middleware destiné aux microservices recevant des appels internes. Il vérifie le token inter-MS puis construit :
req.internalContext
Exemple :
{
userId: '...',
country: 'CM'
}
Utilisation :
const { createInternalContextMiddleware } = require('@aventii/link');
app.use(createInternalContextMiddleware({
isValidToken: checkInternalToken
}));
requireUserContext
Middleware exigeant la présence d'un utilisateur dans le contexte interne.
app.get('/private', requireUserContext, handler);
L'identifiant est ensuite disponible via :
req.internalContext.userId
requireCountryContext
Middleware exigeant la présence d'un pays valide dans le contexte interne.
app.get('/country-specific', requireCountryContext, handler);
Le pays est disponible via :
req.internalContext.country
Gateway
createGatewayProxy(options)
Crée un middleware Express capable de relayer une requête vers le microservice correspondant à une table de routes.
Le gateway sélectionne la destination, retire les informations sensibles fournies directement par le client, remplace le token d'autorisation par le token inter-MS, injecte éventuellement l'identifiant utilisateur et le pays depuis des fonctions de confiance, transmet les corps et flux, puis relaie la réponse du microservice.
Les détails techniques des erreurs internes ne sont pas publiés au client.
Exemple
const { createGatewayProxy, createFetchTransport } = require('@aventii/link');
const proxy = createGatewayProxy({
routes: [
{ prefix: '/di', target: process.env.DI_URL },
{ prefix: '/pics', target: process.env.PICS_URL }
],
getAuthToken,
getUserId: req => req.user?._id,
getCountry: req => req.country,
transport: createFetchTransport()
});
app.use(proxy);
Transports HTTP
La logique de communication de @aventii/link est indépendante de la
bibliothèque HTTP utilisée.
Un transport doit exposer :
transport.request(options)
Deux transports sont fournis.
createFetchTransport(options)
Transport basé sur l'API Fetch native de Node.js.
const { createFetchTransport } = require('@aventii/link');
const transport = createFetchTransport();
Il prend notamment en charge les timeouts, JSON, texte, réponses sans contenu et streams.
Le module Node.js stream utilisé par ce transport est natif et ne
nécessite aucune installation supplémentaire.
createAxiosTransport(options)
Transport basé sur Axios.
const axios = require('axios');
const { createAxiosTransport } = require('@aventii/link');
const transport = createAxiosTransport({ axios });
Axios est volontairement injecté par le consommateur. Le cœur du paquet n'est ainsi pas couplé à une implémentation HTTP particulière.
Streams
Les clients peuvent demander une réponse streamée :
const response = await pics.request({
method: 'GET',
path: '/image/123',
stream: true
});
response.data.pipe(destination);
Le transport Fetch convertit les ReadableStream Web en streams Node.js
afin d'exposer un comportement cohérent avec Axios.
Erreurs
ServiceRequestError
Les erreurs provenant d'un appel inter-MS sont normalisées sous forme de
ServiceRequestError.
Propriétés disponibles :
err.name
err.code
err.status
err.data
err.method
err.url
err.cause
Les erreurs de transport peuvent notamment être normalisées avec les codes :
TIMEOUT
NETWORK_ERROR
Exemple :
try {
await access.request({ path: '/api/user/123' });
} catch (err) {
if (err instanceof ServiceRequestError) {
console.error(err.code, err.status);
}
}
Les messages générés par le paquet sont préfixés par [link] afin
d'être facilement identifiables dans les logs.
Sécurité
@aventii/link distingue volontairement les données reçues d'un client
public des informations inter-MS de confiance.
En particulier :
- le header
authorizationd'origine n'est pas propagé par le gateway ; x-user-idfourni directement par le client n'est pas considéré comme fiable ;x-countryfourni directement par le client n'est pas considéré comme fiable ;- ces valeurs sont reconstruites depuis les fonctions de confiance fournies au gateway ;
- les erreurs réseau internes ne sont pas exposées telles quelles au client public ;
- les détails techniques restent disponibles dans les logs serveur.
Le paquet ne doit pas recevoir directement les secrets maîtres de
l'infrastructure. Il reçoit uniquement les fonctions nécessaires à leur
utilisation, par exemple getAuthToken.
Philosophie
@aventii/link définit le protocole commun de communication entre les
composants Aventii sans connaître leur métier.
Il ne doit pas savoir ce qu'est une réservation, une notification ou un utilisateur métier, quelles bases de données sont utilisées, ni comment les secrets maîtres sont stockés.
Il fournit uniquement les primitives communes permettant aux services de communiquer de manière homogène, contrôlée et interchangeable.
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| jest | ^30.0.0 |