Skip to content

Commit

Permalink
Sync original
Browse files Browse the repository at this point in the history
  • Loading branch information
forresst committed Mar 8, 2021
1 parent 02137b3 commit 938171a
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 9 deletions.
8 changes: 6 additions & 2 deletions README.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Lire dans une autre langue : [![CN](/assets/flags/CN.png)**CN**](/README.chines
## Table des matières

1. [Structure de projet (5)](#1-structure-de-projet)
2. [Gestion des erreurs (11) ](#2-gestion-des-erreurs)
2. [Gestion des erreurs (12) ](#2-gestion-des-erreurs)
3. [Style du code (12) ](#3-style-du-code)
4. [Tests et pratiques générales de qualité (13) ](#4-tests-et-pratiques-générales-de-qualité)
5. [Pratiques de mise en production (19) ](#5-pratiques-de-mise-en-production)
Expand Down Expand Up @@ -699,7 +699,9 @@ Toutes les déclarations ci-dessus renverront false si elles sont utilisées ave

## ![] 5.14. Attribuez un id de transaction à chaque relevé du journal

**TL;PL :** Attribuez le même identifiant, transaction-id : {une valeur}, à chaque entrée du journal à l'intérieur d'une même requête. Ensuite, lors de l'inspection des erreurs dans les journaux, il est facile de conclure ce qui s'est passé avant et après. Malheureusement, cela n'est pas facile à réaliser dans Node en raison de sa nature asynchrone, consultez les exemples de code.
Également connu sous le nom de corrélation id / transit id / tracing id / request id / request context / etc.

**TL;PL :** Attribuez le même identifiant, transaction-id : {une valeur}, à chaque entrée du journal à l'intérieur d'une même requête. Ensuite, lors de l'inspection des erreurs dans les journaux, il est facile de conclure ce qui s'est passé avant et après. Malheureusement, cela n'est pas facile à réaliser dans Node en raison de sa nature asynchrone, consultez les exemples de code. Jusqu'à la version 14 de Node, cela n'était pas facile à réaliser en raison de la nature asynchrone de Node, mais depuis l'arrivée de AsyncLocalStorage, cela est devenu possible et plus facile que jamais. Consultez les exemples de code fournis.

**Autrement :** L'examen d'un journal d'erreurs de production sans le contexte (ce qui s'est passé auparavant) rend le travail de réflexion beaucoup plus difficile et lent.

Expand Down Expand Up @@ -1564,6 +1566,8 @@ Thanks goes to these wonderful people who have contributed to this repository!
<td align="center"><a href="https://github.com/andrewjbarbour"><img src="https://avatars.githubusercontent.com/u/77080074?v=4?s=100" width="100px;" alt=""/><br /><sub><b>andrewjbarbour</b></sub></a><br /><a href="#content-andrewjbarbour" title="Content">🖋</a></td>
<td align="center"><a href="https://MasujimaRyohei.jp"><img src="https://avatars.githubusercontent.com/u/17163541?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mr</b></sub></a><br /><a href="#content-MasujimaRyohei" title="Content">🖋</a></td>
<td align="center"><a href="https://github.com/kubanac95"><img src="https://avatars.githubusercontent.com/u/16191931?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandar</b></sub></a><br /><a href="#content-kubanac95" title="Content">🖋</a></td>
<td align="center"><a href="http://vincentjonathan.com"><img src="https://avatars.githubusercontent.com/u/32597776?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Owl</b></sub></a><br /><a href="#content-SuspiciousLookingOwl" title="Content">🖋</a></td>
<td align="center"><a href="https://github.com/yedidyas"><img src="https://avatars.githubusercontent.com/u/36074789?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yedidya Schwartz</b></sub></a><br /><a href="#content-yedidyas" title="Content">🖋</a> <a href="#example-yedidyas" title="Examples">💡</a></td>
</tr>
</table>

Expand Down
2 changes: 1 addition & 1 deletion sections/docker/use-cache-for-shorter-build-time.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ CMD ["node", "dist/server.js"]

## Useful links

Docker docks: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache
Docker docs: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache
163 changes: 161 additions & 2 deletions sections/production/assigntransactionid.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,150 @@

Un journal typique est un registre des entrées de tous les composants et requêtes. Lorsqu'une ligne ou une erreur suspecte est détectée, il devient difficile de faire correspondre d'autres lignes appartenant au même flux spécifique (par exemple, l'utilisateur "John" a essayé d'acheter quelque chose). Cela devient encore plus critique et difficile dans un environnement de micro-services lorsqu'une requête/transaction peut concerner plusieurs ordinateurs. Il convient de remédier à ce problème en attribuant une valeur d'identification de transaction unique à toutes les entrées d'une même requête, de sorte qu'en détectant une ligne, on puisse copier l'identifiant et rechercher toutes les lignes qui ont un identifiant de transaction similaire. Toutefois, la réalisation de cette opération dans Node n'est pas simple, car un seul processus est utilisé pour toutes les requêtes - envisagez d'utiliser une bibliothèque qui peut regrouper les données au niveau de la requête - voir l'exemple de code suivant. Lorsque vous appelez d'autres micro-services, transmettez l'identifiant de la transaction en utilisant une entête HTTP comme "x-transaction-id" pour conserver le même contexte.

<br/><br/>
<br/>

### Exemple de code : partage de TransactionId entre les fonctions de requête et entre les services à l'aide de [async-local-storage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage)

**Qu'est ce que async-local-storage ?** Vous pouvez le considérer comme l'alternative de Node pour le stockage local des threads.
Il s'agit essentiellement d'un stockage pour les flux asynchrones dans Node. Vous pouvez en savoir plus [ici](https://www.freecodecamp.org/news/async-local-storage-nodejs/).

```javascript
const express = require('express');
const { AsyncLocalStorage } = require('async_hooks');
const uuid = require('uuid/v4');

const asyncLocalStorage = new AsyncLocalStorage();

// Définit le TransactionId des requêtes entrantes
const transactionIdMiddleware = (req, res, next) => {
// Le premier argument de asyncLocalStorage.run est l'initialisation de l'état du stockage, le second argument est la fonction qui a accès à ce stockage
asyncLocalStorage.run(new Map(), () => {
// Essaye d'extraire le TransactionId de l'entête de la requête, ou en génére un nouveau s'il n'existe pas
const transactionId = req.headers['transactionId'] || uuid();

// Définit le TransactionId à l'intérieur du stockage
asyncLocalStorage.getStore().set('transactionId', transactionId);

// En appelant next() dans la fonction, nous nous assurons que tous les autres middlewares fonctionnent dans le même contexte AsyncLocalStorage
next();
});
};

const app = express();
app.use(transactionIdMiddleware);

// Définit le TransactionId des requêtes sortantes
app.get('/', (req, res) => {
// Une fois que TransactionId a été initialisé dans le middleware, il est accessible à tout moment pour le flux de requêtes.
const transactionId = asyncLocalStorage.getStore().get('transactionId');

try {
// Ajoute TransactionId comme entête afin de le passer au service suivant
const response = await axios.get('https://externalService.com/api/getAllUsers', headers: {
'x-transaction-id': transactionId
});
} catch (err) {
// L'erreur est transmise au middleware, et il n'est pas nécessaire d'envoyer le TransactionId
next(err);
}

logger.info('externalService a été appelé avec succès avec l\'entête TransactionId');

res.send('OK');
});

// Un middleware de gestion des erreurs appelle le journal
app.use(async (err, req, res, next) => {
await logger.error(err);
});

// Le journal peut désormais ajouter le TransactionId à chaque entrée, de sorte que les entrées d'une même requête aient la même valeur
class logger {
error(err) {
console.error(`${err} ${asyncLocalStorage.getStore().get('transactionId')}`);
}

info(message) {
console.log(`${message} ${asyncLocalStorage.getStore().get('transactionId')}`);
}
}
```
<br/>

<details>
<summary><strong>Exemple de code : utilisation d'une bibliothèque pour simplifier la syntaxe</strong></summary>

Partage du TransactionId entre les fonctions de requête actuelles en utilisant [cls-rtracer](https://www.npmjs.com/package/cls-rtracer) (une bibliothèque basée sur async-local-storage, implémentée pour les middlewares Express & Koa et les plugins Fastify & Hapi)

```javascript
const express = require('express');
const rTracer = require('cls-rtracer');

const app = express();

app.use(rTracer.expressMiddleware());

### Exemple de code : configuration typique d'Express
app.get('/getUserData/{id}', async (req, res, next) => {
try {
const user = await usersRepo.find(req.params.id);

// Le TransactionId est accessible de l'intérieur du journal, il n'est pas nécessaire de l'envoyer
logger.info(`les données de l'utilisateur ${user.id} ont été récupérées avec succès`);

res.json(user);
} catch (err) {
// L'erreur est transmise au middleware
next(err);
}
})

// Un middleware de gestion des erreurs appelle le journal
app.use(async (err, req, res, next) => {
await logger.error(err);
});

// Le journal peut désormais ajouter le TransactionId à chaque entrée, de sorte que les entrées d'une même requête aient la même valeur
class logger {
error(err) {
console.error(`${err} ${rTracer.id()}`);
}

info(message) {
console.log(`${message} ${rTracer.id()}`);
}
}
```
<br/>

Partage le TransactionId entre les micro services

```javascript
// cls-tracer a la capacité de stocker le TransactionId sur les entêtes des requêtes sortantes de votre service, et d'extraire le TransactionId des entêtes des requêtes entrantes, en remplaçant simplement la configuration par défaut du middleware
app.use(rTracer.expressMiddleware({
// Ajoute le TransactionId à l'entête
echoHeader: true,
// Respecte le TransactionId de l'entête
useHeader: true,
// Nom de l'entête TransactionId
headerName: 'x-transaction-id'
}));

const axios = require('axios');

// Maintenant, le service extérieur obtiendra automatiquement le TransactionId actuel comme entête
const response = await axios.get('https://externalService.com/api/getAllUsers');
```
</details>
<br/>

**REMARQUE : l'utilisation de async-local-storage est soumise à deux restrictions :**
1. Il nécessite Node v.14.
2. Il est basé sur une construction de niveau inférieur dans Node appelé async_hooks qui est encore expérimental, donc vous pouvez craindre des problèmes de performance. Même s'ils existent, ils sont très négligeables, mais vous devriez faire vos propres choix.

<br/>

<details>
<summary><strong>Exemple de code - configuration Express typique sans dépendance de async-local-storage</strong></summary>

```javascript
// à la réception d'une nouvelle requête, commencez un nouveau contexte isolé et définissez un identifiant de transaction. L'exemple suivant utilise la bibliothèque npm continuation-local-storage pour isoler les requêtes
Expand Down Expand Up @@ -37,3 +178,21 @@ class logger {
}
}
```
</details>

<br/><br/>

### Bon : Journaux avec un TransactionId attribué - peut être utilisé comme filtre pour ne voir qu'un seul flux
![alt text](https://i.ibb.co/YjJwgbN/logs-with-transaction-id.jpg "Journaux avec transaction id")
<br/><br/>

### Mauvais : journaux sans TransactionId - pas de possibilité d'utiliser un filtre et de ne voir qu'un seul flux, vous devez comprendre par vous-même quels journaux sont pertinents entre tous les « bruits » environnants
![alt text](https://i.ibb.co/PFgVNfn/logs-withtout-transaction-id.jpg "Journaux avec transaction id")

<br/><br/>

### Citation de blog : « La notion d'ID de corrélation est simple. C'est une valeur qui est commune à toutes les requêtes, messages et réponses dans une transaction donnée. Avec cette simplification, vous obtenez beaucoup de pouvoir ».

Extrait de [rapid7](https://blog.rapid7.com/2016/12/23/the-value-of-correlation-ids/)

> Dans le passé, lorsque le comportement transactionnel se déroulait dans un seul domaine, dans le cadre de procédures par étapes, le suivi du comportement des requêtes et des réponses était une tâche simple. Cependant, aujourd'hui, une requête vers un domaine particulier peut impliquer une myriade de requêtes asynchrones ultérieures du domaine de départ vers d'autres domaines. Par exemple, vous envoyez une requête à Expedia, mais en coulisse, Expedia transmet votre requête sous forme de message à un gestionnaire de messages. Ce message est ensuite consommé par un hôtel, une compagnie aérienne et une agence de location de voitures qui répondent également de manière asynchrone. La question se pose donc, alors que votre seule requête est transmise à une multitude de consommateurs en cours de traitement, comment pouvons-nous suivre la transaction ? La réponse est : utiliser un identifiant de corrélation.
2 changes: 1 addition & 1 deletion sections/testingandquality/3-parts-in-name.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('Service Produits', () => {
describe('Service Produits', () => {
describe('Ajoute un nouveau produit', () => {
it('Devrait retourner le bon statut', () => {
//hmm, quelle est cette vérification de test ? quels sont le scénario et les attentes ?
//hmm, quelle est cette vérification de test ? quels sont le scénario et les attentes ?
const newProduct = new ProductService().add(...);
expect(newProduct.status).to.equal('validationEnAttente');
});
Expand Down
18 changes: 15 additions & 3 deletions sections/testingandquality/avoid-global-test-fixture.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
### Exemple de code : chaque test agit sur son propre ensemble de données
```javascript
it('Lors de la mise à jour du nom du site, obtenez une confirmation réussie', async () => {
// le test ajoute de nouveaux enregistrements et agit uniquement sur les enregistrements
// Arrange (Préparer) - le test ajoute de nouveaux enregistrements et agit uniquement sur les enregistrements
const siteUnderTest = await SiteService.addSite({
name: 'siteForUpdateTest'
});

// Act (Agir)
const updateNameResult = await SiteService.changeName(siteUnderTest, 'newName');

// Assert (Vérifier)
expect(updateNameResult).to.be(true);
});
```
Expand All @@ -28,15 +32,23 @@ before(() => {
// ajouter des données de sites et d'administrateurs à notre base de données. Où sont les données ? à l'extérieur. Sur un json externe ou un framework de migration
await DB.AddSeedDataFromJson('seed.json');
});

it('Lors de la mise à jour du nom du site, obtenez une confirmation réussie', async () => {
// Je sais que le nom du site « Portal » existe - je l'ai vu dans les fichiers de remplissage
// Arrange (Préparer) - Je sais que le nom du site « Portal » existe - je l'ai vu dans les fichiers de remplissage
const siteToUpdate = await SiteService.getSiteByName('Portal');

// Act (Agir)
const updateNameResult = await SiteService.changeName(siteToUpdate, 'newName');

// Assert (Vérifier)
expect(updateNameResult).to.be(true);
});

it('Lorsque vous interrogez par le nom du site, obtenez le bon site', async () => {
// Je sais que le nom de site « Portal » existe - je l'ai vu dans les fichiers de remplissage
// Act (Agir) - Je sais que le nom de site « Portal » existe - je l'ai vu dans les fichiers de remplissage
const siteToCheck = await SiteService.getSiteByName('Portal');

// Assert (Vérifier)
expect(siteToCheck.name).to.be.equal('Portal'); // Échec ! Le test précédent change le nom :[
});
```

0 comments on commit 938171a

Please sign in to comment.