Skip to content

Structure du talk #7

@julien-topcu

Description

@julien-topcu

Déroulé du talk

0. Prérequis

Liens de référence:

Étapes de préparation pré-live-coding:

  • Mettre VSCode en thème "fond clair".

  • Installer Snippet - Visual Studio Marketplace pour VSCode.

  • Créer snippet "basic functional test" avec Snippet - Visual Studio Marketplace:

    contenu du snippet
    // @ts-check
    
    // run with: $ npx mocha test/functional/createPlaylist.functional.tests.js
    
    const assert = require('assert');
    const { createPlaylist } = require('../../app/domain/features');
    
    describe('playlist', () => {
      it('should be created by a user without playlist', async () => {
        const playlistName = 'summer mega mix 2022';
        const playlist = await createPlaylist('userWithoutPlaylist', playlistName);
        assert.equal(playlist.id, 0);
        assert.equal(playlist.name, playlistName);
      });
    
      it('should be created by a user with playlist', async () => {
        const playlistName = 'summer mega mix 2023';
        const playlist = await createPlaylist('userWithPlaylist', playlistName);
        assert.equal(playlist.id, 1);
        assert.equal(playlist.name, playlistName);
      });
    });
  • Préparer l'environnement de live coding, dans le terminal de VSCode:

    nvm use 
    npm install
    git checkout migration-start # ancienne version: migration-start-devoxx-2022
    docker compose up -d mongo
    . ./env-vars-testing.sh 
    export MONGODB_PORT=27117 
    npm run test-reset

1. Faire une démo d'OpenWhyd

Adrien

  • La démo
  • Décrire le problème : Les contributeurs ont lâché l'affaire car trop fragile et trop difficile à comprendre
  • Survol de la code base :
    • Partir de Application.js
    • préciser que: pas de typage
    • parler du framework maison => fichier app.route, notamment les api --> subdir --> controllers/api/post.js --> fonction intéressante: insertion d'un morceau de musique. (post Controller sur la méthode insert)

Objectifs:

  • Permettre à quelqu'un qui n'a pas d'expérience sur la code base d'openWhyd d'être autonome dans la correction de bugs.
  • Le but étant de maintenir l'existant, on veut améliorer sur place ! On ne veut pas faire une fuite en avant, en créant un module from scratch qui paraitrait plus simple à faire mais qui rajouterait de la complexité de maintenance sur le legacy

Donc => Migration in situ

2. Préparation du terrain opératoire: Biopsies Clean Codiennes

But : Y voir plus claire sur la code base
But caché (intention) : Montrer la fragilité de la code base

  1. Expliquer que l'on vise de migrer l'action insert de controllers/api/post
  2. Exécuter les tests d'intégration pour montrer qu'on en a :
npm run test:integration:post # avant, on lançait le serveur, puis: $ npm run test:integration
  1. Changer dans controllers/api/post, p puis uId uNm de l'objet q dans insert. Et expliquer qu'on est en train de changer la structure en base de données.
  2. (redémarrer serveur puis) Réexécuter les tests d'intégration et montrer que les messages d'erreurs ne permettent pas de voir ce qu'on a cassé

3. Ajout des approval tests

git checkout migration-start-with-approval-tests
npm install
  1. Expliquer le principe des Approval tests, théorie + pratique.
  2. Montrer un fichier d'approval de When posting a track
  3. Ouvrir approval.tests.js et expliquer le test correspondant
  4. Lancer le test :
npm run test:approval
  1. Montrer dans les logs que l'on a un diff des approvals.
  2. Supprimer la modification de userName et userId

4. Amener plus de lisibilité dans le code

Lisibilité

Adrien:

  1. Survol de insert jusqu'à process playlist en expliquant le code
  2. Montrer que insert a beaucoup de responsabilité => décider que la partie createPlaylist est déjà un scope suffisant à refactorer

Jordan:

  1. Extraction de extractPlaylistRequest
  2. Passage du bloc try-catch en pure fonction
  3. postRequest.pl = extractPlaylistRequest(...) et dire que ça pourrait être fait directement à l'initialisation de postQuery.
  4. Remplacer l'initialisation à undefined par extractPlaylistRequest(...)
  5. Extraction needToCreatePlaylist dans le if
  6. Comment rendre la séquentialité de createPlaylist plus explicite ?

Adrien:

  1. Wrapper dans une Promise createPlaylist()
  2. Virer le actualInsert() superflu

Diagnostic

  • Julien : Mais il fait quoi ce createPlaylist ?
  • Adrien : Présente userModel.createPlaylist()
  • Jordan & Julien s'étonnent et commentent du couplage du métier dans la couche de persistence

5. Modéliser le domaine

  1. Créer le répertoire domain
  2. Créer le contrat d'entrée du domaine via l'api. Le domaine doit dépendre que des objets du domaine, mais il n'y pas de typage fort en JS.
  • Julien : On peut migrer TypeScript ?
  • Adrien : Trop compliqué mais y'a un autre moyen... Tu veux quoi comme types ?
  1. Création du type CreatePlaylist dans api.ts
  2. Création du type Playlistdans types.ts
  • Julien : Mais du coup c'est du TypeScript, comment on va raccrocher les wagon sans migrer de langage ?
  • Adrien : Avec la JSDoc
  1. Refactorer l'appel de la Promise(createPlaylist) pour bien détourer la déclaration de la fonction createPlaylist
const createPlaylist = (userId, playlistName) =>
      new Promise((resolve) =>
        userModel.createPlaylist(userId, playlistName, resolve)
);
  1. Appliquer le type CreatePlaylist via JSDoc puis activer la vérification avec ts-check
  2. Montrer que l'on bénéficie du typage dans vscode via l'autocomplétion sur playlist et les warnings de "compilation" si on fait un playlist.toto
  3. Extract de createPlaylist dans features.js dans le domaine.
  4. Appliquer le ts-check dans features
  5. Lancer les approval tests

6. Extraire la logique métier de la couche de persistence

  1. Remontrer le code de userModel.createPlaylist() pour caractériser le métier qu'on veut migrer.
  2. ⚠️ Créer un test fonctionnel (ex: test/functional/playlist.functional.test.js) pour figer les comportements que l'on a compris et expliquer qu'on ne s'appuie pas sur les Approvals, car le domaine est testé en isolation pure.
  3. Appliquer le snippet basic functional test (clic droit + "insert snippet") et expliquer les tests
  4. Supprimer la dépendance userModel dans features.createPlaylist() en la commentant
  5. Résumer le code de userModel.createPlaylist() et écrire un "algo" dans features
  await fetch()
  playlist = {
    id:
    name: 
  }
  await save()
  return playlist
  1. Créer spi.ts
  2. Expliquer que les playlists sont stockées dans les utilisateurs, ce qui nécessite de créer un type UserRepository.
  3. Ajouter une fonction getUserById qui retourne une Promise de User
  4. Définir User dans types.ts
  5. features a maintenant besoin d'une instance de UserRepository. Comme on ne veut pas se coupler avec le repository, on crée une factory createFeatures` pour d'injecter une instance.
  6. Typer le paramètre userRepository de la factory
  7. Appeler userRepository.getUserbyId dans createPlaylist
  8. Retourner dans userModel et expliquer la logique de création d'un id de playlist, la copier-coller dans features et la nettoyer.
  9. Julien à Adrien : est-ce qu'on doit sauvegarder tout le user pour sauvegarder une playlist ?
  10. Définir une fonction insertPlaylist dans la SPI dans le UserRepository
  11. L'appeler dans features
  12. Replugguer dans le test fonctionnel via createFeatures
  13. Expliquer la nécessité de créer un Stub de UserRepository
  14. Créer le stub userRepository et la typer avec la SPI. Générer le squelette au moyen de l'IDE.
  15. Créer une base d'utilisateurs dans le test pour initier le repository en reprenant les ids de user dans les tests fonctionnels
  16. Implémenter les fonctions du Stubs
  17. Enrichir les tests pour vérifier la persistance de la playlist
    //premier test
    assert.deepEqual(users[0].playlists, [playlist]);
   //deuxieme test
    assert.deepEqual(users[1].playlists[1], playlist);
  1. Faire passer les tests ($ npx mocha test/functional/playlist.functional.test.js)

7. Recabler le domaine avec la persistance

  1. Créer un répertoire infrastructure avec UserCollection.js
  2. Typer UserCollection avec la SPI et générer le squelette
  3. Retourner dans le controller et rebinder le domain avec une instance de UserCollection
  4. Exécution des tests approvals pour vérifier que le câblage fonctionne
  5. Implémenter UserCollection.getUserById()
  6. Lancer les approvals montrer que ça casse à cause d'un problème de length sur les playlists. Expliquer la différence de modélisation entre la base et le domaine (playlists vs pl).
  7. Définir UserDocument dans infrastructure/type.ts, typer le retour de la requête mongo dans getUserbyId et tenter de retourner le userDocument.
  8. Créer la fonction de mapping mapToUser et expliquer le rôle de l'anti-corruption layer
  9. Relancer les tests d'approval pour montrer que le problème n'est plus là
  10. Implémenter insertPlaylist
  11. Montrer que les tests d'approval ne fonctionnent toujours pas à cause de l'inconsistance des données en base (prefs, mid...).

Jordan reprend la main du copilote.

  • Jordan : Où est-ce qu'on fait ça ?
  • Adrien montre d'où ça vient via le fetch et le save dans userModel
  • Montrer que la migration de données est faite de manière implicite et n'est pas très robuste (s'il n'y a jamais de save après un save).
  1. Appliquer la migration de données
git cherry-pick migrate-db # --> commit: 96d94f5ee3e5d4488615ca054a45b34c8e742a9a

Si le cherry-pick ne passe pas, vider l'index de git
14. Relancer les approvals

8. Découplage du controller

  1. Commencer par le controller post en expliquant qu'on sort du fichier les instanciations. Commenter les require de features et userCollection
  2. Faire passer features à la stack d'appel de insert jusqu'à Application.js
  3. Ne pas oublier subdir.js en montrant app.route
  4. Déplacer la création des objets dans attachLegacyRoutesFromFile
  5. Relancer les tests d'approval et montrer que ça fonctionne

9. Décommisionnement des Approval tests (optionnel)

git checkout main

Aller dans les tests d'intégration de post et de l'infra mongo


TODO:

  • Voir comment définir les types en JSDoc de manière globale pour éviter les imports

Metadata

Metadata

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions