Imports de données

user-friendly

avec Loopback

@ClementRicateau 

cRicateau 

9 mois

3 projets

contexte

Projet AGILE de 9 moiS dans une grande banque

 

construction d'un outils metier pour remplacer

 

fichier à importer régulièrement

 

audits de sécurité

HYPOTHÈSES

UN SEUL LANGAGE POUr LE BACKEND

 

développement réalisé en agile, sprints d'une semaine

 

Le client veut passer peu de temps possible sur l'import de données

 

L'IMPORT DE DONNÉES SERA RÉALISÉ PAR UN UTILISATEUR DU MÉTIEr

pierre

Pierre est chef de projet informatique

Pierre est trentenaire, éduqué, "digital native"

Pierre a des notions sur les bases de données

Pierre maitrise excel

import failed, file is incorrect. Please fix it and retry

pierre ne comprend pas:

alors pierre ré-essaye

possibilité de se tromper sans corrompre la base

LE DROIT DE SE TROMPER

 

autonomie  pour corriger LES ERREURS DANS LES données

UTILISATEUR INFORMÉ DE L'ÉTAT SON UPLOAD DE FICHIERS

Pour pierre

 

pas de dépendance envers une équipe de développeurs

 

correspond aux standards du marché en ux

 

autonomie:

 

qualité de service:

Pour les devs

pas de responsabilité après la fin du projet

 

pas besoin de tester en local

 

pas processus difficile à débugger

DEMO

1. upload d'un fichier CSV

 

2. fork d'un process node

 

3. STOCKEr EN BASE L'ÉTAT DE L'IMPORT: 'EN COURS'

 

4. debut d'une transaction SQL

 

 

5. execution du process d'import

stocker les erreurs

rollback

status = 'error'

 

commit transaction

status = 'sucess'

les transactions sql

Connecteurs pour MySQL, PostgreSQL, SQL Server,Oracle connector

 

Read Committed: un SELECT voit seulement les données non commitées

Cheese.beginTransaction(
  {
     isolationLevel: Post.Transaction.READ_COMMITTED
  }, function(err, tx) {
  // Now we have a transaction (tx)
});
Cheese.import = function(file, callback) {
  var tx;
  tx = {};
  return Model.startTransaction(tx, file)
  .then(function() {
    return Cheese.importProcess(tx, file);
  })
  .then(function() {
    return tx.commit(function(err) {
      return Cheese.exitAfterSuccess(callback);
    });
  })
  .catch(function(err) {
    return tx.rollback(function(rollbackError) {
      return Cheese.exitAfterError(err, callback);
    });
  });
};

process d'import

dans cheese.js

resultat

deuxième étape

un fichier

  • 2 fichiers = 2 méthodes

  • 3 fichiers = 3 méthodes

  • ...

 

les mixins loopback

ajouter une méthode à plusieurs modèles

 

    {
      "_meta": {
        ...
        "mixins": [
          ...
          "./mixins"
        ]
      }
    }

model-config.json

common/mixins/upload.js

Model.import = function(file, callback) {
  var tx;
  tx = {};
  return Model.startTransaction(tx, file)
  .then(function() {
    return Model.importProcess(tx, file);
  })
  .then(function() {
    return tx.commit(function(err) {
      return Model.exitAfterSuccess(callback);
    });
  })
  .catch(function(err) {
    return tx.rollback(function(rollbackError) {
      return Model.exitAfterError(err, callback);
    });
  });
};

les mixins loopback

ajouter une méthode à plusieurs modèles

common/mixins/upload.js

Model.import = function(file, callback) {
  var tx;
  tx = {};
  return Model.startTransaction(tx, file)
  .then(function() {
    return Model.importProcess(tx, file);
  })
  .then(function() {
    return tx.commit(function(err) {
      return Model.exitAfterSuccess(callback);
    });
  })
  .catch(function(err) {
    return tx.rollback(function(rollbackError) {
      return Model.exitAfterError(err, callback);
    });
  });
};
{
  "name": "Cheese",
  "base": "PersistedModel",
  "mixins": {
    "Upload": {},
  },

cheese.json

overrider la logique métier dans le model

if (!Model.importLine) {
  Model.importLine = function(line) {
    return new Promise(function(resolve, reject) {
      return resolve();
    });
  };
}

common/mixins/upload.js

Cheese.importLine = function(transaction, line) {
  return Cheese.findOne({
    where: {
      name: line.CheeseName
    }
  }, transaction
  ).then(function(foundCheese) {
    var cheese;
    cheese = {
      name: line.CheeseName
    };
    if (foundCheese) {
      cheese.id = cheese.id;
    }
    return Cheese.upsert(cheese, transaction);
  });
};

common/cheese.js

paramétrisation

Description des colonnes de l'excel dans le model.json

{
  "name": "Cheese",
  "base": "PersistedModel",
  "mixins": {
    "Upload": {},
    "CsvUpload": {
      "requiredHeaders": [
        "cheeseName",
        "animalMilk"
      ],
      "validators": {
        "cheeseName": {
          "required": true,
          "type": "string"
        },
        "animalMilk": {
          "required": true,
          "type": "string"
        }
      }
  },
module.exports = function(Model, options) {
  if (!Model.validateLine) {
    return Model.validateLine = function(line) {
        return importService.validate(line, options.validators);
      }
    };
  }
};

un fichier csv a importer = un modèle loopback

du CSV au XLS

{
  "name": "Cheese",
  "base": "PersistedModel",
  "mixins": {
    "Upload": {},
    "XlsUpload": {
      "requiredHeaders": [
        "cheeseName",
        "animalMilk"
      ],
      "validators": {
        "cheeseName": {
          "required": true,
          "type": "string"
        },
        "animalMilk": {
          "required": true,
          "type": "string"
        }
      }
  },

un modèle != une table en base

difficultés techniques

PROCESS node forké pas tué

erreurs sql mal gérées

certaines erreurs manquées

callbacks

+

gestion d'erreurs chainée

complexité

tout promissifier!

loopback-promisify

Loopback 3.0

Model.import = function(file, callback) {
  var tx;
  tx = {};
  return Model.startTransaction(tx, file)
  .then(function() {
    return Model.importProcess(tx, file);
  })
  .then(function() {
    return Model.exitAfterSuccess(callback);
    });
  })
  .catch(function(err) {
    return Model.exitAfterError(err, callback);
    });
  });
};

bilan

les utilisateurs testent l'upload trop tard

permettre de choisir le delimiteur

permettre l'import .xls  .xlsx

règles de validations souples

réparation automatique d'erreurs ?

Pour aller plus loin...

sécurité: détection du type de fichiers

ux: détection de l'encodage

Merci

DES QUESTIONS ?

Copy of Imports de données User-friendly avec Loopback

By Clément Ricateau

Copy of Imports de données User-friendly avec Loopback

  • 1,001