Une histoire de l'Open Source chez Lectra

Category: 
March 14, 2019


Il y a quelque temps maintenant, Lectra a décidé de faire le pas vers le monde de l'Open Source.
Après tout, pourquoi pas ?
Nous utilisons ici des centaines de milliers de lignes de code venant du monde Open Source, pourquoi ne pas contribuer et rendre un peu de ce que nous apporte le monde de l'Open Source?


Pourquoi faire de l'Open Source pour une entreprise?



Cela peut sembler contre-productif pour une entreprise de faire de l'Open Source.
Pourquoi payer des développeurs pour créer la meilleure application possible pour ensuite mettre le code produit à disposition de tout le monde et en particulier des concurrents ?
La réponse est simple : cela peut rapporter à votre entreprise.
Nous pouvons citer Tom Preston-Werner, co-fondateur de GitHub sur le sujet.

If you do it right, open sourcing code is great advertising for you and your company.

 
Pourquoi ?

  • Cela donne une énorme visibilité (gratuite) pour l'entreprise. Vos futurs clients seront rassurés de voir que vous avez les talents nécessaires au sein de vos équipes de dev pour publier du code open source.
  • Cela permet d'attirer les meilleurs développeurs. A chaque fois qu'un développeur ouvre votre code, le critique, le commente, vous avez gagné !
  • Cela permet également de faire avancer votre code, vos développeurs et donc l'entreprise.
  • Cela permet de retenir les talents. Publier du code sur GitHub c'est la meilleure publicité possible pour un développeur et c'est un paradoxe, mais c'est le meilleur moyen pour le retenir ! En effet cela rendra le développeur plus "bankable" pour les autres compagnies, et c'est exactement ce genre de développeur dont vous avez besoin. De toute façon si vous ne le faites pas, ces développeurs feront de l'Open Source chez quelqu'un d'autre...
  • Si vos projets deviennent populaires vous allez avoir des pull request, des personnes vont abonder votre projet avec des idées, des bonnes idées et en plus ils vont faire le code pour vous. Votre code, votre projet va devenir meilleur. Bon il y aura aussi des mauvaises idées et du mauvais code, mais cela reste votre responsabilité de faire la part des choses.

Néanmoins, il ne faut pas tout mettre en Open Source.
Vous devez toujours vous poser la question, qu'est ce qui doit être publié et qu'est ce qui ne doit pas l'être.
Tout ce qui représente votre cœur de métier, vos algorithmes, tout ce qui fait la valeur de votre service ne doit, bien évidemment, pas se retrouver en Open Source.

 

Comment faire de l'Open Source ?


Ok. Tout cela étant dit, par quoi commencer ?
Cela semble évident, mais pour se lancer il faut trouver un projet éligible.
Dans le processus de mise en place de l'Open Source chez Lectra, on s'est tout d'abord posé la question de quoi publier.


Le bon projet

Pour un premier projet publié sur GitHub, nous voulions commencer par quelque chose d'assez simple.
Il faut aussi quelque chose qui n'existe pas encore et qui soit un besoin pour l'entreprise.
Chez Lectra nous utilisons depuis peu les services de Launch Darkly pour servir notre feature flipping.
Nous avons remarqué que, à l'époque (ce n'est plus le cas maintenant), il y avait bien un SDK javascript pour intégrer le service de Launch Darkly, mais rien de spécifique pour React que nous utilisons sur nos interfaces Front.
Nous avions trouvé notre candidat idéal !
Un composant React assez simple pour commencer et quelque chose qui peut apporter à la communauté. En plus c'est de toute façon un composant que nous devions faire pour nos projets Lectra.


Notre projet chez Lectra


Le feature flipping

Lectra utilise les services de Launch Darkly pour assurer le feature flipping sur nos applications Cloud.
Le feature flipping sert principalement à nos developpeurs à mettre en production le plus rapidement possible du code même si la fonctionnalité n'est pas encore disponible. Il sert aussi à faire du A/B testing, ouvrir des fonctionnalités en avance à certains de nos clients, etc...
L'utilisation du feature flipping apporte une grande souplesse pour la gestion de nos développements, mais à grand pouvoir, grandes responsabilités.
On peut voir le feature flipping comme un gros "if" dans notre code et si on multiplie les conditions, on peut rapidement aboutir à un code inmaintenable et difficilement testable.


Approche avec React

L'idée du composant du feature flipping est donc de proposer un HOC (Higher-Order Component) afin de ne pas avoir à mettre les conditions partout dans notre code front.
Nous avons fait le choix de proposer deux composants. Un "Provider" qui va fournir les informations nécessaires à Launch Darkly pour instancier celui-ci et un "Consumer" qui va nous permettre d'utiliser les flags de feature flipping définis dans Launch Darkly.
Et pour faire le lien entre tout cela, nous utilisons l'API de contexte disponible dans React depuis la version 16.3.0.
Nous commençons donc par positionner le contexte via l'API de React.

import React from "react";
export const FlagsContext = React.createContext();


Nous créons ensuite un composant Provider.
Dans celui-ci il faut une phase d'initialisation du client Launch Darkly.
Pour cela nous avons instancié le client dans la phase de montage componentDidMount du composant React.

async componentDidMount() {
    const { clientkey, user, bootstrap, onFlagsChange } = this.props;
    this.ldClient = await Client(clientkey, user, bootstrap);
    await this.LDReadyEvent(this.ldClient);

    if (onFlagsChange) {
      this.ldClient.on('change', changes => {
        onFlagsChange(changes);
      });
    }

    this.setState({
      isFlagsLoaded: true
    });
  }

Notez que nous avons besoin ici que la méthode du cycle de vie du composant React soit asynchrone, ce qui nous permet de faire un await sur l'initialisation du client et d'attendre l'évènement ready de Launch Darkly.
Une fois cela fait, l'état du composant est mis à jours, isFlagsLoaded est passé à true, cela signifie que notre composant est prêt à charger le reste de l'application.
Dans le render du composant nous avons le code suivant :

render() {
    const { children, loadingComponent } = this.props;
    return this.state.isFlagsLoaded ? (
      <FlagsContext.Provider value={this.ldClient}>
        {children}
      </FlagsContext.Provider>
    ) : (
      loadingComponent
    );
  }

Tant que isFlagsLoaded est à false, un composant de loading est rendu. Dès que le client de Launch Darkly est instancié isFlagsLoaded est passé à true et le children de notre Provider est alors rendu.
De plus nous passons le client Launch Darkly dans le contexte afin de pouvoir l'utiliser par la suite dans toute notre application.
Au final cela nous donne le code suivant :

import React, { Component } from "react";
import PropTypes from "prop-types";
import { initialize as Client } from "ldclient-js";

import { FlagsContext } from "./FlagsContext";

export default class FlagsProvider extends Component {
  static propTypes = {
    children: PropTypes.any,
    user: PropTypes.shape({
      key: PropTypes.string
    }).isRequired,
    clientkey: PropTypes.string.isRequired,
    bootstrap: PropTypes.object,
    onFlagsChange: PropTypes.func,
    loadingComponent: PropTypes.element
  };

  static defaultProps = {
    bootstrap: {},
    onFlagsChange: undefined,
    loadingComponent: <div />
  };

  constructor() {
    super();
    this.ldClient = null;
    this.state = {
      isFlagsLoaded: false
    };
  }

  async componentDidMount() {
    const { clientkey, user, bootstrap, onFlagsChange } = this.props;
    this.ldClient = await Client(clientkey, user, bootstrap);
    await this.LDReadyEvent(this.ldClient);

    if (onFlagsChange) {
      this.ldClient.on("change", changes => {
        onFlagsChange(changes);
      });
    }

    this.setState({
      isFlagsLoaded: true
    });
  }

  LDReadyEvent(ldclient) {
    return new Promise(resolve => {
      this.ldClient.on("ready", () => {
        resolve();
      });
    });
  }

  render() {
    const { children, loadingComponent } = this.props;
    return this.state.isFlagsLoaded ? (
      <FlagsContext.Provider value={this.ldClient}>
        {children}
      </FlagsContext.Provider>
    ) : (
      loadingComponent
    );
  }
}


L'utilisation du Provider se fait de la manière suivante :

import { FlagsProvider } from "ld-react-feature-flags";

ReactDOM.render(
  <FlagsProvider
    user={user}
    clientkey="myClientKey"
    loadingComponent={<div>please wait</div>}
  >
    <App />
  </FlagsProvider>,
  document.getElementById("root")
);

Notez que pour instancier le client Launch Darkly il faut lui passer une clientkey et des informations concernant le user connecté à l'application.
Ceci étant fait, il nous faut maintenant un moyen de récupérer et utiliser ce qui a été instancié dans le Provider.
Pour cela nous allons créer un nouveau composant Consumer.
Pour notre projet nous avons créé deux consumers. Un en mode Render Props et un autre en mode HOC.
C'est à l'appréciation de chacun d'utiliser le consumer comme il le souhaite.


Si je prends le code du consumer en mode Render Props, voilà ce que cela donne pour le render :

render() {
    return (
      <FlagsContext.Consumer>
        {ldClient => {
          const flagValue = ldClient.variation(this.props.flag, false);
          const featureProps = {
            [camelize(this.props.flag)]: flagValue
          };
          return (() => {
            if (flagValue === true) {
              return this.props.renderOn(featureProps) || this.props.children;
            }
            if (flagValue === false) {
              return this.props.fallbackRender(featureProps) || null;
            }
            if (typeof flagValue === 'string') {
              return this.props.renderOn(featureProps) || this.props.children;
            }
            return null;
          })();
        }}
      </FlagsContext.Consumer>
    );
  }



FlagsContext.Consumer vient nous donner le client Launch Darkly initialisé dans le Provider.

Nous le passons ensuite au composant enfant ou via la props renderOn.
Si le flag est true, nous allons rendre le composant passé dans le renderOn (ou en children), si le flag est false, nous allons rendre le composant passé dans le fallbackRender.
Chez Launch Darkly, il est aussi possible d'avoir des flags multivariants, c'est-à-dire qu'ils vont pouvoir prendre des valeurs différentes suivant l'utilisateur connecté par exemple.
Pour ces flags qui ne sont pas des booléens nous passons juste les flags au composant passé en renderOn (ou children).
Charge après au composant enfant de gérer son comportement dans ce cas.
Finalement l'utilisation du consumer en mode Render Props se fait comme suit :

import { Flags } from "ld-react-feature-flags";

<Flags
  flag="beta-only"
  renderOn={flag => <h4>for beta users</h4>}
  fallbackRender={flag => <h4>for regular users</h4>}
/>;

Et voila !
On voit bien ici que les "if" du feature flipping sont centralisés dans le composant consumer.
Il sera par la suite bien plus facile de maintenir nos projets. Il suffira d'enlever les consumer le jour où l'on voudra nettoyer notre application du feature flipping.

 

Et maintenant ? On déploie sur GitHub ?


Bon. Nous avons maintenant un ensemble de composants React qui encapsulent le service de Launch Darkly.
Ils fonctionnent, nous les utilisons déjà sur certaines de nos applications.
Mais est-ce suffisant pour publier sur GitHub ?
Evidemment non.
Pour publier sur GitHub nous avons encore un certain nombre de pré requis manquants.

Les pré requis

  • La licence

Vous n'êtes pas obligé de choisir une licence. Cependant, sans licence, les lois sur le droit d'auteur par défaut s'appliquent, ce qui signifie que vous conservez tous les droits sur votre code source et que personne ne peut reproduire, distribuer ou créer des œuvres dérivées à partir de votre travail. Ce n'est évidement pas ce que nous souhaitons pour notre projet.

Pour bien choisir votre licence, il faut déjà savoir de quoi on parle. Le site Choose a licence peut vous aider à faire votre choix en fonction de ce qui est permis par une licence, les conditions et les limitations.
Chez Lectra pour les projets javascript nous avons choisi la licence MIT. Elle est peu contraignante et simple. Cela nous suffit largement.
La licence sera dans votre repo sous le fichier LICENCE.md.

  • Les tests

Un bon code est un code testé. Et cela est particulièrement vrai sur GitHub, car vous allez recevoir du code d'autres développeurs, et la meilleure façon de ne pas avoir de régression est d'avoir une bonne couverture de test.

Cela ne veut pas dire forcement 100% de couverture, mais il faut vous assurer que les parties "sensibles" de votre code soient bien testées.
Nous utilisons Jest pour tester unitairement nos composants React. Les tests sont bien sûr inclus sur notre repository et sont joués avant chaque deploiement sur GitHub et NPM.

  • Packager notre composant

Pour pouvoir utiliser notre composant via NPM il faut le packager. Il y a différents moyens pour packager une librairie NPM.Nous pouvons utiliser soit WebPack soit des solutions dédiées au packaging comme Rollup.js ou encore microBundle

Pour notre cas nous avons choisi d'utiliser Rollup.js.
C'est une solution simple, efficace et moderne qui utilise les standards ES6 pour créer des modules javascript.

  • Une documentation

Pour que tout le monde puisse profiter de vos composants ou librairies, il faut expliquer comment les utiliser. Pour cela la convention est d'écrire un fichier README pour un projet GitHub. Un fichier README est un fichier texte qui présente et explique un projet. Il contient des informations qui sont généralement nécessaires pour comprendre en quoi consiste le projet.
Une bonne ressource pour savoir comment rédiger un bon ReadMe est disponible sur le site Make a README.

  • Des exemples

Un README pour expliquer comment fonctionne votre projet c'est bien, des exemples concrets de code c'est mieux ! Pour cela il y a plusieurs façons de procéder. Vous pouvez créer un petit projet d'exemple d'utilisation de votre code dans votre repo, dans un repertoire example. Cela peut être une petite application web qui montre concretement comment consommer votre librairie. Vous pouvez aussi utiliser les éditeurs de code online et faire un lien dans votre README vers ces éditeurs. Vous pouvez utiliser Code Sandbox, CodePen ou bien encore PlayCode.

  • Les autres fichiers

Vous pouvez ajouter un ensemble de fichiers supplémentaires pour expliquer comment se comporter par rapport à votre repo.
Comment contribuer au code ? Vous pouvez ajouter un fichier CONTRIBUTING.md.
Un code de conduite sur le repo ? Vous pouvez ajouter un fichier CODE_OF_CONDUCT.md.
Avoir un historique des changements sur votre librairie ? Vous pouvez ajouter un fichier CHANGELOG.md. Un bon site pour vous expliquer comment gérer un CHANGELOG : keep a changelog.

Le bon outillage

Maintenant il faut déployer tout cela sur GitHub (c'est quand même le but).
Vous pouvez bien évidement tout simplement faire un push sur votre repo.
Mais il faut aussi publier sur npm dans le cas des packages NPM.
Pour cela c'est facile un npm publish peut également suffire.
Chez Lectra nous avons fait le choix d'automatiser les déploiements via Travis CI. Le code est testé avant d'être déployé et le déploiement se fait à la fois sur GitHub et NPM
Pour le déploiement sur NPM, vous pouvez avoir besoin d'un fichier .npmignore.
En effet, ce qui est sur votre repo n'est pas forcément un miroir de ce que vous avez besoin d'envoyer sur NPM.
Avoir vos tests sur NPM n'est pas nécessaire, de même qu'un répertoire contenant une application d'exemple d'utilisation de votre libraire.
Il faut donc définir dans ce fichier tout ce qui ne doit pas se retrouver dans un package NPM.
C'est important pour sauver un peu de bande passante pour NPM.

Le résultat


Le résultat c'est notre première contribution sur GitHub à l'adresse suivante : ld-react-feature-flag.
Depuis la publication de ce repo, Launch Darkly a publié son propre composant pour React : https://docs.launchdarkly.com/docs/react-sdk-reference.
C'est intéressant de voir que, dans les grandes lignes, Launch Darkly a choisi quasiment les mêmes solutions techniques pour leur SDK React que nous.
Launch Darkly a aussi eu la gentillesse de référencer notre projet sur leur repo Awesome LaunchDarkly.

Authors


Merci à Romain DSO pour avoir travaillé avec moi sur ce projet et à sylvainchagnaud pour le déploiement via Travis CI ;-).
©️ Lectra