Blog

Optimisation des performances Ionic

ionic optimisation

Ionic est un framework open source permettant de développer des applications mobiles hybrides. Pour ce faire, il utilise des technologies web comme HTML, CSS et JavaScript. Il a été lancé en 2013 et a beaucoup évolué depuis.

Evolutions

En 2013, lors de sa sortie, Ionic était fortement lié à AngularJS pour l’interface graphique et à Cordova pour l’accès aux fonctionnalités du téléphone. Depuis, Ionic permet l’utilisation de React, Vue.js et Angular dans leurs dernières versions. Il a également introduit Capacitor pour résoudre les problématiques liées à Cordova.
Avec ses dernières versions, Ionic offre un rendu plus proche des applications natives de chaque plateforme et prend en compte les dernières directives et normes AAA pour l’accessibilité.

Performance

Ionic repose essentiellement sur les WebView (WebView pour Android et WKWebView pour iOS). Par conséquent, les performances de l’application dépendent fortement de celles des WebView, qui ne figurent pas parmi les éléments les plus performants des appareils mobiles.
Les manipulations intensives du DOM peuvent provoquer d’importants ralentissements, notamment sur les appareils d’entrée de gamme.
Une surutilisation des appels aux plugins natifs (via Capacitor) ou une mauvaise configuration de ces derniers peut accroître le temps de réponse et générer de la latence, en raison des interactions entre la couche native et le JavaScript de la WebView.
De plus, l’utilisation de ressources trop volumineuses, telle que des images ou des fichiers CSS/JS, peut également ralentir l’application.

Optimisations

Pour remédier aux problèmes de performance potentiels qu’une application Ionic peut rencontrer, notamment dans le cas d’applications complexes, plusieurs pistes peuvent être envisagées.

Optimisation des ressources

Si votre application est relativement volumineuse, il est recommandé d’envisager l’utilisation du lazy loading pour charger les modules et composants uniquement lorsque cela est nécessaire.

En Angular, il faut utiliser « loadChildren » dans les routes :

export const routes: Routes = [
    {
        path: '',
        component: AppComponent
    },
    {
        path: 'something',
        loadChildren: () => import('./pages/lazy/lazy.routes').then(m => m.routes)
    }
]

Pour la gestion des médias, il est conseillé d’utiliser le lazy loading et de compresser les images avec des formats tels que SVG ou WebP. Il est également souhaitable de limiter le chargement des vidéos et audios sur les écrans inactifs.

Pour la gestion des sources, il est important d’activer la minification des fichiers afin de transmettre des ressources plus légères.

Optimisation de l’interface utilisateur

Lors de l’affichage de longues listes de données, il est recommandé d’utiliser ion-virtual-scroll. Cet élément permet en effet d’afficher de manière optimisée une grande liste en ne rendant visible que les éléments affichés dans la fenêtre de visualisation.

<ion-virtual-scroll [items]="items" approxItemHeight="50px">
  <ion-item *virtualItem="let item">
    <ion-label>
      <h2>{{ item.name }}</h2>
      <p>ID: {{ item.id }}</p>
    </ion-label>
  </ion-item>
</ion-virtual-scroll>

Vous pouvez combiner ion-virtual-scroll avec ion-infinite-scroll pour charger progressivement davantage de données à mesure que l’utilisateur atteint le bas de la liste.

<ion-content>
  <!-- Liste avec défilement virtuel -->
  <ion-virtual-scroll [items]="items" approxItemHeight="50px">
    <ion-item *virtualItem="let item">
      <ion-label>
        <h2>{{ item.name }}</h2>
        <p>ID: {{ item.id }}</p>
      </ion-label>
    </ion-item>
  </ion-virtual-scroll>

  <!-- Chargement progressif -->
  <ion-infinite-scroll threshold="100px" (ionInfinite)="loadMoreData($event)">
    <ion-infinite-scroll-content
      loadingSpinner="bubbles"
      loadingText="Chargement des éléments...">
    </ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>

Pour les animations, il faut limiter au maximum les animations JavaScript et privilégier les animations CSS natives. Il est également recommandé de privilégier les animations GPU, telles que transform, plutôt que l’utilisation de top/left.

Les manipulations du DOM sont très coûteuses en termes de performance. Il est donc important de limiter l’utilisation de hooks et de directives qui entraînent des modifications fréquentes du DOM.

Optimisation des requêtes réseaux

Étant sur mobile, l’utilisation du réseau peut poser un certain nombre de problèmes, notamment dans les zones blanches ou mal desservies.
Pour résoudre ce problème, il convient de regrouper au maximum les requêtes et, le cas échéant, de compresser les données envoyées et reçues.
Il est également important de mettre en cache les données fréquemment utilisées. On peut ainsi utiliser Ionic Storage, IndexedDB ou @capacitor-community/sqlite. Les deux premiers s’appuient sur les bases de données des navigateurs. Le dernier utilise une base de données native.

Optimisation des performances natives

L’utilisation de fonctionnalités sensibles aux performances (caméra, GPS…) doit passer par des plugins Capacitor bien maintenus. Il faut donc optimiser les appels à ces plugins en les réduisant au minimum.

Optimisations générales

L’optimisation du code, avec la suppression des dépendances inutiles, la suppression des balises HTML superflues et le contrôle de la profondeur des nœuds HTML, permettront d’optimiser votre application.
L’utilisation de techniques avancées, comme le SSR (Server-Side Rendering), permettra d’optimiser les temps de chargement initiaux de l’application. Attention, dans le cadre du SSR, il faut prendre en compte le chargement des données. Lorsque la page est chargée depuis le serveur, on n’a pas accès aux données du navigateur ou aux données natives.
Pour les PWA (Progressive Web App), la configuration des services workers contribuera également à rendre l’expérience hors ligne plus rapide.

Suivi des performances

Dans le cadre du développement, l’utilisation de Chrome DevTools ou Safari Web Inspector permettra d’analyser le temps de rendu et de détecter les goulets d’étranglement de votre application.
Pour analyser les performances directement sur un appareil, vous pouvez utiliser des outils tiers comme Firebase Performance Monitoring ou Sentry.

Si vous souhaitez monter en compétence sur ce framework et travailler sur les performances et l’optimisation Ionic, n’hésitez pas à découvrir notre programme de formation ici.

A lire également notre article sur les Décorateurs TypeScript et Web Components.

Décorateurs TypeScript et Web Components

Chez DocDoku nous animons des formations sur les outils et frameworks pour le développement front-end. Les trois principaux acteurs du marché sont aujourd’hui React, Vue et Angular. Ils offrent tous les trois la possibilité d’écrire du code de manière déclarative pour créer nos interfaces.Néanmoins, si nous devions réaliser une petite application, choisir un de ces framework pourrait paraître surdimensionné. Alors pourquoi ne pas réaliser un petit framework maison ? Il y a en effet plusieurs technologies à notre disposition pour ce faire. Nous vous proposons d’en étudier quelques-unes dans cet article.

Les deux technologies que nous allons aborder sont les Web Components (suite de technologies HTML5) et les décorateurs TypeScript qui vont nous permettre d’effleurer la surface de la réalisation d’un framework.

Les Web Components

Les Web Components sont un regroupement de trois technologies HTML5 :

  • Les custom elements
  • Les shadow root
  • Les templates

La principale caractéristique des Web Components est la possibilité de créer des éléments personnalisés, par exemple enrichir des éléments existants comme un champ de texte, un bouton, etc. C’est aussi la possibilité de créer de nouveaux éléments HTML (balises) avec un apport sémantique dans la construction de nos applicatifs, par exemple une nouvelle balise HTML my-todo-list qui construirait elle même ses sous-éléments. Nous parlons alors de custom elements.

Voici le code le plus simple pour définir une nouvelle balise :


class MyTodoList extends HTMLElement {
  constructor() {
    super();
  }
}
customElements.define("my-todo-list", MyTodoList);

Une autre caractéristique notable est l’isolement (encapsulation) de l’HTML sous-jacent. Notre nouvelle balise et son contenu enfant peuvent être totalement « détachés » du reste de la page et ainsi ne subir aucun impact du reste du DOM, avoir sa propre feuille de style locale, et ne pas impacter les autres éléments voisins. C’est le rôle du shadow DOM.

Finalement nous pouvons nous passer de l’écriture fastidieuse en JavaScript de la construction des éléments enfants en utilisant la balise template. Cette balise nous permet d’écrire le contenu de nos éléments personnalisés directement en HTML. Aussi nous pouvons combiner les templates avec l’utilisation de la balise slot afin de rendre nos templates paramétrables.

Utilisation de la balise template :



 

Instanciation d’un élément depuis ce template :


let template = document.getElementById("my-todo-list-template");
let templateContent = template.content;
document.body.appendChild(templateContent);

Exemple complet avec ces trois technologies :



   Hello todo list 1




class MyTodoList extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById('my-todo-list-template');
    const templateContent = template.content;

    this.attachShadow({ mode: 'open' }).appendChild(
      templateContent.cloneNode(true)
    );
  }
}
customElements.define('my-todo-list',MyTodoList)

Les décorateurs TypeScript

TypeScript est une extension du langage JavaScript qui nous apporte le confort des contraintes. Etudions ce code JavaScript :

var i = 2
i = "hello"

Nous ne savons pas lors de l’écriture du code si la variable i est un nombre ou une chaine de caractères et cela peut être fatal si nous ne faisons pas l’effort de vérifier son type lors de son utilisation.

Avec TypeScript nous sommes contraints à garder le type défini lors de l’initialisation.

let i : number = 2
i = "hello" // Interdit

Cet exemple nous montre seulement un des aspects de TypeScript. Ce qui nous intéresse ici est l’apport des décorateurs.

Imaginons que nous souhaitions automatiser l’exécution de code pour deux classes différentes. Par exemple une classe Voiture et une classe Animal qui auraient toutes les deux leur table associée dans la base de données.


class Animal {
  static {
    // code pour initialiser la table ANIMAL
  }
}

class Voiture {
  static {
    // code pour initialiser la table VOITURE
  }
}

Dans cet exemple, difficile de mutualiser du code, nos classes ont un bloc d’initialisation static qui peut contenir de plus en plus de choses, difficile de maintenir, etc.

Avec les décorateurs TypeScript nous pouvons imaginer écrire nos classes différemment :

@CreateTable("ANIMAL")
class Animal {
}

@CreateTable("VOITURE")
class Voiture {
}

Et pourquoi pas leur ajouter d’autres fonctionnalités, par exemple l’utilisation d’un utilitaire de log différent :

@CreateTable("ANIMAL")
@Logger(SomeLogger)
class Animal {
}

@CreateTable("VOITURE")
@Logger(AnOtherLogger)
class Voiture {
}

Derrière ces décorateurs se cachent en fait de simples fonctions. Celles-ci ne sont exécutées qu’une fois au chargement de la classe, et non lors de nouvelles instances de celles-ci.

const CreateTable = (name: string) => {
  return (target: Function) => {
    console.log(name)    // Le nom passé en paramètre, ici "ANIMAL"
    console.log(target)  // L'objet de type class, ici class Animal
    console.log(Object.getOwnPropertyDescriptors(target.prototype)) // Liste des méthodes
  };
};


@CreateTable("ANIMAL")
class Animal {
    _age: number
    get age(){ return this._age }

    manger(){
        //...
    }
}

/*
ANIMAL
[class Animal]
{
  constructor: {
    value: [class Animal],
    writable: true,
    enumerable: false,
    configurable: true
  },
  age: {
    get: [Function: get age],
    set: undefined,
    enumerable: false,
    configurable: true
  },
  manger: {
    value: [Function: manger],
    writable: true,
    enumerable: false,
    configurable: true
  }
}
*/

Début d’un framework basé sur ces deux technologies

En combinant les Web Components et les décorateurs TypeScript, nous pouvons alors imaginer réaliser un début de framework maison inspiré d’Angular.

Avec Angular, la création d’un composant est déclarative, et son utilisation très simple depuis un fichier HTML :


@Component({
  selector: 'app-my-component',
  template: '

{{ title }}

', styles: ['h1 { font-weight: normal; }'] }) class MyComponent { title = "Hello world" }

Nous allons nous inspirer de cela pour écrire un début de framework. Nous avons besoin d’un décorateur qui va créer automatiquement son shadow DOM depuis le template passé dans les arguments du décorateur « Component ».


// Paramètres du décorateur
interface ComponentOptions {
  selector: string; // Nom de la nouvelle balise
  template: string; // Le template associé
  style: string; // Un peu de css
}

// Fonction décorateur
function Component(options: ComponentOptions) {
  return function < T extends { new (..._args: any[]): HTMLElement } >(
    target: T
  ) {
    const cls = class extends target {
      constructor(..._args: any[]) {
        super();

        // Creation du shadow DOM
        let root = this.attachShadow({ mode: "open" });

        // Création des balises enfants avec EJS (moteur de template)
        root.innerHTML = options.template;

        // Ajout de style local
        let style = document.createElement("style");
        style.textContent = options.style;
        root.appendChild(style);
      }
    };

    // Enregistrement automatique en tant que custom element
    customElements.define(options.selector, cls);

    return cls;
  };
}

@Component({
  selector: "my-todo-list",
  template: `
   

<%= title %>

  • Do this
  • Do that
`, style: `h2 {color: blue}`, }) export default class HelloWorld extends HTMLElement { title = "Hello world !"; }

Bien entendu, notre framework maison est encore loin d’être complet. Nous aurions besoin d’y ajouter une détection des changements, du routing, une couche HTTP, etc. Seule la partie de création de composants et d’affichage a été abordée dans cet article.

ElasticSearch ILM et répartition des données

Description

La gestion d’une centaine de milliards de documents (données issues de logs applicatifs, équipements réseau, middlewares, etc.) s’avère coûteuse (machines, disques, espace de sauvegarde, etc.).
Pour alléger ce coût d’infrastructure nous devons distinguer les données brûlantes des données tièdes et froides (voir gelées). C’est ce que propose ElasticSearch avec l’ILM (Index Lifecycle Management). Nous vous proposons ainsi d’analyser sa mise en œuvre sur un cluster de production d’un de nos clients.
Les données considérées brûlantes doivent être exploitables dans des temps de réponse très courts, nous devons donc les placer sur des machines dimensionnées correctement. Les requêtes sur les données tièdes et froides n’ont pas besoin d’être aussi performantes, ces données peuvent donc automatiquement être déplacées vers des machines plus modestes grâce aux règles de rétention offertes par ILM.

Limitations des écrans dans Kibana et Cerebro

Kibana, l’interface graphique de la suite Elastic ne propose pas une vue synthétique de cette répartition. Nous pouvons obtenir cette information de répartition grâce à plusieurs requêtes, mais nous devons alors croiser les résultats, ce qui est fastidieux.
Cerebro, une interface graphique issue de la communauté open source, est excellent dans la visualisation de la répartition de la charge, mais ne propose pas cette visualisation au niveau du cycle de vie.
Même constat pour Elasticvue, qu’il s’agisse de sa version desktop, extension Chrome ou webapp.
Un moyen d’obtenir les informations souhaitées est donc de passer par l’API d’ElasticSearch.
Avec une première API nous pouvons obtenir les rôles des machines :

GET _nodes/settings?filter_path=nodes.*.roles,nodes.*.name

{
  "nodes": {
    "ECNRRDP2SUKmcu3s9qJgnA": {
      "name": "es3",
      "roles": [
        "data_cold",
        "ingest",
        "master"
      ]
    },
    "NXZNCa_BQ_SE613oSnDf-g": {
      "name": "es6",
      "roles": [
        "data_cold",
        "ingest",
        "master"
      ]
    },
    "jqmdwzdeQHG84601oQjpGw": {
      "name": "es1",
      "roles": [
        "data",
        "data_hot",
        "ingest",
        "master"
      ]
    },
    "4z2k13n8SZS4t2JmLielaQ": {
      "name": "es4",
      "roles": [
        "data",
        "data_hot",
        "ingest",
        "master"
      ]
    },
    "AFRMIIagRWK4Cu-ZhbLH2A": {
      "name": "es5",
      "roles": [
        "data_warm",
        "ingest",
        "master"
      ]
    },
    "sxljzKF-Q1app0VSkVgTxg": {
      "name": "es2",
      "roles": [
        "data_warm",
        "ingest",
        "master"
      ]
    }
  }
}

Puis avec une deuxième API nous pouvons récupérer le cycle de vie des indices (index au pluriel).

GET /*/_settings?filter_path=*.settings.index.routing.allocation.include,*.settings.index.uuid,*.settings.index.provided_name

{
  ".ds-my-index-1-2024.09.17-003772": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003772",
        "uuid": "HyELPU6oTpC_EfskAxP5BQ"
      }
    }
  },
  ".ds-my-index-1-2024.09.17-003748": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_warm,data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003748",
        "uuid": "QNC-Z2loT4SCKFb2-LJKAw"
      }
    }
  },
  ".ds-my-index-1-2024.09.17-003714": {
    "settings": {
      "index": {
        "routing": {
          "allocation": {
            "include": {
              "_tier_preference": "data_cold,data_warm,data_hot"
            }
          }
        },
        "provided_name": ".ds-my-index-1-2024.09.17-003714",
        "uuid": "zIP72E-qTBSfKbZdAYmFEQ"
      }
    }
  },
  [...]
}

Et avec une troisième API nous pouvons restituer le détails des blocs de données correspondant aux indices :

GET /_cat/shards?format=json&h=index,node,state,prirep,docs

[
  {
    "index": ".ds-my-index-1-2024.09.17-003848",
    "node": "es1",
    "state": "STARTED",
    "prirep": "p",
    "docs": "120"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003848",
    "node": "es4",
    "state": "STARTED",
    "prirep": "r",
    "docs": "47"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003828",
    "node": "es5",
    "state": "STARTED",
    "prirep": "p",
    "docs": "29"
  },
  {
    "index": ".ds-my-index-1-2024.09.17-003828",
    "node": "es2",
    "state": "STARTED",
    "prirep": "r",
    "docs": "147"
  },
  [...]
]

En croisant les résultats de ces trois requêtes, nous pouvons donc savoir si un bloc de données d’un index « brûlant » est bien placé sur un noeud « brûlant ». Mais pour des milliers d’index, cela devient beaucoup plus laborieux.

Développement d’une solution de visualisation

Quelques lignes de JavaScript (64), d’HTML (80) et de CSS (118) plus tard et voici un aperçu d’un rapport réalisé sur un cluster ElasticSearch en local.

ElasticSearch ILM test

Résultat de l’analyse du cluster local

L’outil génère un fichier de rapport en format HTML, consultable depuis n’importe quel navigateur. Alors, en un coup d’œil on peut savoir si un bloc est à la bonne place.
Le code source est disponible sur Github.
Pour résumer ce que fait techniquement ce code source :

  • Lancement des 3 requêtes précédemment étudiées
  • Injection de ces données dans un moteur de template HTML
  • Sauvegarde du résultat dans un fichier HTML

Premier rapport réalisé sur une infrastructure de production et analyse

Ensuite, nous avons intégré l’outil dans une chaîne d’intégration continue de Gitlab pour automatiser la génération des rapports. Nous pouvons ainsi désormais nous interfacer avec le cluster à analyser et donc générer un premier rapport.
Quelques données sur la taille du cluster :

  • 10 machines ElasticSearch
  • 110 milliards de documents
  • 125 To de données stockées
  • 25 000 événements par seconde

Le premier rapport montre une répartition de la charge logique comme ci-dessous :

ElasticSearch ILM

Résultat de l’analyse du cluster de production

Les nœuds ayant le rôle data_warm reçoivent bien les blocs data_warm. Les nœuds ayant le rôle data (tout les rôles) reçoivent tout type de blocs.
Cependant ce n’est pas encore optimisé pour réduire les coûts car :

  • Il reste des blocs data_warm non alloués à des nœuds data_warm car ElasticSearch cherche l’équilibre en termes de nombre de blocs par machine.
  • Aucun bloc de données froides n’apparaît car les règles de rétention ne définissent pas de phase « cold ». Des nœuds data_cold doivent être ajoutés au cluster.
  • Il y a légèrement trop de données brûlantes en proportion, la durée de rétention en phase « hot » doit être revue à la baisse.

Evolution du cluster

Ainsi, après réflexions avec les équipes en charge de la maintenance du cluster, nous avons définit la cible à atteindre :

  • 3 nœuds cold (à venir)
  • 5 nœuds warm (node-3 node-9 node-10 node-1 node-2)
  • 2 nœuds hot warm master (node-4 node-5)
  • 3 nœuds hot ingest master content (node-6 node-7 node-8)

Les raisons de ces choix dépendent des caractéristiques des machines à disposition (CPU, disques, RAM).
Les mouvements de blocs vont être nombreux, et nous réfléchissons déjà à la procédure de migration afin de perturber au minimum le service. Il faut bien prendre en compte que l’espace disque pris par chaque bloc est de 50Go, et que chaque déplacement prend entre 30 minutes et 1 heure sur cette infrastructure réseau.
Nous avons ensuite planifié l’exécution de l’outil pour fournir un rapport tous les jours afin de suivre l’évolution de la répartition. Nous aurons donc un joli jeu de couleurs d’ici quelques semaines 😉

Découverte de JetPack Compose

Bannière Jetpack Compose

Description

Jetpack Compose est un framework d’interface utilisateur développé par Google, sorti en version 1.0 en août 2021. Comme SwiftUI pour iOS, il permet de faciliter et d’accélérer la création d’interfaces graphiques pour les applications Android. C’est une approche innovante et différente de ce que proposait Android en termes de développement d’UI via les XML Views.

Avantages

Un des principaux avantages de Compose est l’approche déclarative des éléments. Grâce à la gestion de la fonction remember. Lorsqu’un élément change d’état, l’interface se met à jour automatiquement.
Par exemple, lorsque nous déclarons une variable var name by remember { mutableStateOf("Jean") }, la valeur initiale de la variable est Jean, mais lorsque le nom changera, l’interface affichera automatiquement la nouvelle valeur associée.
Les composants sont appelés Composable et peuvent être imbriqués entre eux afin de créer des composants modulables, complexes et réutilisables. Cela permet ainsi de créer un code plus structuré et plus compréhensible pour les développeurs.
De plus, Compose est plus performant grâce à sa capacité à « recomposer » les bons composants au bon moment. C’est-à-dire qu’il est capable de mettre à jour les composants nécessitant un changement d’état sans avoir à tout recréer.
Voulant optimiser et faciliter au maximum le développement d’interfaces utilisateur, Google intègre des Composable favorisant les standards de l’écosystème Android, tels que Scaffold, qui permet de gérer une vue avec une TopBar, une BottomBar, un FloatingActionButton, tout en gérant automatiquement les bonnes marges pour le contenu.

Démonstration

Prenons l’exemple d’une interface affichant une liste de prénoms pouvant être alimentée par un champ de texte.

XML

activity_main.xml

Ce fichier va définir l’interface principale de l’application. Comprenant un EditText pour la saisie d’un nouveau nom, une ImageView pour ajouter l’élément à la liste et une RecyclerView pour afficher celle-ci. Comme la liste peut contenir un nombre indéterminé d’éléments, ce composant est plus adapté et optimisé.

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">


   <EditText
       android:id="@+id/et_name"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       android:hint="Nouveau nom"
       android:inputType="text"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toStartOf="@+id/but_add"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.0"
       tools:ignore="Autofill">


   <ImageView
       android:id="@+id/but_add"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@id/et_name"
       android:layout_marginEnd="16dp"
       android:src="@android:drawable/ic_menu_add"
       app:layout_constraintBottom_toBottomOf="@+id/et_name"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="1.0"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="@+id/et_name"
       app:tint="#2196F3">


   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/rv_names"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_below="@id/but_add"
       android:layout_marginTop="16dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/et_name">


</androidx.constraintlayout.widget.ConstraintLayout>

row_item.xml

Représentant un élément de la liste, ce fichier comprendra un TextView pour afficher le nom, ainsi qu’une ImageView pour supprimer l’entrée si nécessaire.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="vertical">


   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       android:padding="8dp">


       <TextView
           android:id="@+id/tv_name"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:textSize="16sp">


       <ImageView
           android:id="@+id/but_delete"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:srcCompat="@android:drawable/ic_delete">


   <LinearLayout>


   <com.google.android.material.divider.MaterialDivider
       android:layout_width="match_parent"
       android:layout_height="1dp">


<LinearLayout>

ListAdapter.kt

Maintenant que les fichiers XML ont été créés, nous devons les associer à leurs fichiers Kotlin. ListAdapter.kt va, comme son nom l’indique, associér l’élément de la liste avec le composant XML

class ListAdapter(
   private val names: MutableList,
   private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter() {


   inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
       val tvName: TextView = view.findViewById(R.id.tv_name)
       val butDelete: ImageView = view.findViewById(R.id.but_delete)


       init {
           butDelete.setOnClickListener {
               onDeleteClick(adapterPosition)
           }
       }
   }


   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
       val view = LayoutInflater.from(parent.context).inflate(R.layout.row_item, parent, false)
       return ViewHolder(view)
   }


   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
       holder.tvName.text = names[position]
   }


   override fun getItemCount(): Int {
       return names.size
   }
}

MainActivity.kt

Maintenant que tout est en place, nous pouvons créer le fichier principal qui gérera toute la logique de la fonctionnalité. Ce fichier associera les différents composants aux variables ainsi que ListAdapter à la RecyclerView. De plus, c’est ici que nous pourrons gérer les actions des utilisateurs, telles que l’ajout et la suppression d’un prénom avec la mise à jour de l’interface.

class MainActivity : AppCompatActivity() {


   private lateinit var etName: EditText
   private lateinit var butAdd: ImageView
   private lateinit var rvNames: RecyclerView
   private lateinit var names: MutableList
   private lateinit var adapter: ListAdapter


   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       etName = findViewById(R.id.et_name)
       butAdd = findViewById(R.id.but_add)
       rvNames = findViewById(R.id.rv_names)
       names = mutableListOf("Pierre", "Paul", "Jacques")
       adapter = ListAdapter(names) { position ->
           names.removeAt(position)
           adapter.notifyItemRemoved(position)
       }
       rvNames.setLayoutManager(LinearLayoutManager(this))
       rvNames.setAdapter(adapter)
       butAdd.setOnClickListener(View.OnClickListener {
           val name = etName.getText().toString()
           if (name.isNotEmpty()) {
               names.add(name)
               adapter.notifyDataSetChanged()
               etName.setText("")
           }
       })
   }
}

Comme nous pouvons le constater dans la MainActivity.kt, l’adaptateur a besoin d’être notifié d’un changement; à la fois lorsqu’on ajoute un élément adapter.notifyDataSetChanged() et lorsqu’on supprime un élément adapter.notifyItemRemoved(position).

Compose

Avec Compose, nous aurons besoin d’un seul fichier. Nous pouvons bien évidemment séparer les composants en plusieurs fichiers pour plus de lisibilité ou d’ergonomie.
La fonction remember nous permet de suivre les changements de valeurs, tant sur la liste des noms que sur la saisie de texte.
Le composable LazyColumn est l’équivalent d’une RecyclerView. Il permet d’optimiser l’affichage d’une liste de taille indéterminée.
Enfin, le composable FirstNameRow représente une entrée dans la liste. Il n’y a pas ici besoin d’adaptateur, il suffit de déclarer le composant tel quel et de l’intégrer dans la boucle de la LazyColumn.

MainActivity.kt

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MainView()
       }
   }
}


@Composable
fun MainView() {
   var names by remember { mutableStateOf(listOf("Pierre", "Paul", "Jacques")) }
   var entryName by remember { mutableStateOf("") }


   Column(
       modifier = Modifier.padding(16.dp),
       verticalArrangement = Arrangement.spacedBy(16.dp)
   ) {
       TextField(
           value = entryName,
           onValueChange = { entryName = it },
           label = {"Nouveau prénom"},
           modifier = Modifier.fillMaxWidth(),
           trailingIcon = {
               Icon(
                   imageVector = Icons.Default.AddCircle,
                   contentDescription = "Ajouter prénom",
                   modifier = Modifier.clickable {
                       if(entryName.isNotEmpty()) {
                           names += entryName
                           entryName = ""
                       }
                   },
                   tint = Color.Blue
               )
           }
       )


       LazyColumn(
           verticalArrangement = Arrangement.spacedBy(5.dp)
       ) {
           items(names.count()) { index ->
               FirstNameRow(firstName = names[index]) {
                   names -= names[index]
               }
           }
       }
   }
}


@Composable
fun FirstNameRow(
   firstName: String,
   onDelete: () -> Unit
) {
   Column(
       Modifier.fillMaxWidth()
   ) {
       Row(
           Modifier.fillMaxWidth(),
           verticalAlignment = Alignment.CenterVertically
       ) {
           Text(text = firstName)
          
           Spacer(modifier = Modifier.weight(1f))


           Icon(
               imageVector = Icons.Default.Close,
               contentDescription = "Supprimer prénom",
               modifier = Modifier.clickable {
                   onDelete()
               },
               tint = Color.Red
           )
       }


       Divider()
   }
}

@Composable
fun MainActivityPreview() {
   MainView()
}

Comme nous pouvons le constater ci-dessus, aucun adaptateur n’a été initié, et aucune notification envers le LazyColumn n’a besoin d’être faite. Tout se fait distinctement, simplement et rapidement.
Nous pouvons également noter qu’il existe une fonction de prévisualisation qui permet d’afficher la vue en temps réel. Nous avons aussi la possibilité d’interagir directement avec les différents composants sans qu’aucun émulateur ou appareil physique ne soit nécessaire. Cela permet d’effectuer des tests d’UI et fonctionnels beaucoup plus rapidement.

Conclusion

Tout comme SwiftUI côté iOS, Jetpack Compose modernise le développement d’applications Android en offrant une approche plus simple, plus puissante et plus concise pour la création d’interfaces utilisateur. Les développeurs bénéficient d’une productivité accrue et d’une meilleure expérience de développement. Pour ma part, je trouve cette approche de développement bien plus intéressante et innovante que le modèle XML traditionnel. De plus, lorsqu’on développe à la fois sur Android avec Kotlin/Compose et sur iOS avec Swift/SwiftUI, nous remarquons des similitudes qui facilitent et accélèrent considérablement le développement sur les deux plateformes concurrentes.

KMM : découverte de Kotlin Multiplateform Mobile

Définition

Kotlin Multiplateform Mobile, plus communément appelé KMM, est une technologie open source créée par JetBrains. Stable et en production depuis novembre 2023, elle permet de simplifier le développement multiplateforme, notamment pour Android et iOS.
Le principal langage de programmation de KMM est, comme son nom l’indique, Kotlin. Développé en 2011 par JetBrains, ce langage, moins verbeux que Java, permet une écriture de code plus rapide et concise, offrant une accélération conséquente du rythme de développement. Kotlin propose également des améliorations, notamment avec l’introduction de la null-safety, afin d’éviter les NullPointerException en déclarant des variables pouvant être nulles, assurant ainsi un code plus limpide et robuste. Depuis 2017, il est devenu le langage officiel du développement mobile Android.

Fonctionnement

KMM permet d’intégrer le code métier de l’application de manière partagée entre Android et iOS, réduisant drastiquement le temps de développement et évitant la redondance. Il est utilisé pour écrire les différents modèles de l’application, les appels réseau et aux bases de données. Pour une architecture MVVM, certaines librairies offrent la possibilité de partager également les ViewModels sur les deux supports. Seule la partie visuelle doit être écrite nativement, en XML ou Jetpack Compose pour Android, et en SwiftUI ou UIKit pour iOS, assurant ainsi des performances et une expérience utilisateur optimales.

Exemple

Prenons l’exemple de base d’un projet KMM tout nouvellement créé :

Arborescence KMM (Kotlin Multiplateform Mobile) composeApp : contient le code source de l’application Android en Compose, non partagé.
iosApp : contient le code source de l’application iOS en Swift/SwiftUI, non partagé
shared : contient le code partagé entre les 2 plateformes séparé en 3 dossiers

 

Dans le dossier shared, nous pouvons remarquer la présence des classes et des fichiers Kotlin. Le fichier Platform.kt quant à lui, dans commonMain, contient une interface qui retourne le nom de la plateforme associée au téléphone ainsi qu’une fonction expect. Déclarer une fonction expect permet d’implémenter du code spécifique natif à la plateforme, et c’est pour cela que dans androidMain et dans iosMain, nous retrouvons l’utilisation de cette interface afin de récupérer nativement le nom et le numéro de version de la plateforme. Dans la classe Greeting, nous pouvons appeler une fonction qui nous retournera le nom de la plateforme associée à la fois en Compose sur Android et en SwiftUI sur iOS.

Code KMM (Kotlin Multiplateform Mobile)

Code KMM (Kotlin Multiplateform Mobile)

Conclusion

À l’instar des autres technologies de développement hybride, comme React Native ou Flutter, Kotlin Multiplateform Mobile permet de conserver l’aspect natif de l’application. Il permet en effet d’utiliser les composants et l’expérience des écosystèmes Android et iOS tout en partageant du code métier, alliant à la fois performance de l’application et rapidité de développement. Pour ma part, je trouve que KMM est une véritable innovation dans la création d’applications mobiles natives, permettant d’éviter la redondance d’écriture de code métier lors de la création d’une application Android et iOS. KMM prend dors et déjà de plus en plus d’ampleur au sein des stacks de développement mobile, qu’il s’agissent de nouvelles application ou de refonte.

A noter que les développeurs Android auront forcément plus d’attirance pour cette approche basée sur un langage et un écosystème Kotlin. Cela ouvre également un débat plus organisationnel au sein des équipes mobiles. En effet, il faudra savoir quels développeurs sont ou seront responsables du développement du code partagé.

UIKit vs SwiftUI

UIKit

Pour développer des applications iOS en utilisant le langage Swift ou Objective-C, Apple propose un framework d’interface utilisateur impératif : UIKit. Il permet de construire la partie UI de l’application notamment via l’InterfaceBuilder, l’outil de développement d’interface graphique intégré dans Xcode.
La prise en main de cet outil peut être fastidieuse, et malgré l’habitude, ajouter un simple écran s’avère assez long. Il est tout d’abord nécessaire de glisser et déposer 1 à 1 chacun des composants, pour ensuite les lier entre eux afin de les positionner. Enfin, il faut toujours procéder de la même manière et déclarer ces derniers dans un ViewController.
Pour certains écrans, parfois complexes, cette méthode est chronophage et répétitive. De plus, les écrans créés par cet outil sont intégrés dans un fichier appelé Storyboard, pouvant contenir de multiples interfaces et donc présenter plusieurs inconvénients :

  • Un temps de chargement trop long
  • De nombreux conflits lors de merge (en utilisant Git par exemple)
  • Des composants non-dynamiques et non-adaptables
  • Une interface qui semble désordonnée

Exemple de Storyboard (Source: Swiftement)

Pour pallier ces difficultés, ainsi qu’aux nouveaux designs qui peuvent être plus exigeants, et par conséquent nécessiter un temps de développement plus important, en 2019, Apple a proposé un nouveau framework : SwiftUI.

SwiftUI

Ce nouveau framework d’UI propose une approche déclarative et n’est disponible qu’en Swift. La construction d’un écran se déroule directement dans le code, ce qui est beaucoup plus rapide et compréhensible par les développeurs. Construire une simple interface prendra ainsi peu de temps, chaque écran faisant partie d’un unique fichier (à contrario des Storyboards cités auparavant). Il propose également un canvas interactif permettant de visualiser les changements en temps réel selon plusieurs configurations. Une est notamment utilisée pour l’accessibilité, rappelant ainsi les développements web et mobiles hybrides.

Ce framework peut à la fois être intégré dans des composants UIKit pour ainsi disposer de composants plus dynamiques. De par sa jeunesse, il peut également au contraire intégrer des composants UIKit afin de compenser un certain manque d’adaptabilité. N’étant disponible qu’à partir d’iOS13, certaines applications et téléphones restent évidemment incompatibles avec SwiftUI. Cependant, les versions minimales iPhone sont souvent augmentées pour des raisons de sécurité, rendant ainsi SwiftUI potentiellement supporté par tous les iOS dans un futur proche. A contrario d’UIKit, nécessitant la création d’un nouveau d’un projet en AppKit pour développer une application MacOS, SwiftUI prend lui en charge le multi-device de tous les appareils de la pomme.

UIKit vs SwiftUI : démonstration comparative chronométrée

Prenons l’exemple d’un écran constitué simplement d’un titre et d’un texte, possédant une valeur qui s’incrémente ou se décrémente à l’aide de deux boutons. L’approche de conception est bien différente, avec UIKit, le code n’est utilisé que pour la partie logique de l’application. La partie design se fait via un outil d’interface qui permet de construire notre écran au fur et à mesure, de manière plus contrôlée, mais plus lente. Une bonne partie du temps est utilisée pour parcourir les menus des différents composants. Avec SwiftUI, nous restons sur la même page, en utilisant que très peu la souris et le pad, tout se construisant via le code. Les vidéos accélérées ci-dessous démontrent que l’approche SwiftUI est jusqu’à 2,5 fois plus rapide qu’UIKIt dans cet exemple.

UIKit Demo
SwiftUI Demo

Conclusion UIKit vs SwiftUI

Bien que l’utilisation des deux frameworks puissent s’associer, et qu’UIKit prévale de par son ancienneté, SwiftUI se démarque. En effet, son dynamisme, sa facilité de prise en main ainsi que sa rapidité d’écriture et de conception me permettent, après une réelle utilisation sur projet d’affirmer qu’il représente le futur du développement natif Swift.
Précédemment, j’avais pu développer un projet mobile natif Android en Java et iOS en Swift/UIKit. Je me sentais beaucoup plus à l’aise sur Android et retourner sur xCode m’apparaissait fastidieux et pénible. En effet, avec des temps de chargements longs, de simples écrans à construire pouvaient prendre des heures de travail, pour le même rendu final sur Android, en 3 fois moins de temps. Apprendre et utiliser SwiftUI a donc été, sans l’ombre d’un doute, une vraie bouffée d’air frais. Le framework était certes encore jeune, mais tellement puissant, fluide et dynamique qu’il m’a donné à l’époque l’envie de rouvrir xCode !

LRA Long Running Actions

Principe général

Long Running Actions (LRA) est un module de la stack Eclipse MicroProfile comme peuvent l’être « Health », « Fault Tolerance » ou « GraphQL ». Son objectif est de permettre la coordination des microservices concourant à implémenter une même activité métier. Sur le plan théorique ce pattern est référencé sous le nom de SAGA.

Bien sûr l’idée n’est pas de revenir au mode de fonctionnement centralisé et fortement couplé dont l’architecture microservices essaie de s’éloigner. Il n’est donc pas question d’opérer des transactions SQL distribuées comme on a pu le faire il y a quelques années, chaque microservice reste seul responsable de son système de stockage qui n’est pas partagé.

Le but ici est d’atteindre l’eventual consistency. Attention pour ceux qui ne maîtriseraient pas l’anglais, eventual est un faux amis, il ne s’agit pas de dire que le système sera éventuellement cohérent mais qu’il le sera forcément tôt ou tard.
Ce concept est familier aux utilisateurs des bases de données de nouvelle génération qui l’exploitent pour offrir une bonne montée en charge.

Examinons maintenant plus concrètement comment déployer cela au sein d’une architecture microservices.

Le coordinateur

L’implémentation de Long Running Actions requière la coopération d’un service additionnel à nos microservices métier. Son rôle sera d’orchestrer les transactions, c’est à dire d’informer nos services pour leur demander de compenser (défaire) leurs actions ou au contraire de les valider définitivement.

Ainsi la première étape consiste à lancer ce fameux coordinateur. Nous utiliserons ici l’implémentation issue du projet Narayana, packagée dans un microservice Quarkus:
docker run -d -p 8080:8080 quay.io/jbosstm/lra-coordinator

Pour vérifier qu’il tourne correctement:
curl -i localhost:8080/lra-coordinator
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
content-length: 2
Narayana-LRA-API-version: 1.1

[]

D’autres alternatives à Narayana existent et il est possible de les utiliser sans impact sur le reste de notre architecture car les interactions s’appuient sur un protocole HTTP REST normalisé.

Sur les microservices

Au niveau du code de nos microservices, les _endpoints_ participant à une transaction métier longue doivent être annotés @LRA, par exemple:

@LRA(value = LRA.Type.REQUIRED, end=false)
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Path("/book")
public Response bookFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String destination) {
  if (destination.equals("Toulouse") || destination.equals("Paris")) {
    System.out.println("Succès");
    return Response.ok().build();
  }
  else {
    System.out.println("Echec");
    return Response.serverError().build();
  }
}

L’annotation @LRA présente des similitudes avec une autre annotation largement utilisée : @Transactional. En effet, elles partagent toutes deux des paramètres pour spécifier le périmètre de la transaction, qui se ressemblent beaucoup. Par défaut, l’attribut value est égal à « REQUIRED », ce qui indique que si une transaction est déjà en cours, la méthode sera exécutée dans son contexte. La propagation de l’id de la LRA se fait au travers de l’entête HTTP « Long-Running-Action ». C’est ainsi la présence de cette information dans la requête entrante qui détermine s’il faut créer une LRA ou pas. Dans la réponse HTTP nous retrouverons également cette entête. Elle devra être réutilisée par le client dans les requêtes successives. Pour fixer les idées la valeur de cette entête pourrait être par exemple :

http://localhost:8080/lra-coordinator/0_ffffc0c5100f_f355_6113bebe_22

L’id de la LRA prend la forme de son URL canonique. La création de la LRA se fait sur demande par l’appel de l’API REST du coordinateur depuis notre microservice.
Nous n’avons pas à coder cette interaction, cela se fait automatiquement par la librairie LRA.
Les appels aux microservices suivants, lorsque ceux-ci sont annotés avec @LRA et que l’entête est bien fournie, se voient donc inclus dans la même transaction tant que la propriété end=true n’est pas fixée. Quand ce sera le cas, la transaction LRA sera fermée après l’exécution de la méthode et le coordinateur appellera pour chaque microservice son point de terminaison d’API annoté @Complete.

@Complete
@Path("/complete")
@PUT
public Response completeFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String userData) {
  String message = "Flight Booking completed with LRA with id: " + lraId;
  System.out.println(message);
  return Response.ok(ParticipantStatus.Completed.name()).build();
}

Si au contraire, une des méthodes des microservices annotées @LRA avait été en erreur, la LRA dans son ensemble aurait été annulée. Les méthodes callback invoquées auraient alors été celles annotées @Compensate.

@Compensate
@Path("/compensate")
@PUT
public Response compensateFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId, String userData) {
  String message = "Flight Booking compensated with LRA with id: " + lraId;
  System.out.println(message);
  return Response.ok(ParticipantStatus.Compensated.name()).build();
}

Conclusion

Comme nous venons de le voir, le module Long Running Actions fournit un cadre d’implémentation du pattern SAGA.
Si des points communs avec les transactions JPA (Java Persistence API) peuvent être relevés, le fonctionnement des LRAs est cependant largement plus découplé et repose sur un système de compensation. C’est en effet à nous d’implémenter le rollback en fonction bien sûr de la technologie sous-jacente. Même principe pour la méthode @Complete c’est à nous qu’incombe de coder la logique de « commit« . Ce terme est à considérer avec des guillemets car même si le système de stockage du microservice est construit sur une base traditionnelle, sa transaction SQL locale sera déjà véritablement commitée (il est inenvisageable de conserver des verrous base de données sur plusieurs requêtes HTTP). La plupart du temps la tâche de la méthode @Complete se résume à nettoyer les ressources ou informations conservées dans l’optique de pouvoir compenser la LRA si finalement elle devait être annulée.

Ainsi l’essentiel du framework consiste à mettre en œuvre une série de notifications pour informer les microservices des résultats des opérations des uns des autres. Cette synchronisation se fait avec un décalage qui peut être plus ou moins long, certains microservices pouvant échouer à compenser, mais le coordinateur insistera et les notifiera à nouveau. L’objectif final n’étant pas de garantir une consistence instantanée des données mais juste de permettre d’y arriver à un moment donnée.

Pour plus de détails encore dans le protocole Long Running Actions, voici sa javadoc, bien sûr n’hésitez pas à expérimenter avec Quarkus par exemple:
https://download.eclipse.org/microprofile/microprofile-lra-2.0/apidocs/

Agrément CIR renouvelé pour DocDoku jusqu’en 2027

Agrément CIR depuis 2011

Pour la cinquième fois consécutive, DocDoku obtient son agrément Crédit d’Impôt Recherche (CIR) pour quatre ans, jusqu’en 2027 donc. Nous détenons en effet cet agrément depuis 2011.
Pour rappel, cela signifie que DocDoku est reconnu comme organisme ayant la capacité à mener des travaux de Recherche et Développement (R&D) et que toutes les dépenses de réalisation d’opérations de R&D confiées à DocDoku ouvrent droit au Crédit d’Impôt en faveur de la Recherche, dans les mêmes conditions que pour des investissements internes.

Le CIR, c’est quoi ?

Pour rappel, le CIR est un dispositif d’incitation fiscale en faveur de la recherche et de l’innovation. Il permet aux entreprises de bénéficier d’un crédit d’impôt correspondant à un pourcentage du montant des dépenses investies dans l’année en recherche et développement. Cet avantage fiscal varie entre 30% et 50% de Crédit d’Impôt sur les dépenses engagées pour les projets éligibles (voir conditions sur le site du Ministère).

Qu’attendons-nous pour innover ensemble ?

Qu’attendez-vous pour innover et lancer de nouveaux projets R&D puisque ces derniers peuvent faire l’objet d’une prise en charge substantielle au travers du Crédit d’Impôt Recherche lorsque vous passez par notre intermédiaire ?
Pour toutes questions sur vos projets innovants, contactez-nous.

FNE-Formation ou comment financer vos formations à 100% en chômage partiel

Vous êtes nombreux à vous interroger sur le dispositif FNE-Formation aujourd’hui renforcé à cause de la crise sanitaire que nous traversons.
L’objectif de cet article est donc de vous apporter toutes les réponses afin de bénéficier au mieux de ce dispositif.

Qui peut y avoir droit ?

Tous les salariés du privé placés en chômage partiel (les contrats d’apprentissage et de professionnalisation étant exclus) ! La formation doit par contre avoir lieu durant les heures et jours de chômage partiel, donc pas pendant les congés, RTT ou jours fériés.

Quel montant est pris en charge ?

La totalité des coûts pédagogiques est prise en charge. Il faut cependant savoir que les formations en dessous de 1500 euros TTC par salarié sont traitées de manière plus rapide et de manière quasi automatique au niveau de votre DIRECCTE régionale.
Au-dessus de 1500 euros TTC par salarié une instruction plus avancée doit être engagée.

Quels types de formation ?

Toutes les formations sont éligibles, à condition d’avoir lieu à distance et à l’exception des formations en alternance et obligatoires à la sécurité incombant à l’employeur.

Quelle est la procédure à suivre ?

Il y a deux procédures possibles :

N’hésitez pas à nous contacter si vous souhaitez en savoir plus ou vous lancer, toutes nos formations au catalogue étant bien entendu disponibles à distance.

Sources :

Chaîne DevOps intégrant Jenkins avec Ansible ou OpenShift (2/2)

Introduction

Le but de cet article est de présenter brièvement le concept de DevOps, illustré par une implémentation concrète dans un environnement applicatif de microservices Restfull, développés dans le contexte d’une approche agile.

La première partie est consacrée au rappel des principes de DevOps et à la présentation du schéma architectural d’une chaîne DevOps avec Github, Jenkins, Artifactory et Ansible.

Cette deuxième partie présente une autre chaîne DevOps avec Github, Jenkins, Artifactory et OpenShift.

1 Exemple d’une mise en oeuvre avec OpenShift

La chaîne DevOps décrite ci-dessous permet d’assurer des déploiements dans une plateforme de containers (OpenShift) sur différents namespaces  (§. 1.1 ci-dessous) : INT, VAL et PROD.

Elle est composée des éléments suivants :

  • GitHub
  • Git local installé sur chaque poste développeur
  • OpenShift qui héberge différents namespaces. Dans chaque namespace, un pod Jenkins pilote les builds (compilation/packaging) et le déploiement
  • Artifactory : repository des artifacts

Dans ce cas d’utilisation, tout se passe dans OpenShift, plateforme de containers. La plateforme est structurée en namespaces. Chaque namespace correspond à un environnement technique d’un projet (VAL, INT, PROD, …).

Chaque namespace (§. 1.1 ci-dessous) dispose de son propre pod Jenkins. Celui-ci exécute, à chaque update du projet dans Github (déclenchement par webhook), un job de type pipeline qui comporte plusieurs phases (stages) :

    1. Git clone du repository Github
    2. Build par maven (compilation –> tests unitaires –> tests intégration –> packaging)
    3. Audit du code par sonar
    4. Déploiement exécuté en fonction d’une build configuration et d’une deployment configuration (§. 1.1 ci-dessous)
      • Création d’une image docker à partir des sources de l’application
      • Création d’un pod (host exécutant le microservice) incluant le container de cette image
      • Génération du service, interface externe correspondant aux différents pods d’une application, et joue le rôle de répartiteur de charge
      • Génération des routes qui permettent d’accéder au service d’un pod

Exemple d’un dashboard de pipeline Jenkins, illustré ci-dessous :

Par ailleurs, OpenShift assure d’autres fonctionnalités opérationnelles :

  • Il gère les variations de charge de l’application par l’intermédiaire d’un replication controller: ajuste le nombre d’instances d’un pod en fonction de la charge.
  • Il assure le monitoring des applications qu’il héberge
  • Il gère les logs : logs des builds et logs applicatifs
  • Il permet via une interface Web d’accéder et d’administrer toutes ces fonctionnalités (§. 1.3 ci-dessous)

 

1.1    Notion de namespace dans OpenShift

La notion de namespace dans OpenShift vient de Kubernetes dont il est une sur-couche. Elle permet de regrouper dans un même espace de nommage les ressources qui décrivent une application et comment elle doit être déployée. La figure ci-dessous regroupe tous les éléments qui constituent un namespace :

Contrairement à Kubernetes qui permet d’accéder à tous les namespaces d’un cluster, OpenShift encapsule chaque namespace dans un projet qui va permettre d’en contrôler les accès par un modèle d’authentification et d’autorisations basé sur les users et les groups.

Il existe 3 canaux pour interagir avec un cluster Openshift à travers son API Rest : une CLI (ligne de commande) oc, une interface Web et l’API elle-même. Plus de détails dans la documentation OpenShift.

1.2    CLI (Ligne de commande) OpenShift

L’objet de ce document n’étant pas d’exposer en détail le fonctionnement d’OpenShift, on se contentera de montrer ci-dessous quelques vues du déploiement d’un service de base de données PostgreSql en exécutant des commandes oc. Dans le paragraphe suivant, on verra les vues relatives à ce service dans la console Web.

  • Déploiement d’une base PostgreSql

  • Affichage de la description du déploiement dc/my-database, créé par la commande précédente :

1.3    Console web OpenShift

Ci-dessous, quelques vues dans la console Web relatives au déploiement décrit dans le paragraphe précédent :

  • Description du déploiement dc/my-database : on voit une représentation du pod correspondant. Les flèches haut/bas (encadrée en rouge) permettent d’augmenter/diminuer manuellement le nombre d’instances du pod.

 

  • Vue des services : Outre le service my-database, on distingue un autre service, (microservice springboot) déployé qui utilise le service my-database

  • Vue d’une route (URL) pour accéder au microservice :

  • Vue du monitoring :

 En résumé

Dans cet article ont été rappelés les concepts de DevOps en relation avec une approche agile du développement d’applications. Deux schémas architecturaux de chaînes DevOps sont présentés ayant en commun les outils de versionning et de repositories (GitHub et Artifactory) ainsi que Jenkins comme pilote de l’intégration et du déploiement continus.

Dans cette deuxième partie, la chaîne DevOps présentée utilise OpenShift. Celui-ci joue les rôles à la fois d’une plateforme de containers et d’un cloud privé. A travers la notion de namespace encapsulé dans un projet, il gère les aspects droits d’accès, cycle de vie des applications, répartition de charges, gestion des logs et monitoring.

La première partie de cet artice est consacrée à la présentation d’une autre chaîne DevOps.  Elle utilise Ansible pour exécuter les déploiements sur les différents nœuds et environnements de l’application. Ansible est installé sur un nœud unique et permet via les playbooks et son interface Ansible Tower d’automatiser le processus de déploiement sur un grand nombre de nœuds (serveurs, périphériques, …).

 Pour en savoir plus

Chaîne DevOps intégrant Jenkins avec Ansible ou OpenShift (1/2)

Introduction

Le but de cet article est de présenter brièvement le concept de DevOps, illustré par une implémentation concrète dans un environnement applicatif de microservices Restfull, développés dans le contexte d’une approche agile.

La première partie est consacrée au rappel des principes de DevOps et à la présentation du schéma architectural d’une chaîne DevOps avec Github, Jenkins, Artifactory et Ansible.

La deuxième partie présente une autre chaîne DevOps avec Github, Jenkins, Artifactory et OpenShift.

1      Agile et DevOps main dans la main

Image result for devops

Le développement d’applications dans le cadre d’une approche agile impose des livraisons fréquentes au rythme des itérations,  tout en préservant la qualité des livrables, la maîtrise des releases et l’adaptabilité à différents environnements techniques et applicatifs. Pour répondre à ces exigences, l’automatisation des processus est nécessaire ainsi qu’une synergie entre les équipes de développement et celles des opérations. C’est là que DevOps apporte la solution à ces problématiques d’intégration continue (IC) et de déploiement continu (DC).

2     Les apports de DevOps

  • Fréquence des déploiements accrue
  • Délai d’exécution réduit pour les modifications
  • Récupération plus rapide en cas de problème
  • Une sécurité plus robuste et mieux intégrée
  • Qualité accrue
  • Feedback rapide

3      Les outils de DevOps

A chacune des étapes du workflow automatisé de DevOps correspondent des outils open source ou propriétaires (ci-dessous une liste non exhaustive) :

  • Versionning-commit de code : SVN, Git, TFS, GitHub, Tuleap
  • Qualité de code : SonarQube, IntelliJ, Eclipse
  • Sécurité : SonarQube, Checkmarx, dependency-check-maven
  • Tests : JUnit, Mockito, Selenium, Postman, Gatling, Jmeter
  • Build : Maven, NPM
  • Intégration/Déploiement/Livraison continus : Jenkins, Teamcity, Bamboo, TFS
  • Gestion des releases : Nexus, Artifactory
  • Déploiement : Ansible, Docker, OpenShift, Azure, AWS, VmWare
  • Performance, monitoring : Nagios, Zabbix, Spring boot Actuator
  • Logs : Splunk, Logstash, Elasticsearch

4      Exemple d’une mise en oeuvre avec Ansible

La chaîne DevOps décrite ci-dessous permet d’assurer des déploiements sur différents environnements techniques : INT, VAL et PROD.

Elle est composée des éléments suivants :

  • GitHub
  • Git local installé sur chaque poste développeur
  • Jenkins assure les builds (compilation/packaging)
  • Artifactory : repository des artifacts
  • Ansible assure les déploiements


Dans ce cas d’utilisation, un job Jenkins pilote le build (compilation, tests unitaires automatisés, audit de code par SonarQube, packaging). L’artifact résultant (un jar, dans le cas d’un microservice springboot) est uploadé dans Artifactory.

En post-action, le job va déclencher un script Ansible chargé de déployer le microservice dans les différents nœuds d’un cluster.

4.1    Concepts utilisés dans Ansible

Ansible permet d’automatiser les tâches de configuration et de déploiement des applications. Son administration se fait par le biais d’une interface Web, Ansible Tower.

Cette administration est centralisée dans une seule machine, le Control Node, où est installé Ansible. Aucune installation d’un agent n’est faite sur les autres machines gérées par Ansible (Managed nodes). Les concepts énumérés ci-dessous permettent de comprendre le fonctionnement de Ansible :

  • Inventory (inventaire) : liste des nœuds gérés, spécifiant des informations telles que les adresses IP. Cette liste peut être organisée selon différents critères : localisation, fonction, …
  • Module: c’est une unité de code Ansible spécialisée sur un type de fonctionnalités qu’on peut invoquer pour exécuter des tâches : administration des utilisateurs sur un type spécifique de base de données, gestion des interfaces VLAN sur un type spécifique de périphérique réseau, etc …
  • Task: c’est une unité d’action Ansible qu’on peut exécuter par une commande
  • Playbook: Liste ordonnée de tâches, enregistrées à exécuter à plusieurs reprises. Les playbooks peuvent inclure des variables ainsi que des tâches. C’est des fichiers écrits en YAML.
  • Roles : les rôles sont définis pour charger automatiquement des fichiers de configuration, tâches et gestionnaires. Ils sont organisés sous forme de fichiers dans des répertoires ayant un nommage et une structure pré-définis. Les rôles sont invoqués dans les playbooks (comme les imports dans un fichier java).

Exemple de mise en œuvre d’Ansible dans   Integrating Ansible with Jenkins in a CI/CD process.

  En résumé

Dans cette première partie,  ont été rappelés les concepts de DevOps en relation avec une approche agile du développement d’applications. Un premier schéma architectural de chaîne DevOps est présenté ayant comme outil de versionning et de repositories GitHub et Artifactory,  Jenkins comme pilote de l’intégration et du déploiement continus.

Cette chaîne utilise Ansible pour exécuter les déploiements sur les différents nœuds et environnements de l’application. Ansible est installé sur un nœud unique et permet via les playbooks et son interface Ansible Tower d’automatiser le processus de déploiement sur un grand nombre de nœuds (serveurs, périphériques, …).

Dans la deuxième partie, un autre schéma architectural d’une chaîne DevOps utilisant OpenShift sera présenté.

  Pour en savoir plus

Comprendre l’approche Cloud Native…pour bien la vendre en interne.

Selon la Cloud Native Computing Foundation (CNCF), fondée en 2015 par la Linux Foundation, les technologies Cloud Native permettent aux entreprises de créer et d’exécuter des applications scalables au sein d’environnements modernes et dynamiques tels que le Cloud public, privé ou hybride.

En guise d’exemple de technologies, on peut citer les containers, le service mesh, les microservices, les infrastructures immuables, l’intégration continue ou encore les APIs déclaratives. Les applications créées avec cette approche sont généralement basées sur une infrastructure en tant que service IaaS ou une plateforme en tant que service PaaS qui sera ensuite combinée avec les outils précédemment cités.

Une approche Cloud Native présente plusieurs avantages.
Tout d’abord, elle permet aux entreprises de transformer plus rapidement leurs idées d’applications en produits disponibles sur le marché. Elle permet non seulement d’accélérer les changements, mais aussi d’en réduire les risques.

En outre, les technologies Cloud Native permettent aussi une scalabilité accrue pour les applications. À mesure que les entreprises attirent de nouveaux utilisateurs dans un plus grand nombre de régions et sur un plus grand nombre d’appareils, il est possible de préserver la réactivité de l’application et de maintenir les coûts.

Un autre avantage majeur est qu’il permet généralement aux entreprises de dépenser moins d’argent dans l’hébergement. Ainsi, il est possible de réduire efficacement les charges.

Les applications Cloud Native requièrent une architecture très différente des applications d’entreprise traditionnelles, et c’est pourquoi elles présentent de nombreuses différences avec les applications sur site.

Tout d’abord, alors que les applications sur site conçues pour être exécutées sur les serveurs de l’entreprise sont généralement écrites dans des langages traditionnels (C/C++, C#…), les applications Cloud Native sont écrites dans des langages centrés web comme le HTML, le CSS, le Java, le JavaScript, le .net, le Go, le Node.js, le PHP, le Python ou le Ruby.

Les applis Cloud Native présentent aussi la particularité d’être toujours à jour et disponibles. De leur côté, les applications sur site requièrent des mises à jour durant lesquelles elles sont indisponibles, et sont généralement proposées avec un modèle d’abonnement par le vendeur.

En cas de pic d’utilisation, les applications Cloud Native peuvent profiter de l’élasticité offerte par le Cloud en utilisant davantage de ressources. Ces dernières peuvent ensuite être désactivées lorsque l’utilisation retourne à la normale. C’est une flexibilité dont ne disposent pas les applis sur site.

Une application sur source peut aussi être exécutée au sen d’un environnement virtualisé et partager des ressources avec d’autres applications. En cas de panne de l’un des Data Centers du fournisseur de Cloud, le stack peut être immédiatement transféré vers une autre région. Le risque de downtime est donc largement réduit par rapport à une application sur site.

Grâce au Cloud, la gestion des applications Cloud Native est entièrement automatisée grâce aux outils d’automatisation et d’orchestration. De plus, les applications Cloud Native présentent un design modulaire, car un grand nombre de leurs fonctions peuvent être décomposées en microservices. Il est donc possible de désactiver certaines fonctions ou de déployer des mises à jour pour des modules spécifiques plutôt que pour l’application complète.

Besoins de compétences Cloud Native ?

Faites appel au centre de compétences Cloud Native DocDoku ou développez vos compétences grâce à nos offres de formations DevOps Cloud Native.

Source : d’après un article de Bastien L publié sur LeBigData

Quelles perspectives pour Java et le développement Mobile ?

Quelles perspectives pour Java et le développement Mobile ?

C’est la question posée aux intervenants et participants de notre dernier ApéroTech du 14 novembre dernier à Toulouse.

A la découverte de Flutter 

Pour lancer l’ApéroTech sur le sujet du futur du développement Mobile, Thibaud a présenté une alternative à React Native : Flutter, lancé par Google. Les participants ont ensuite pu bénéficier d’une démo en live de l’API Spatioport pour découvrir les capacités du framework.

Retour d’experiences croisées sur Oracle Code One 2019

L’ApéroTech s’est poursuivi avec l’intervention d’Olivier et Bertrand, heureux participants du dernier Oracle Code One à San Francisco qui ont fait le point sur l’avenir de l’écosystème Java.
JVM, GraalVM, JakartaEE, Microprofile et Kubernetes : de multiples bouleversements accompagnent les mutations des environnements d’exécution.
Les impressions à chaud de Bertrand sont à retrouver sur son article ici.

Merci aux intervenants et participants pour leurs échanges et leur belle énergie !

Nous avons hâte de vous retrouver en mars 2020 pour une nouvelle édition.

Informations et inscriptions sur la page Meetup Les Z’ApéroTech.

8 thématiques incontournables à connaître pour maîtriser HTML5

L’HTML va bientôt fêter ses 30 ans. Ce langage de balisage a beaucoup évolué depuis sa création, avec l’intégration de JavaScript et CSS (Cascading Style Sheets) qui sont venus grossir les rangs des technologies web.
Et côté utilisateurs, dynamiser dans son ensemble la navigation web.

Développer des applications web modernes comme le permet HTML5 et CSS3 ne s’improvise pas.
Voici le top 8 des sujets incontournables à connaître pour maîtriser ce langage :

  • Une brique essentielle : JavaScript
  • Les API de communication
  • Le stockage côté client
  • Graphisme et multimédia
  • Les nouveaux tags HTML5
  • La présentation avec CSS3
  • Architecture et conception
  • L’outillage et l’environnement de développement

Vous êtes Architectes, développeurs ou webmasters et certains de ces sujets vous échappent encore ?

Nous avons une piste pour vous permettre de mettre à jour rapidement vos compétences : rejoignez notre prochaine session de formation Développer des applications HTML 5.

Prochaines sessions HTML5 à Toulouse :
Du 26 au 28 février 2020 (dernières places disponibles!)
Du 18 au 20 mai 2020

Prochaines sessions HTML5 à Paris :
Du 22 au 24 janvier 2020
Du 18 au 20 mars 2020

 

Collaboration et efficacité au quotidien avec Git

Vous êtes chef de projets, développeur ou architecte et vous cherchez à gérer vos sources de façons innovante et efficace ?

Vous avez sûrement entendu parler de Git, le système de contrôle de version distribué en licence Open Source.
Mais l’avez-vous concrètement utilisé ?
Et le maîtrisez-vous ?

Devenu incontournable, Git est un gestionnaire polyvalent capable de gérer aussi bien les petits que les très gros projets informatiques.
Des équipes de développement réparties géographiquement peuvent bénéficier des fonctionnalités de Git, en permettant à chacun de travailler de manière déconnectée et de se resynchroniser au moment voulu.

Git s’impose aujourd’hui dans de nombreuses organisations mais ses fonctionnalités avancées le rendent inévitablement plus complexe que les gestionnaires de sources traditionnels.
Pour répondre à cet enjeu et maîtriser ses fonctions surpuissantes, pourquoi ne pas vous former à Git ?

Constitués de 50 % de travaux pratiques, profitez de 2 jours pour revoir les fondamentaux de Git, son utilisation efficiente au quotidien, maîtriser la gestion des branches et son usage en équipe.

Informations et inscriptions ici.

Prochaines sessions 2019 :
Toulouse, du 4 au 5 novembre
Paris, du 2 au 3 décembre

Découvrez un éclairage différent sur la Blockchain

Saviez-vous que la technologie Blockchain proposait des solutions concrètes pour :

  • Assurer la véracité et la pertinence de vos données
  • Obtenir une vision globale de votre chaîne de valeur
  • Coordonner parfaitement des process Métier hétérogènes
  • Trouver des solutions à la complexité et l’étendue de votre Supply Chain ?

La Team DocDoku vous propose de partager son éclairage sur les usages concrets de la Blockchain au cours d’un petit déjeuner le Vendredi 18 octobre 2019 à partir de 8h30. Commencez votre journée en découvrant comment la Blockchain peut se mettre au service de votre Métier, au travers de cas d’usage concrets mais également de best practices métier et techniques.

Programme
8h30-9h : Accueil
9h-9h15 : Définir la Blockchain et identifier les projets « Blockchain ready »
9h15-9h45 : Découvrir un usage de la Blockchain dans l’Aéronautique (MRO)
9h45-10h00 : Comprendre les enjeux techniques
10h00-10h15 : Réflexions et perspectives
10h15-10h30 : Echanges avec les participants

Accès libre sur inscription réservé aux acteurs de l’innovation (CTO, DSI, Responsable Métier, R&D, Data…)

Informations pratiques
Quand ? Vendredi 18 octobre de 8h30 à 10h30
Où ? Chez DocDoku, 76 allée Jean Jaurès, 31 000 Toulouse

Inscrivez-vous en ligne ici

Quarkus : le framework Cloud Native révolutionnaire

L’écosystème Java est en plein renouveau. Oracle a officiellement acté son désengagement de Java EE (renommé Jakarta EE), les spécifications et leur implémentation de référence se font désormais sous l’égide de la fondation Eclipse et non plus au JCP (Java Community Process). Si ce chamboulement a pu générer quelques craintes et appréhensions quant au futur de la plateforme, aujourd’hui les conditions de passation de témoin entre Oracle et Eclipse semblent dorénavant établies (voir ici pour plus de détails sur l’imbroglio au sujet de l’utilisation du nom de package javax).

Place donc à la technique et je dois dire, pour vivre les choses de l’intérieur (nous sommes membres du consortium Jakarta) que la dynamique Jakarta est bien réelle. Le tournant micro-service a été pris avec conviction (il était temps), l’axe Cloud Native est clairement une priorité pour le consortium.

Jakarta poursuit l’oeuvre de standardisation de Java EE et offre aux développeurs le choix de l’implémentation. Nous avons toujours la filière traditionnelle avec un modèle basé sur le serveur d’applications. Mais surtout nous avons désormais la déclinaison Microprofile du standard. L’application ne nécessite alors plus de serveur pour fonctionner mais est packagée sous la forme d’une application autonome taillée pour les containers Dockers et les clusters Kubernetes. L’approche prise par la spécification Microprofile est particulièrement intéressante : conserver tels quels les modules Jakarta EE (CDI, JAX-RS, JPA…) et y adjoindre de nouveaux composants dédiés aux nouvelles problématiques découlant des architectures microservices. Notamment sont couverts les sujets liés à la tolérance à la panne, l’authentification par token JWT, la documentation Open API, les métriques et les traces, le Health Check, la génération de clients REST, l’intégration aux services de messaging… Bien sûr pour chacun de ces thèmes, il y a d’un côté la spécification des API et de l’autres les implémentations.

Cela nous amène à Quarkus qui est la nouvelle proposition Microprofile de Red Hat. Je dis nouvelle parce qu’il y a déjà Thorntail qui est la déclinaison Microprofile du serveur d’application WildFly. Quarkus lui, est une réécriture pensée véritablement pour l’approche Cloud Native. En effet, l’objectif ultime de Quarkus est de permettre la compilation native des applications grâce à GraalVM et ainsi corriger les deux faiblesses historiques de Java que sont l’empreinte mémoire et le temps de démarrage. A une époque pas si lointaine où le modèle d’exécution était le gros serveur d’applications pas ou peu redondé qui devait être redémarré le moins souvent possible, ces deux lacunes posaient peu de problème. Aujourd’hui le paradigme a changé, les applications sont découpées en plusieurs unités d’exécution (microservices) qui sont démarrées, répliquées, déplacées sur un cluster Kubernetes de nombreuses fois par jour (potentiellement tout du moins). Il est ainsi crucial que nos processus soient faiblement gourmands en ressources et puissent être lancés juste en une fraction de seconde.

Quarkus est encore jeune, la compilation native échoue plus souvent qu’elle ne réussit, mais ce n’est pas bloquant, il suffit alors de se rabattre sur une exécution classique JVM qui reste bien plus performante que n’importe quel serveur d’applications ou que Thorntail. Quarkus nous montre le futur des applications Java d’entreprise orientées Cloud Native. Si vous êtes intéressés, n’hésitez pas à échanger avec nous ou à vous inscrire à une de nos sessions de formation.

 

AWS IoT Greengrass et NBitcoin au menu du 3ème ApéroTech DocDoku

Le 27 juin dernier avait lieu la 3ème édition des ApéroTech dans nos locaux de Toulouse.

Au programme, présentation de 2 thèmes portés par des collaborateurs passionnés, suivie par un bon moment de convivialité en ce début d’été.

AWS IoT Greengrass

Premier thème abordé : comprendre ce qu’est le Edge Computing et ses applications.
Passionné de nouvelles technologies et persuadé des applications prochaines de l’internet des objets, Pierre a donné aux participants les clés de cette notion et présenté en cas d’usage le fonctionnement de Greengrass ou comment intégrer AWS dans un contexte IoT.

A la découverte de NBitcoin

Benjamin a ensuite guidé les participants dans la découverte de #NBitcoin pour #Csharp : de la construction d’une transaction Bitcoin à son transfert, puis à sa diffusion sur le réseau.  Au delà de l’usage en tant que cryptomonnaie, les perspectives de cette technologie sont très variées et l’assemblée a pu découvrir d’autres applications innovantes du Bitcoin.

Merci à nos deux talentueux orateurs et rendez-vous à la rentrée pour un prochain ApéroTech.

Bel été à toutes et tous !

Réaliser une transaction avec NBitcoin C# .NET

Introduction

  • Par soucis de « vulgarisation » et d’accessibilité au plus grand nombre, certains concepts clés évoqués dans cet article ont été « simplifiés » ou « raccourcis ». Il conviendra à chacun d’étoffer sa recherche à l’aide des liens fournis dans la rubrique « Ressources ».
  • Généralement, les présentations autour de Bitcoin commencent par une approche théorique des mécanismes permettant de sécuriser le réseau.
  • Dans cette présentation nous allons faire abstraction de tout cela → ?.
  • Nous allons  considérer la blockchain Bitcoin comme un service de stockage décentralisé que l’on va exploiter en mode Blockchain As a Service.
  • Nous consommerons ce service à l’aide de la librairie NBitcoin.net et de l’api QBitNinja.
  • Nous auront une approche pratique, qui consistera à programmer et signer un transfert Bitcoin entre deux adresses que nous allons créer.
  • La notion de « transfert » et de « vérification de propriété » est fondamentale pour le réseau Bitcoin puisqu’il à été spécialement conçu pour cela !
  • Tout en construisant notre transfert, nous découvrirons notamment à quoi servent une adresse, une clé privé, une clé publique et une transaction …

Généralités sur Bitcoin

  • Un système de transfert et de vérification de propriété,
  • Repose sur un réseau pair à pair,
  • Pas d’autorité centrale,
  • L’application initiale et l’innovation principale de ce réseau est un système de monnaie numérique décentralisé,
  • L’unité de compte au sein du réseau Bitcoin est nommée « Bitcoin »,
  • Bitcoin fonctionne avec des logiciels et un protocole,
  • Permet à ses participant d’émettre et de gérer des transactions de façon collective et automatique,
  • Un protocole libre et ouvert dont le code source est publié sous licence MIT,
  • Bitcoin est conçu pour s’auto-réguler,
  • Son bon fonctionnement est garanti par une organisation générale que tout le monde peut examiner,
  • Tout y est public : protocoles de base, algorithmes cryptographiques, programmes opérationnels, données de comptes, débats des développeurs …

Bitcoin Transfert : Problématique

  • Des fonds initiaux ont été envoyés à Bob,
  • Bob à réalisé un transfert de Bitcoin à Alice en lui laissant un message,
  • Nous allons recréer la transaction entre Bob et Alice à l’aide de NBitcoin,

Configuration du projet (.netCore + VSCode)

Auteur de NBitcoin et QBitNinja.Client : Nicolas Dorier, METACO SA

$ mkdir btcMeetup

$ cd btcMeetup

$ dotnet new console

$ dotnet add package NBitcoin

$ dotnet add package QBitNinja.Client

$ dotnet restore

Eléments fondamentaux

Pour réaliser un transfert sur le protocole Bitcoin, on s’appuie sur 5 éléments fondamentaux :

Private Key :

  • Permet de signer une transaction,
  • Donne le droit de dépenser les fonds liés à une adresse,
  • Ne doit pas être partagé, doit être conservé en lieu sûr.

Public Key :

  • Permet de s’assurer que vous êtes le propriétaire d’une adresse pouvant recevoir des fonds,
  • Est générée à partir de la clé privée (l’inverse étant « impossible ») Permet de s’assurer que vous êtes le propriétaire d’une adresse pouvant recevoir des fonds,
  • La clé public permet de générer une adresse bitcoin et un scriptPubKey.

ScriptPubKey :

  • C’est un peu l’équivalent d’une BitcoinAddress mais au niveau du protocole.
  • Vous n’envoyez donc pas des fonds à une adresse mais à un ScriptPubKey.
  • N’est pas facilement partageable contrairement à une adresse.

Bitcoin Address :

  • Pour recevoir un transfert de fonds,
  • Information facilement encodable en QR Code, 
  • Peut être communiquée à tout le monde.

Transaction :

  • Structure de données permettant d’encoder un transfert de valeur entre un ou plusieurs participants au réseau Bitcoin.

Générer une adresse pour Bob et une adresse pour Alice

Une adresse Bitcoin est une information que vous allez partager avec les autres utilisateurs du réseau pour recevoir des fonds.

Mais pour obtenir une adresse vous devez d’abord générer une clé privée !

  • Elle est le seul moyen de dépenser les bitcoins envoyés à votre adresse.
  • Les clés privées sont personnelles et ne sont pas stockées sur le réseau.
  • Elles peuvent être générées sans être connectées à internet.

NBitcoin supporte plusieurs standards pour générer des clés privées …

Nous allons utiliser les standards BIP32 et BIP39 qui permettent de générer une clé racine à partir d’une wordlist. C’est sur ce principe que fonctionnent les cold-wallets tel que Ledger Nano ou Trezor. Cette wordlist ou seed ou mnemonic permet de re-générer indéfiniment la même suite de clé.

Ci-après le processus de génération de clés que nous allons suivre :

Etape 1 : Générer une nouvelle mnémonique :

Etape 1 (bis) : Restaurer une mnémonique existante :

Etape 2 : On génère la clé racine  qui dérive de notre « Mnémonique » et d’un mot de passe :

Etape 3 : On peut maintenant générer deux « sous-clés », une pour  Bob, une pour Alice, en dérivant de notre clé racine :

Remarque : Grâce à ce mécanisme de dérivation, il est possible de générer et régénérer des arborescences complexes de clés. On peut par exemple construire une arborescence de clés basées sur l’organigramme d’une entreprise.

Etape 4 : A partir de nos deux clés privées, nous allons pouvoir obtenir leurs clés publiques respectives :

  • On génère une clé publique à partir d’une clé privée au moyen d’une fonction à sens unique.
  • C’est à dire une fonction qui peut aisément être calculée mais difficilement inversée.
  • La clé publique permet de recevoir des fonds et d’attester que vous être le propriétaire d’une adresse.
  • En revanche, elle ne permet pas de dépenser les fonds d’une adresse.

Etape 5 : Après avoir généré les clés publiques d’Alice et Bob, nous allons pouvoir obtenir leurs adresses :

  • Il existe 2 réseaux Bitcoins : MainNet et TestNet,
  • On obtient une adresse Bitcoin à partir de sa clé publique, et en précisant le réseau sur lequel on souhaite opérer.
  • Remarque: sur le MainNet, les erreurs peuvent coûter cher ? !

Etape 6 : Consulter les comptes de Bob et d’Alice :

Adresse de Bob : 16joUFCsaVDfacYsDxCm2oF5mRyLTq6DvY

Adresse d’Alice : 13hA6HP6W4BCwmtr89CKh6fTra19Exm4bL

Si on regarde d’un peu plus près les informations fournies par BlockCypher concernant l’adresse de Bob, on peut remarquer deux choses plutôt curieuses au premier abord :

  • la différence entre le total des inputs et des outputs (0.000355 BTC) → Frais de minage.
  • Bob se renvoie à lui même une partie des fonds impliqués dans la transaction → Tous les fonds impliqués doivent être dépensés.

Etape 7 : Analyser une transaction Bitcoin

Pour envoyer des fonds de Bob vers Alice, il va donc nous falloir construire une transaction et la soumettre au réseau Bitcoin.

Les transactions sont au coeur du système Bitcoin, elles contiennent les informations relatives aux transferts de valeur entre les participants du réseau.

Pour envoyer des fonds à Alice, il va donc falloir faire référence à l’output d’une transaction qui à transféré des fonds initiaux à Bob et que ce dernier n’à pas dépensé …

En consultant la dernière transaction liées à l’adresse de Bob, on retrouve rapidement cette information :

Notre nouvelle transaction va donc « s’accrocher » à l’output non dépensé de celle-ci !

QBitNinja va nous permettre de récupérer ces informations afin de les manipuler dans notre application console.

Etape 8 : Récupérer une transaction stockée sur la blockchain avec le client QBitNinja :

Affichons l’intégralité de la transaction :

Résultat :

Etape 9 : Construire la transaction Bitcoin

Maintenant que l’ont sait quoi dépenser, on peut commencer à construire notre nouvelle transaction :

Et ajouter un nouvel input à cette nouvelle transaction :

Si on visualise notre transaction à ce stade, on obtient :

Voyons maintenant comment dépenser notre input …

On calcule la répartition des fonds entre les différents Outputs de notre future transaction en respectant le fait que :

  • tous les fonds en input de la transaction doivent être dépensés,
  • la différence entre le montant total des inputs et des outputs sera reversé au mineur,
  • des frais de minages trop faibles entrainent un traitement plus long de la transaction (voire elle n’est jamais traitée),

Pour calculer les frais de minages on peut directement chercher sur google :

  • plus vous payez, plus vite est traitée votre transaction,
  • il s’agit d’une moyenne, le prix est fixé en fonction du poids de votre transaction.
  • si vous ajoutez un message, vous devrez augmenter les frais.
  • pour calculer les frais adéquats :

https://bitcoinfees.earn.com/ (coût par byte)

La répartition des fonds entre les différents outputs est prête, on peut les créer et les ajouter à notre transaction :

Etape 10 : Joindre un message à votre transaction

Output contenant le message :

  • On peut donc stocker une information sans forcément transférer de la valeur, du moment que les frais de minage sont couverts !
  • Ce message est inaltérable et incensurable.
  • Vous payez pour écrire ce message, mais sa lecture est « gratuite », par n’importe qui ou n’importe quoi ayant accès au réseau Bitcoin.

Etape 11 : Signer la transaction avec votre clé privée :

Avant de pouvoir diffuser votre transaction, vous devez d’abord la signer à l’aide de votre clé privée :

Etape 12 : Diffuser la transaction sur le réseau Bitcoin :

Il est désormais temps de diffuser notre transaction au réseau !

Pour cela, il faut la transmettre à un noeud du réseau,

Plusieurs options s’offrent à vous :

  • Copier le code Hexadécimal de votre transaction et le pusher via un explorateur de blocks tel que BlockCypher :
  • Passer par l’API QBitNinja :

Etape 13 : Consulter l’état de la transaction diffusée :

Pour consulter l’état de notre transaction diffusée, rendez-vous sur un explorateur de Block tel que BlockCypher :

Transaction ID : a9172485a20e06b9a745f5105cd23cabd9f866e890b0bd4c25c3d29c073cce14

Si on consulte l’adresse d’Alice :

Conclusions et perspectives

L’application première du réseau Bitcoin est l’échange de la cryptomonnaie du même nom.

D’ailleurs sur ce réseau tout se paye en Bitcoins.

Mais il peut également servir à autre chose (et NBitcoin supporte tout cela):

  • stocker des données de façon indélébile et non-censurable,
  • émettre et détruire votre propre « tokens » pour représenter les parts d’une entreprise, des actions ou des votes,
  • attacher un « contrat ricardien » à un token (une sorte de smart contract lisible par l’homme et destiné au monde juridique).

Le protocole est en perpétuelle évolution et vise à devenir de plus en plus efficient (plus rapide, moins coûteux).

Il est également à noter que le coût de transaction n’est pas proportionnel au montant transféré et dépends de la « taille » en bytes de votre transaction. Ainsi, transférer 10$ ou 10M$ aura un coût de transaction équivalent !

Si aujourd’hui la lenteur et les frais de transaction élevés limite l’usage du réseaux, des mises à jour comme le « Lightning Network » ont le potentiel de changer la donne et maintenir Bitcoin sur le devant de la scène.

Ressources :

Programming the Blockchain in C# (Nicolas Dorier, Metaco SA)

NBitcoin (Nicolas Dorier, Metaco SA)

QBitNinja (Nicolas Dorier, Metaco SA)

Stratis Platform (Blockchain As A Service / BaaS, C# Smart-Contracts)

Microsoft Visual Studio Code (IDE)

IoT et Agriculture, SAFe et les secrets de JakartaEE8

Une nouvelle édition des ApéroTech a réuni il y a quelques jours une trentaine de passionnés de technologie dans les locaux de DocDoku à Toulouse.

IoT, méthodologie projet et programmation ont été les thèmes choisis et présentés par des intervenants particulièrement enthousiastes (et ce, alors que la partie Apéro n’avait pas encore commencé 😉 )

Les participants ont ainsi pu découvrir et échanger sur :

IoT : à la découverte d’une sonde connectée dernière génération.

Junior a immergé les participants dans la conception et le fonctionnement d’un device et de sa plateforme associée. Conçue en collaboration avec des agriculteurs pour connaître les besoins des sols en fonction des plantes cultivées, le projet présenté utilisait les technologies bluetooth4.0, Lorawan, Objenious, REST, PostgreSQL et Rails.

L’agilité à l’échelle de l’entreprise avec SAFe.

Bertrand a donné aux participants les clés de cette méthodologie, ses avantages, dans quels contextes l’utiliser sans toutefois oublier ses limites.

Les nouveautés de la plateforme #JavaEE ou plutôt #JakartaEE 8 .

Florent a fait le point sur les grandes familles de nouveautés à retrouver dans cette nouvelle plateforme : l’alignement entre modules, les nouveaux standards Web, les nouveaux modules…

Le prochain ApéroTech aura lieu le jeudi 18 avril à Paris.
Renseignez-vous et téléchargez votre invitation gratuite.