Architecture multi-tenant partie 1 : présentation

icon Tags de l'article : , ,

Octembre 22, 2022
En anglais, tenant signifie locataire. Donc architecture multitenant = architecture multi-locataires.

La plupart du temps, lorsqu'on développe une application destinée à plusieurs clients, on va avoir tendance à concevoir une (et une seule) appli web, qu'on déploiera ensuite pour chaque client, chaque client/appli ayant sa propre BDD.



Cette approche simplifie le développement, mais elle va complexifier :
  • le déploiement
  • les mises à jour
  • la maintenance
  • le débogage
  • les backups

Ce qui fait qu'on a vite beaucoup à faire niveau infra/ops :



Ces opérations, quand on a 1 ou 2 clients, sont faciles à gérer manuellement. Mais quand on commence à arriver à 10, 20, 50, ... clients, ben vous voyez vite où ça nous mène :


Du coup on doit automatiser ces opérations. Ce qui demande des compétences ops/devops assez poussées. Et même en automatisant un maximum (procédures, provisionning, scripts, monitoring, ...), ben vous aurez très souvent à intervenir manuellement, ne serait-ce que pour lancer les opérations à chaque nouveau client.

En résumé, plus vous aurez de clients, plus vous aurez besoin d'une équipe ops conséquente et compétente. Ce qui va créer de gros problèmes de scaling humain dans votre structure. Ca plus les problématiques de synchronisation/management.

Et bien sachez qu'il existe une solution applicative à ça : l'architecture multi-tenant.

En quoi ça consiste ? C'est assez simple à expliquer : vous avez une seule application web déployée, et une seule base de données, pour tous vos clients.



Là vous vous dites : euh ouais mais ça peut pas être si simple. En effet, techniquement il va y avoir pas mal de choses à implémenter.

Déjà, il y a trois façons de faire :
  • colonne personnalisée : sur chaque table on va créer une colonne "tenant" ou "tenantId" qui servira à filtrer par tenant dans toutes les requêtes
  • BDD séparées : une seule application web, mais chaque client a sa propre BDD
  • schémas séparés : on crée différents schémas en BDD, et chaque client a son propre schéma et ses propres tables

Chaque solution a ses avantages et inconvénients.
La solution colonne personnalisée permet d'avoir un multitenant complet mais demande beaucoup de rigueur niveau code et architecture applicative.
La solution de BDD séparées oblige à créer/mettre à jour/backuper X BDD, 1 par client.
La solution de schémas séparés oblige à créer des tables pour chaque client, ce qui va rendre compliqué la partie investigation/débug.

Dans cette série d'articles, je vais me concentrer sur l'approche colonne personnalisée, afin d'avoir à m'occuper le moins possible des systèmes.

Si on devait imaginer un postulat de départ : vendre une suite applicative à plein de clients, sans augmenter ma charge de travail même si je passe de 10 clients à 100.

Pour ça, le fonctionnement implémenté est simple :
  • quand une requête arrive, en fonction du nom de domaine/de l'url/d'un autre paramètre dans les headers, on va filtrer les données de la BDD, afin de ne renvoyer que les données concernant ce client.
  • Et quand le client crée un objet, on ajoute une information sur l'objet pour indiquer à quel client appartient cette donnée.

Ainsi, les clients ne peuvent pas accéder aux données des autres clients. Quoi qu'il arrive, chaque client a sa propre portion de données et n'a pas accès aux données des autres.


Comme on le voit ici, chaque client verra une application différente et des données différentes, alors qu'ils utilisent la même infrastructure.

Evidemment, cette approche a ses avantages, et des défauts.

Avantages :
  • 0 problèmes de scaling humain
  • 1 seule application web et 1 seule BDD à déployer/mettre à jour/backuper/monitorer
  • débogage simplifié

Inconvénients :
  • limitations charge (si vous avez trop de clients votre BDD risque de ne pas suivre)
  • potentiellement bloquant pour certains marchés qui imposent un hébergement des données indépendant

Neutre :
  • sécurité : beaucoup de gens diraient "oui mais si ta BDD fuite ils récupèrent les données de tous tes utilisateurs", mais soyons honnêtes, même avec des BDD séparées, quand t'as une fuite d'infos, toutes les infos de tous les clients fuitent la plupart du temps car la sécurité ne diffère pas entre les clients
  • technique : le code va est plus compliqué au départ, mais une fois la base posée, on ne voit "presque" plus la différence avec une application classique

Malgré ses défauts, je pense qu'une architecture multi-tenant vaut vraiment la peine d'être mise en place lorsqu'on sort un produit SAAS et qu'on veut se simplifier la vie sur le long terme.

On va arrêter là pour l'instant, dans le prochain article je pense me concentrer sur l'implémentation.

D'ici là... bon dev à vous !

FAITES DES BACKUPS !

icon Tags de l'article :

Octembre 13, 2022
Je suis de retour.



Il s'est passé beaucoup de choses ces dernières années. Genre pandémie, guerre Russie Ukraine, je suis devenu papa d'une petite fille il y a 2 semaines... de petits changements quoi.

Mais en ce qui nous/vous concerne ici : j'ai perdu mon blog.

Enfin pas tout, juste 5 ans d'historique...



J'ai perdu mon blog suite à un enchainement de coups de malchance (téléphone cassé, bloqué en réparation, expiration de l'hébergement, impossibilité de payer car pas de téléphone pour valider le paiement, carte de ma femme bloquée au même timing, ...)

Quand j'ai voulu voir avec mon hébergeur pour enfin le réactiver, quelques heures après l'expiration, ils m'ont dit que les données avaient été intégralement et irrémédiablement détruites (questions de sécurité).

Heureusement j'avais un backup, mais un backup de 2015. Sans aucun des articles 2016-2020...



Il m'a fallu 2 ans pour trouver le courage, l'énergie, la motivation et le temps de réactiver mon blog, et reprendre 1 à 1 tous les articles, en recheckant toutes les URLS. C'était plus fastidieux que compliqué, mais tellement démotivant.

Et on s'en veut d'avoir zappé les backups.

Et du coup, la morale de l'histoire... FAITES DES BACKUPS !

IMMEDIATEMENT.

MAINTENANT !

AUTOMATISEZ LES !

There are two kinds of people: those who backup, and those who have never lost all their data.

Je n'aime pas la piscine

icon Tags de l'article : , ,

Janvier 07, 2020
Je suis le seul à haïr le concept de piscine dans les grandes écoles d'ingé ?

Pour celles et ceux qui connaissent pas, c'est un projet ultra intense de 1 mois imposé aux étudiant-es peu après leur arrivée à l'école. Projet demandant de bosser énormément pour y arriver.

Pour moi c'est juste du "bizutage officiel" d'écoles qui choisissent la facilité sans se poser la question des conséquences :
  • Déjà ça oblige les étudiants à apprendre par eux-même (et à souvent "mal" apprendre),
  • C'est ultra malsain niveau rythme de travail (semaines de 50 à 70 heures, ce serait illégal en entreprise, alors pourquoi l'encourager à l'école ?)
  • Ca sert à dire aux étudiants "ouais enfin plus tard tu feras ça aussi en entreprise tu sais, le crunch, les heures sup' non payées, tout ça" (alors que non hein, du taf y'en a énormément, et rien n'oblige à bosser comme un dingue pour les beaux yeux du patron, surtout dans l'informatique)
  • Ca encourage les abus de drogues pour tenir (café, boissons énergisantes, clopes, stimulants, etc.)
  • Ca va faire arrêter des étudiants qui auraient été très bon en informatique, juste qui sont un peu moins autodidactes ou qui ont besoin d'être un peu plus accompagnés au début.
  • Ca va faire arrêter les étudiants qui tomberont dans des groupes avec lesquels ils ne s'entendent pas.
  • Ca empêche les gens qui ont des obligations (=une vie, des loisirs, des passions, des responsabilités...) de suivre ce cursus sans faire de sacrifices.
  • On SAIT qu'au delà de 40h par semaine on perd en productivité, et qu'on fait plus de la merde (= bugs) qu'autre chose. C'est franchement l'inverse que vous voulez enseigner dès les premiers mois à l'école ?
  • Enfin ça encourage encore et toujours les mêmes profils à bosser dans l'informatique : les jeunes hommes blancs, un peu geeks/gamers sur les bords, passionnés et avec des moyens et plein de temps libre (pas de job à côté quoi). Et après ces écoles viennent s'étonner de n'avoir que très peu de femmes étudiantes !

Bref, c'est un truc que je trouve vraiment malsain. C'est le choix de la facilité pour les dirigeants des écoles (éliminer les élèves qui ne rentrent pas exactement dans le moule, un peu moins motivés ou pas autodidactes, créer direct une mentalité élitiste de "démerde toi" et "la vraie vie c'est faire des heures sup' non payées").

Après je suis pas contre le fait de bosser en projet. Au contraire. C'est ultra formateur. Mais ça doit être dans un cadre carré, avec des horaires définies, un prof présent et un travail faisable sur la durée du projet, sans heures sup' obligatoires, pour des étudiants de tous niveaux (s'ils/elles sont motivé-es, évidemment).

Pour moi, un projet intense aurait beaucoup plus de sens à la fin d'une année de formation... qu'au début. Quand les étudiant-es se connaissent, savent avec qui ils/elles veulent bosser, mais aussi quand ils/elles ont acquis les compétences nécessaires.

Mais bon, si beaucoup échouent ce projet, on pourrait se demander si ce n'est pas la faute des profs non ?

A méditer.

Le try/catch est-il, aujourd'hui, aussi performant que des erreurs gérées avec des ifs ?

icon Tags de l'article : ,

Décembre 11, 2019
Ce matin, un collègue m'a montré comment il avait réduit le code d'une méthode en passant par un try/catch (à la place d'un enchainement de if).

Je lui ai expliqué que c'était plutôt à éviter, pour 3 raisons :
  • Moins performant
  • Pas forcément plus clair pour le développeur qui arrivera derrière ("pourquoi il a fait un try/catch ? il y a un cas particulier à gérer ?")
  • Ne permet pas de s'assurer que tous les cas métiers ont été gérés en un seul coup d'oeil

Dans cette situation, les points 2 et 3 ne s'appliquaient pas vraiment, le code étant plutôt simple. Mais le point 1 s'appliquait toujours.

Mon collègue m'a dit qu'après avoir fait des tests de son côté, il s'était rendu compte que l'écart de performances entre un try/catch et un enchainement de ifs était négligeable.

J'ai donc voulu tester ça. #doute

Pour ça, j'ai run 2/3 tests avec le code suivant :
@Component({
    selector: 'app-tab1',
    templateUrl: 'tab1.page.html',
    styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
 
    nbItems: Number = 100000;
    public resultIf: Number;
    public resultTryCatch: Number;
 
    constructor() {
        this.doTest();
    }
 
    doTest() {
        let items = [];
        for(let i = 0; i < this.nbItems ; i++) {
            items.push(this.generateRandomToto())
        }
 
        // first we try to update the values of the Totos with if
        let startDate = new Date();
        for(let i = 0; i < this.nbItems ; i++) {
            this.testWithIf(items[i]);
        }
        let endDate = new Date();
        this.resultIf = endDate.getTime() - startDate.getTime();
 
        // then we try with to update the values of the Totos with try/catch
        startDate = new Date();
        for(let i = 0; i < this.nbItems ; i++) {
            this.testWithTryCatch(items[i]);
        }
        endDate = new Date();
        this.resultTryCatch = endDate.getTime() - startDate.getTime();
    }
 
    generateRandomToto(): Toto {
        if(Math.round(Math.random()) === 0) { // the math.round of a math.random gives 0 or 1
            const result = new Toto();
            result.titi = new Titi();
            result.titi.tutu = 'bonjour';
            return result;
        } else {
            const result = new Toto();
            return result;
        }
    }
 
    testWithIf(toto: Toto) {
        if(toto && toto.titi && toto.titi.tutu) {
            toto.titi.tutu = 'wesh';
        }
    }
 
    testWithTryCatch(toto: Toto) {
        try {
            toto.titi.tutu = 'wesh';
        } catch(error) { }
    }
}
 
class Toto {
    public titi: Titi;
}
 
class Titi {
    public tutu: string;
}

Maintenant la question : quel écart en fonction du nombre de try/catchs ?

Voici le résultat pour 100 objets : 0ms avec des ifs, 2ms avec des try/catchs.

Woaw ! On a déjà une différence avec seulement 100 try/catchs !

Et si on augmente ?
  • 1 000 objets -> 1ms avec des ifs, 17ms avec des try/catchs.
  • 10 000 objets -> 1ms avec des ifs, 153ms avec des try/catchs.
  • 100 000 objets -> 2ms avec des ifs, 1427ms avec des try/catchs.

Résultat : non, le try/catch n'est pas aussi performant que les ifs. Loin de là.

Même aujourd'hui, à l'aube de 2020, le try/catch ne doit être utilisé que lorsqu'on a pas le choix :
  • pour attraper globalement les erreurs
  • lorsqu'on n'a pas le choix (par exemple si l'objet/la couche concernée throw des erreurs)
  • pour gérer un cas particulier (erreur spécifiquement attendue)

En dehors de ces situations, le try/catch reste à éviter.

Bon dev à tous et toutes !

Comment stocker les configurations d'accès aux environnements de façon sécurisée

icon Tags de l'article : , , ,

Septembre 13, 2019
Bonjour à tous et toutes,

Aujourd'hui on va parler fichiers de configuration, variables d'environnement, stockage, sécurité et contrôleur de source.

Quand je parle de variables d'environnement, je parle par exemple des identifiants pour se connecter à la BDD, des clefs analytics, ou encore d'autres identifiants critiques.
Il s'agit en général de variables que les développeurs doivent posséder pour les environnements intermédiaires (développement, qualification, préproduction) mais pas pour certains environnements (production, analytics, BI, etc.).
Ces variables sont, en général, stockées dans un gros fichier de configuration qui sera utilisé par le logiciel pour ses différentes connexions.

Du coup la question qui revient à CHAQUE fois : comment stocker de façon sécurisée quelque part ces variables, afin que les développeurs puissent y avoir accès facilement, sans pour autant les rendre récupérables par n'importe qui de passage dans la société ?
Exit donc la feuille collée au mur avec les mots de passe.

Dans ces 5 dernières années, je suis passé par plusieurs solutions :
  • Le service production/exploitation qui s'occupe lui-même de renseigner les clefs de production dans le fichier de configuration. Avantage : sécurité, inconvénient : mise à jour des clefs de configuration ultra complexe. (on doit spécifier à chaque fois les champs ajoutés, les champs modifiés, les champs supprimés, etc. et les fails arrivent souvent)
  • Les développeurs ont toutes les clefs de tous les environnements, et ils les remplacent eux-même pour faire des builds en production. Avantage : simple, inconvénient : sécurité très faible.
  • Les fichiers de configuration sont archivés avec la solution. Avantage : ultra simple, inconvénient : aucune sécurité, n'importe qui ayant accès aux sources a accès à tous les environnements.
  • Les clefs sont enregistrés dans un keepass archivé avec la solution, et seules les personnes concernées connaissent son mot de passe. Avantage : sécurité, inconvénient : très chiant à modifier et saisie constante du mot de passe.


Afin de simplifier les choses sur le projet sur lequel je suis, j'ai proposé la solution suivante :
1) Nous avons un fichier config.zip archivé et chiffré avec mot de passe à la racine du projet
2) Nous avons développé un script nommé init.sh, qui a pour mission d'initialiser le projet.
3) Lorsqu'on lance le script init.sh, il va se charger :
  • De vérifier les versions des packages globaux installés (par exemple les versions de NPM, d'Ionic, de Cordova, ...)
  • De demander le mot de passe du fichier config.zip
  • De dézipper le fichier config.zip grâce au mot de passe renseigné, pour récupérer le fichier de configuration .config
  • De lancer les commandes nécessaires à l'initialisation du projet (npm i par exemple)

4) Nous avons ajouté une commande "zip:config" qui sert à chiffrer notre fichier de configuration (en saisissant à nouveau le mot de passe)
5) Nous avons ajouté dans le gitignore notre fichier de configuration.

Ainsi, nous pouvons archiver le fichier config.zip qui n'est dézippable qu'avec le mot de passe.
Dès qu'une personne doit mettre à jour le fichier de configuration, elle fait un "npm run zip:config", tape le mot de passe, et archive ensuite le nouveau fichier config.zip.
Dès qu'une personne change de branche ou merge la branche principale dans sa branche, elle a juste à faire un "npm run init", puis de saisir le mot de passe.

Cette solution nous permet de garder un process très simple sans pour autant sacrifier la sécurité du projet !

Voici le fichier init.sh que nous utilisons :
#!/usr/bin/env bash
expectedIonicVersion="5.2."
expectedCordovaVersion="9.0."
 
echo "Config file password (ask your colleagues):"
read -s password
 
ionicVersion="$(ionic -v)"
 
if [[ "$ionicVersion" != "$expectedIonicVersion"* ]]; then
    echo "Wrong Ionic version, you need to install the $expectedIonicVersion"
    exit;
else
    echo Ionic version OK"
fi
 
cordovaVersion="$(cordova -v | cut -c-5)"
 
if [[ "$cordovaVersion" != "$expectedCordovaVersion"* ]]; then
    echo "Wrong Cordova version, you need to install the $expectedCordovaVersion"
    exit;
else
    echo "Cordova version OK"
fi
 
unzip -o -P "$password" config.zip > /dev/null
if [[ $? == 0 ]]; then
  echo "Successfully unzipped the .env file"
else
  echo "Wrong Password for env file"
  exit;
fi
 
echo "Now removing generated folders..."
 
rm -rf ./plugins/
echo "plugins folder removed"
 
rm -rf ./platforms/
echo "platforms folder removed"
 
rm -rf ./www/
echo "www folder removed"
 
npm i

Enfin, voici les commandes qu'on peut trouver dans notre package.json :
{
  "scripts": {
    "zip:config": "zip -o -re config.zip .config",
    "init": "./build-scripts/init.sh"
  }
}

A noter :

  • zip -o -re target origin permet de zipper un fichier de façon chiffrée en demandant un mot de passe
  • read -s password dans le fichier .sh permet de lire un texte saisi sans qu'il s'affiche à l'écran
  • unzip -o -P "$password" config.zip > /dev/null permet de décompresser et déchiffrer le fichier config.zip à l'aide du mot de passe récupéré préalablement
  • Si vous voulez gérer plusieurs fichiers de configuration, par exemple pour l'environnement dev & l'environnement de production, vous pouvez avoir un fichier dev.config.zip et prod.config.zip, et deux scripts d'initialisation, chaque zip ayant son propre mot de passe. Ainsi seules les personnes habilitées pourront dézipper le fichier prod.config.zip :)


En espérant que ça vous soit utile, bon dev à tous et toutes !

Merge request : la checklist

icon Tags de l'article : , ,

Septembre 10, 2019
Hello,

Comme vous le savez peut-être, en plus d'être développeur, je suis le Scrum Master de mon équipe.

Afin d'améliorer la qualité et la stabilité de notre branche de dev principale, j'ai rédigé une petite checklist à utiliser par les développeurs lors des merge requests.

Pourquoi ?
  • Les listes de choses à faire sont souvent oubliées par l'équipe au moment des merge requests. Parfois partiellement (ah mince je n'ai pas testé sur iPhone), et parfois complètement (ah mince, comme c'était un petit dev je pensais que ça n'avait pas d'intérêt de tester).
  • En effet, une checklist a un impact psychologique bien plus puissant qu'une simple liste de choses à faire. Car en cochant, on indique qu'on a, personnellement, bien réalisé la tâche indiquée.
  • Dans l'idéal cette checklist serait à remplacer par un process CI automatisé. Ou en tout cas les étapes automatisables (build, tests, etc.).
  • La checklist peut aussi faire office de code review cheat sheet.
  • La checklist permet d'avoir un historique des merges, et d'en tirer des tendances.
  • Attention cependant : il ne faut pas que la checklist devienne une trop grosse contrainte. Il faut que le rapport besoin de qualité / contraintes soit optimal. Eviter donc d'avoir une liste de 20 étapes de tests avec 20 cases à cocher. Ca servirait juste à gaver votre équipe.
  • Egalement, il faut que l'équipe accepte et valide cette checklist. Vous devez éviter d'imposer ce type de process de force auprès d'une équipe qui n'en voit pas l'intérêt. A vous de sensibiliser votre équipe sur l'importance de la stabilité de vos branches de développement, du temps que ça fera gagner sur le développement, du nombre de bugs que vous éviterez, du fait que ça protégera des builds qui finissent à 21h, etc.


Voici donc cette petite checklist que j'espère avoir suffisamment teasé (à noter que plein d'éléments dans la partie optionnelle ne concernent qu'Angular/Ionic) :

------------------------------- MERGE REQUEST CHECKLIST -------------------------------

Date :
Dev name :
Reviewer name :
Branch :

MANDATORY
☐ It's a product story ? If yes, the PO validated the dev and gave his/her GO.
☐ The target branch (master/dev) was merged into the current branch
☐ No code / lint warnings or errors
☐ It compiles, starts, and was tested on all target devices/browsers
☐ It builds for all target environments (dev, qual, prod)
☐ The CI pipeline is green
☐ The wiki documentation was updated
☐ If the environment changed (global packages, .env file, …), the CI was updated and the team was warned by email

OPTIONAL (code reminders)
☐ PRODUCT : Every complicated choices are explained somewhere (documentation, specifications, comments, etc.)
☐ CODE QUALITY : No //todo or useless logs
☐ CODE QUALITY : No refactoring planned for later
☐ CODE QUALITY : The code is easy to read and understand
☐ COMPONENTS : OnPush on each component
☐ COMPONENTS : Smart/dumb pattern used
☐ PERFORMANCES : takeUntil($onDestroy) pattern applied on subscriptions
☐ PERFORMANCES : the app is fluid to use, no performances drop
☐ ROUTING : the pages routing is consistent and hierarchical
☐ SCSS : Each component is responsible of its look (not its parents)
☐ SCSS : No size calculations in typescript (only CSS for sizes)
☐ TESTS : tested with 360/375/414/768px of width
☐ TESTS : Unit tests written / updated
☐ ACCESSIBILITY : It works with bigger fonts

------------------------------- /MERGE REQUEST CHECKLIST -------------------------------

En espérant que ça vous soit utile,

Bon dev à tous et toutes !

Exposition d'un jeu, AMOUR, au Numerik Games d'Yverdon

icon Tags de l'article : , , ,

Septembre 06, 2019
Hello tout le monde,

Une fois n'est pas coutume, j'avais envie de communiquer sur un évènement ultra cool auquel j'ai pu participer ce weekend : le Numerik Games d'Yverdon !

Il y a 2 mois j'ai participé à une Game Jam (un genre de hackathon sur le thème du jeu vidéo) à Neuchâtel : l'Epic Game Jam.
Avec un collègue et 2 connaissances à lui, on a développé (via Unity3D) un petit jeu de combat basé sur des formes.
Une sorte de Pierre Papier Ciseaux meets Smash Bros jouable à quatre.

L'idée était d'avoir 3 formes, carré, triangle & rond, et que ces formes correspondent aux boutons de la manette de Playstation. Chaque forme était destinée à absorber la suivante (le carré absorbe le triangle, le triangle absorbe le rond, et le rond absorbe le carré).
Le thème de l'Epic Game Jam était en effet que les jeux ne devaient pas contenir de notion de "mort". Et quoi de mieux qu'une fusion des joueurs pour symboliser ça ?
Nous avions donc nommé le jeu AMOUR.

Du coup quelques caractéristiques :
  • L'objectif du jeu était de créer un jeu de combat simple, auquel on peut jouer à 2, 3, 4 et où tout le monde peut s'amuser et gagner
  • Même vaincu, un joueur n'arrête pas de jouer. En effet, à 3/4 joueurs, si le joueur qui vous a absorbé se fait absorber à son tour, il vous "recrache" au passage !
  • L'idée des formes qui match avec les boutons des manettes de Playstation avait pour objectif de simplifier l'assimilation des contrôles.
  • Le jeu peut vite virer en chaos en sans nom, surtout à 4, mais il reste très fun et parfois un joueur gagne sans le vouloir en mode euh il s'est passé quoi ?
  • Le changement de forme a un cooldown de 2 secondes, rendant le joueur vulnérable (si on change en triangle pour manger un rond, un carré qui arrive dans notre dos peut nous avoir)
  • Le jeu est aussi fun en duel, car tout se joue à la forme choisie au moment du contact... du coup il faut anticiper, calculer, choisir si on fonce ou si on esquive, etc.

Voici le jeu à la fin :


Il faut maintenant savoir qu'un des prix de cette Epic Game Jam était que le jeu qui s'y prêterait le plus serait exposé au Numerik Games d'Yverdon, projeté sur un immeuble !

Et c'est notre jeu qui a été choisi au final !

Nous avons donc pu continuer de travailler sur le jeu (en retravaillant les couleurs et en faisant des tests) afin de le rendre le plus adapté à cet évènement.

Voici le résultat final :


Et enfin, projeté sur un batiment au Numerik Games :


C'était une véritable expérience, d'abord la Game Jam, puis tout ce qui a suivi, le rush pour finaliser le jeu, le modifier pour qu'il s'adapte au support, faire fonctionner les nouvelles manettes (à 18h les manettes ne marchaient pas... alors qu'à 21h le jeu devait être installé et jouable !).

J'espère avoir l'occasion de continuer sur ce jeu, mais aussi à travailler sur d'autres jeux. Je commence à me débrouiller pas mal sur Unity ;)

Encore un grand merci à David Javet et aux organisateurs de l'Epic Game Jam !

Je ferai sûrement un autre article à l'occasion pour parler un peu plus de tout ce qu'implique ce genre de projet, de ce qu'on a réussi, ce qui a été difficile, les pièges dans lesquels il faut éviter de tomber, etc.

A suivre,

Bon dev à tous et toutes !

(A noter que c'est article n'est pas sponsorisé, évidemment)

Ionic et développement mobile : bonnes pratiques tests/ux/performances

icon Tags de l'article : , , , ,

Aout 28, 2019
Hello,

Quelques petites astuces / bonnes pratiques en vrac pour celles et ceux qui se mettent à Ionic ou au développement mobile en général.

Déjà, voici comment je teste ce que je développe pour mobile (Ionic) :
  • Je teste avec Firefox en général, car c'est le navigateur le plus rigoureux (si ça marche sur Firefox, y'a 99,9% de chances que tout fonctionne nickel sur Chrome et Safari)
  • Je teste avec 3 largeurs d'écran : 360, 375 et 415. (360 = Galaxy S, 375 = iPhone 6+, 415 = grands smartphones 6")
  • Je teste avec 320px de large. Ca ne doit pas être parfait, mais ça doit être utilisable (pour les utilisateurs de vieux smartphones)
  • Je teste avec 600/700px de large. L'idée est que ce soit exploitable sur tablette / PC.
  • Je teste ensuite sur Chrome et Safari, pour être sûr
  • Je teste, enfin, sur iOS puis Android

Avec ce process de test, j'ai rarement de mauvaises surprises. En développant via Chrome j'ai eu plusieurs soucis sur iPhone. En développant sur Safari j'ai eu des soucis sur Android. En commençant par Firefox, j'ai un truc ultra stable.

Et pourquoi ces tests de tailles ? Ben déjà ça apprend à http://liens.howtommy.net/?txBHZw">maitriser le flex et les alignements.
Ensuite ça permet d'avoir un truc qui pourrait, sur un coup de tête, être sorti sur tablette ou en build web.
Enfin, du CSS bien conçu fonctionnera dans toutes ces configurations... et du CSS bien conçu diminue les risques que notre application soit inexploitable sur un vieux smartphone ou un smartphone pourri.

Quelques astuces CSS/UX :
  • Toujours les tailles en REM, on évite les pixels.
  • Eviter les éléments clickables de moins de 40px de haut
  • Un article ultra clair sur comment bien créer un formulaire pour application mobile
  • Chaque élément avec lequel on peut interagir doit le montrer via son style (encadré, souligné, bordure, ombre, ...)
  • Pensez à checker d'autres applications et regardez comment elles font les choses.
  • Jamais de listes déroulantes, on est sur mobile !
  • Privilégiez une UI simple réutilisant les composants tout faits d'Ionic (qui sont pensés pour iPhone et Android) plutôt que de faire du maison qui va déstabiliser et gêner ... tout le monde.
  • Pas de couches de popups ! Si vous avez besoin de faire ça, privilégiez plutôt un formulaire en étapes.
  • Pensez navigation de gauche à droite (j'avance vers la droite, je reviens en arrière vers la gauche). Surtout avec les derniers iphones qui permettent le retour via un scroll à gauche.
  • Si vous avez un doute sur une façon originale de faire quelque chose (par exemple le bouton retour en haut à droite), essayez de trouver une application utilisée en masse qui fait ça. Si vous n'en trouvez pas, c'est que c'est probablement une mauvaise idée.
  • Pensez mobile et utilisation du pouce ! On ne met pas d'onglets en haut. On ne met pas de burger menu en bas à gauche. On évite d'avoir 6+ onglets. Scroller est naturel sur mobile, mais évitez les scroll verticaux/horizontaux mélangés. etc.
  • Réfléchissez aux cas d'utilisations critiques de votre application. Designez votre application autour d'eux. Ajoutez ensuite les options pour les scénarios moins critiques, sans rendre l'application trop compliquée.
  • Si vous avez des doutes, checkez les composants ionic et autres projets de démo Ionic. Vous tomberez probablement sur un exemple pertinent pour votre besoin.
  • Attention au routing en Ionic 4+. Pensez le comme un routing Web. Exemple : j'ai un onglet Settings, dedans j'ai une option mon profil. Le lien de la page profil doit être tabs/tab-settings/my-profile et pas /my-profile.
  • Privilégiez les modales quand vous êtes sur une action où le routing n'aurait aucun sens (exemple : la modification de votre nom/prénom dans l'application, vous ne feriez pas un routing pour ça, si la personne refresh, elle est censée retomber sur son profil, pas sur une "page" de modification de son profil)
  • Chaque composant doit gérer ce à quoi il ressemblera. Le parent ne doit gérer que le positionnement. Si le parent veut donner un rendu différent (exceptionnellement), on ajoute une classe sur le composant et dans ce composant, on exploite cette classe pour appliquer un CSS différent.
  • Pensez à tester votre application avec les polices augmentées sur un smartphone (par défaut c'est 16px, mais si la personne règle 20px dans son Android, votre application sera-t-elle toujours utilisable ?)

Enfin niveau perfs :
  • Evitez les trucs "réutilisables/bidouilles" pour "gagner du temps". Genre les composants transverses qui gèrent le ion-header et ion-content avec le titre et des booléens pour les boutons à afficher. Ca ruine les perfs en ionic 4.
  • ChangeDetection: Onpush sur TOUS vos composants. Si vous avez besoin de dynamisme : Observables et | Async.
  • Pendant vos tests n'hésitez pas à mettre un console log sur vos actions de services et composants en bas des grappes. Si vous voyez que les appels se font par 2, 5, 10 ou 100, vous avez un souci de perfs potentiel.
  • N'oubliez pas qu'Ionic c'est du HTML/JS embarqué et affiché via une webview. Evitez tout ce qui peut être facultatif mais qui boufferait les performances (par exemple charger une carte par défaut alors que 90% de vos utilisateurs se fichent de la carte)
  • On évite les calculs de tailles / rendu / affichage en typescript, il faut essayer de tout faire en CSS
  • takeUntil($onDestroy) pattern sur les souscriptions, on ne doit pas avoir d'accumulation de souscriptions à l'infini.
  • Si vous voulez gérer l'accessibilité (couleurs, tailles, etc.) ajoutez une classe CSS à la racine de votre application, et dans vos composants (ou dans un CSS spécifique) overridez l'affichage de base en CSS si la classe CSS est présente à la racine (évitez d'appeler un service dans chaque composant).
  • Le pattern Smart vs Dumb reste une référence : https://medium.com/@jtomaszewski/how-to-write-good-composable-and-pure-components-in-angular-2-1756945c0f5b
  • Même dans une page qui ne contient presque rien, on met un container qui contiendra un composant. (réutilisabilité / performances / sécurité)
  • Attention à ngrx. C'est génial, mais il ne faut pas en abuser. C'est là pour gérer des états au niveau de l'application. Evitez de tout faire avec, sinon non seulement vous allez compliquer à mort la relecture du code, mais en plus vous perdrez un temps monstre à développer (car à chaque refresh vous perdez tout votre state).

Enfin je finirais cette note sur le point le plus important pour moi : Pensez votre code pour qu'il soit ultra simple à relire, reprendre et débugguer par un autre développeur.
Si c'est trop compliqué, y'a un souci.
Si vous êtes obligé de commenter le code, y'a un souci.
Si vous découpez des trucs en des tas de morceaux interdépendants, y'a un souci.
Si un dev qui doit reprendre votre code est obligé de venir vous voir pour comprendre le code, y'a un souci.

Egalement, n'oubliez pas une chose : vous n'aurez pas deux chances de faire une bonne première impression.
Si vous sortez une application à la va vite, sans faire de tests utilisateurs, avec une mauvaise UX, votre application va accumuler les notes négatives et ce sera la merde direct.

Privilégiez : Fluidité = UX > UI > masse fonctionnalités.
Les applications mobiles doivent surtout séduire et être agréables à utiliser. Mais si vous accumulez trop de fonctionnalités et que votre application devient lente/inutilisable/moche, vous perdrez bien plus d'utilisateurs que si vous aviez laissé moins de features mais plus d'UX/fluidité. Largement.

Un dernier ultime rappel : une bonne UX c'est comme une blague. Si vous devez l'expliquer, c'est qu'elle n'est pas bonne.
Donc si votre application a besoin d'un tutoriel de 20 pages pour être utilisée... posez vous des questions.

Bon dev à toutes/tous !

Journée de travail, ou journée au travail ?

icon Tags de l'article :

Février 25, 2019
It's story time !

Je vais vous raconter une petite histoire, en écho à cet article qui explique qu'un employé anglais ne travaille en moyenne que 2h53 par jour.

Contexte : j'étais dans mon entreprise depuis 3 ans. Chef de projet technique, je manageais une équipe de 4 développeurs sur le même projet depuis 2 ans et demi.
Nous travaillions en SCRUM, et notre vélocité oscillait entre 37 points et 42 points par sprint. Elle était donc assez stable.
Mon équipe travaillait, elle, 8h par jour. De 9h30 à 12h, et de 13h à 18h30.
Nous partagions un grand openspace avec 2 autres équipes. Openspace d'environ 60/70m² où nous étions 16/18 selon les jours (autant vous dire qu'on se marchait dessus, et qu'on avait très souvent le casque sur les oreilles).

Sauf que voilà, un jour, on a dû changer de locaux. Comme on était sur un projet un peu à part, et comme la place manquait, on s'est retrouvés dans un bâtiment à l'écart.
Mais on était bien mieux ! Openspace de 70/80m² juste pour notre équipe de 8 (avec le nouveau manager et la personne du marketing qui nous ont rejoint).
Une salle de réunion rien qu'à nous (elle n'était pas sur le système info, et on la réservait avec des post-its).
Et la salle CE à côté de nous, avec la Playstation 4 en libre accès.

Autant vous dire qu'on a pris nos aises.
L'équipe a commencé à faire de plus grosses pauses le midi. A aller jouer à Fifa entre midi et deux. A retourner jour à la console à 18h00 pile.
On a même commencé à jouer au ping pong dans la salle de réunion le midi (vive le filet Artengo). On a même parfois fait du Minecraft, du Don't Starve Together ou du Smash Bros sur la TV de la salle de réunion !
Du coup, l'équipe ne travaillait plus 8h par jour, mais 6h30 (9h30/12h, 14h/18h).

Là vous vous dites : mais merde, tu permettais ça ? Et personne n'est venu gueuler ?
Bien évidemment que si...

Un des directeurs, qu'on n'avait plus vu depuis des mois (un manager intermédiaire avait été installé) est venu nous faire un grand discours sur l'investissement et les horaires (déguisé, comme souvent, en : "je compte sur vous pour partir une fois le travail fait, pas quand la cloche sonne").
On se prenait régulièrement des piques, des remarques, des commentaires d'autres personnes.
Mais le nouveau manager du projet (qui commençait à en avoir marre de notre rythme "pépère" de travail) ne pouvait pas faire grand chose, car nous ne dépendions pas de lui, mais de la direction technique de la société.

Du coup, de mon côté, je laissais faire. Je voulais voir les résultats en chiffres. Voir l'évolution de notre vélocité.
Et puis l'ambiance était largement meilleure. Ne pas être les uns sur les autres. Pouvoir faire de vraies pauses le midi, prendre notre temps...
Egalement, j'avais remarqué que si l'équipe faisait de grosses pauses, en général ils compensaient en bossant à fond une fois la pause terminée.

Du coup, j'ai laissé faire. J'ai observé.

Jusqu'au jour où ma manager a demandé à me voir.
Elle m'a expliqué qu'on renvoyait une mauvaise image, que l'équipe clairement bossait moins qu'avant et qu'ils pouvaient faire bien plus, s'investir plus... etc.
Et là, j'ai sorti les chiffres. En travaillant 1h30 de moins par jour, la vélocité de l'équipe n'avait pas bougé. Elle oscillait toujours entre 37 et 42 points par sprint.
L'équipe était tout aussi productive en travaillant 6h30 par jour, qu'en travaillant 8h par jour !

L'équipe avait même tendance à culpabiliser de prendre de telles pauses, et compensait en travaillant parfois plus longtemps ou plus intensément.

Alors oui, on se dit toujours : si l'équipe arrive à faire tout ça en 6h30, ils peuvent faire plus en 8h.
Ben la preuve que non. L'équipe travaillait en 8h avant, et elle ne produisait pas plus.

Ma manager a finit par accepter mes arguments. Evidemment avec le "si la vélocité baisse, il faudra changer ça".
Mais on a pu continuer l'expérience. Et les résultats étaient toujours là.

D'ailleurs, 1 an plus tard, on est repassés dans un openspace partagé avec d'autres équipes. A l'étage de la direction.
Du coup l'équipe est repassée sur un format de journées de 8h.

Et bien vous savez quoi ? La vélocité n'a, à nouveau, pas bougé.. Toujours 37/42 points.
En repassant à 8h par jour, l'équipe produisait autant qu'avant, en travaillant 6h30 par jour !

Voilà pourquoi je pense vraiment que le cadre, le confort, les règles, les méthodos de travail... sont plus importantes que la durée de travail.
Un cadre sain, assez d'espace pour chacun, un focus sur la qualité, des méthodos carrées et strictes (vrai SCRUM = AC, DoR et DoD) et des petites journées... seront, pour moi, plus efficaces sur le rendement long terme. Bien plus que de forcer l'équipe à travailler 45/50h par semaine.
Car qui dit pression et mauvais cadre de travail, dit turn-over, fatigue, lassitude, maladies, ... et bugs ! Je vous rappelle qu'au delà de 40h par semaine, on produit plus de bugs que de code utile !

On a trop souvent tendance à se concentrer sur les heures. Sur le rendement par heure. Sur le temps passé derrière un écran (quitte à somnoler en mode "il me reste 1 heure à tirer"...) ou dans les locaux (coucou les gens qui font des pauses aux toilettes !).

Après tout, l'objectif d'une entreprise ne devrait-il pas être de créer un environnement de travail tellement cool que les employés se voient y rester jusqu'à leur retraite ?

A réfléchir.

Configurer son Mac pour retrouver tous ses raccourcis clavier Windows

icon Tags de l'article : ,

Janvier 10, 2019
Lorsqu'on arrive sur un Mac, après des années de développement Windows... C'est compliqué.

Tout change. Surtout les raccourcis clavier.

Quelques exemples :
CTRL + gauche = ALT + gauche.
CTRL + C = COMMAND + C
CTRL + Y = COMMAND + MAJ + Z

C'est véritablement... un enfer.

Après, si vous passez définitivement sur Mac, c'est l'histoire d'un mois, le temps de vraiment s'habituer.

Le problème c'est que si, comme moi... vous utilisez un Windows chez vous.

En retournant sur mon Windows, j'ai tendance à utiliser régulièrement des raccourcis Mac. Et ça ne marche pas. Dans le pire des cas ? Ca me fait perdre du temps et/ou du contenu.

Du coup, je me bats depuis 2 mois à essayer de concilier mes habitudes Windows et Mac. Ca marche à peu près, mais il y a toujours des dérapages...

Fort heureusement, je viens de découvrir un outil génial qui va me permettre, ENFIN, d'utiliser exactement les mêmes entre Windows et Mac : BetterTouchTool
Il est payant (7.5$ pour 2 ans) mais c'est carrément worth. Ca change juste la vie.
En combinant ce logiciel et les réglages de l'OS et de l'IDE, on peut arriver à un résultat juste parfait : un clavier fonctionnant comme sur Windows, mais sur Mac.

Du coup, voici tous mes réglages :

System Preferences => Keyboard => Input Sources => Ajouter un clavier French - PC
J'utilise un clavier externe AZERTY pour PC, j'ai donc configuré mon Mac pour qu'il utilise le même mapping.

System Preferences => Keyboard => Modifier Keys => Inverser Command et Control.
Ne voulant pas trop galérer sur toutes les commandes de base (copier/coller/annuler/refaire/etc.) j'ai inversé Command et Control.

Dans l'IDE : binder la complétion de code sur ALT + Espace
MacOS bloque le raccourci COMMAND + ESPACE par défaut, réservé à son spotlight... Il faut donc ruser.

Ensuite il faut installer le logiciel BetterTouchTool et le configurer comme suit :



Quelques explications :
  • On force les boutons début et fin (pas gérés par défaut par l'IDE...) à envoyer les commandes CONTROL + A et CONTROL + E (qui servent au début de ligne/fin de ligne sur Mac)
  • On remplace les CTRL + début et CTRL + fin par COMMAND + Haut et COMMAND + Bas (début de fichier et fin de fichier sur Mac)
  • On gère aussi ces boutons avec la touche Shift enfoncée
  • On force notre CTRL + Gauche à envoyer ALT + Gauche. Pareil pour la droite, et pareil quand SHIFT est enfoncé. Ainsi on peut naviguer dans le texte comme sur un Windows.
  • On remplace le CTRL + Y par un CTRL + MAJ + Z (redo sur Mac).
  • Enfin, je supprime l'action du CTRL + Q (quitter l'application), car le moindre fail sur un CTRL + A quitte l'application, sans confirmation

Maintenant je configure ma souris :



Quelques explications :
  • L'appui sur les boutons précédent et suivant feront l'équivalent d'un swipe gauche/droite à trois doigts (oui, Mac gère nativement un swipe à trois doigts, mais pas le bouton précédent d'une souris)
  • Je supprime le scroll gauche et droite de la souris (rien de pire quand on veut faire un clic molette)

Ensuite, dans les réglages clavier de mon IDE, je remplace le COMMAND + ESPACE par ALT + ESPACE (pour appeler l'autocomplétion façon Windows) :



Et voilà !

Après, si comme moi vous utilisez VS Code, il reste 2/3 modifications à faire. A savoir que VS Code va disjoncter avec ces raccourcis, surtout avec certains raccourcis.

Voici donc ma configuration VSCode :
{
    "key": "alt+space",
    "command": "editor.action.triggerSuggest",
    "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly"
},
{
    "key": "ctrl+space",
    "command": "-editor.action.triggerSuggest",
    "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly"
}
{
    "key": "ctrl+shift+a",
    "command": "cursorHomeSelect",
    "when": "textInputFocus"
},
{
    "key": "shift+home",
    "command": "-cursorHomeSelect",
    "when": "textInputFocus"
},
{
    "key": "ctrl+shift+e",
    "command": "cursorEndSelect",
    "when": "textInputFocus"
},
{
    "key": "shift+end",
    "command": "-cursorEndSelect",
    "when": "textInputFocus"
},
{
    "key": "shift+cmd+left",
    "command": "-cursorHomeSelect",
    "when": "textInputFocus"
},
{
    "key": "shift+cmd+up",
    "command": "-workbench.action.terminal.selectToPreviousCommand",
    "when": "terminalFocus"
},
{
    "key": "shift+cmd+down",
    "command": "-workbench.action.terminal.selectToNextCommand",
    "when": "terminalFocus"
}

Explications :
  • Je supprime les raccourcis clavier en double qui font que le MAJ + CMD + Bas/Haut ne marcheront pas
  • J'active l'autocomplétion sur ALT + Espace
  • Je supprime l'action sur le COMMAND + Espace (qui de toute façon ne marchera pas)
  • Je remplace la gestion de début/fin avec les bons réglages.

Et voilà !

Grâce à tout ça, j'ai maintenant un Mac qui fonctionne exactement comme un Windows au niveau du clavier et de la souris ! Un véritable bonheur quand on doit passer de l'un à l'autre !