My garage door is accessible through a REST API

First of all, let me confess, the title is a bit sensationalist. A REST API? Yeah that’s quite exaggerated. But yes, I now can send a PUT request to a super mega secret URL from my smartphone and see my building garage door opening almost magically. Still quite cool, uh?

Garage door remote control prototype Raspberry PI

A few words about the project

Those who know me could say how much I love those powerful tiny and yet affordable computers that are Raspberry PI. You didn’t buy one already? Go for it now! Seriously. You don’t know what to do with it? No worries, you can do anything. And if you don’t know yet what to do, that’s the awesome part, you will find something to justify your purchase. The only limit is your imagination and it shouldn’t be limited too much.

OK, a few words about the project. At this point, you should have guessed that it involves a Raspberry PI. What else? Well, the idea is to prototype something that opens my garage door by sending a programmable request across Internet. There are a large number of solutions to solve this problem but I have a few constraints to deal with:

  • My building garage door, in fact, doesn’t belong to me. I can’t make any technical modifications to it and it’s completely excluded to plug cables on existing hardware
  • I don’t have accessible Wifi behind the garage door, but yay, I can access cellular data network
  • And of course, it should be fun and so, combine geeky solutions to solve the problem

Now that we know the constraints, let’s build our solution.

Let’s do it!

Hardware part, DIY

I am a web developer, this part has been the most difficult for me. But also maybe the funniest.

As I said before, I can’t make any modification to the existing garage door hardware. The solution I chose was to use one of the remote control I already had to open the garage door, associated to a pair of the Raspberry PI’s GPIO pins to send the correct radio signal which will open the door. If you don’t know what GPIO are, it stands for General Purpose Input/Output.

Here is a short extract of the official documentation:

One powerful feature of the Raspberry Pi is the row of GPIO (general purpose input/output) pins along the edge of the board, next to the yellow video out socket.

These pins are a physical interface between the Pi and the outside world. At the simplest level, you can think of them as switches that you can turn on or off (input) or that the Pi can turn on or off (output). Seventeen of the 26 pins are GPIO pins; the others are power or ground pins.

What are they for? What can I do with them?

You can program the pins to interact in amazing ways with the real world. Inputs don’t have to come from a physical switch; it could be input from a sensor or a signal from another computer or device, for example. The output can also do anything, from turning on an LED to sending a signal or data to another device. If the Raspberry Pi is on a network, you can control devices that are attached to it from anywhere** and those devices can send data back. Connectivity and control of physical devices over the internet is a powerful and exciting thing, and the Raspberry Pi is ideal for this.

You can get more informations here: https://www.raspberrypi.org/documentation/usage/gpio/

So, here is what my remote control looks like before and after being disassembled:

Badge remote control

Lucky me! First, the remote is powered by a 3V battery which should be easy to replace directly by the Raspberry. Secondly, the printed circuit board and mostly the button part which opens the door seem simple enough to suit my needs in customization.

After a few tries, I found the two weld points I needed to connect in order to simulate a constant push on the opening button. You can see me connecting them in the photo above.
Then I used a few tweaks to replace the battery and use the RPI’s GPIOs as an “on demand” power supply.

Below is the simplest electronical schema ever which shows the pins I used to connect the PI as a power supply on my remote:

RPI + remote control schema

As you can see, I used the first available 3V programmable output (pin #11) + the ground. That’s it.

Control the hardware

Now that the Raspberry PI is connected and ready to power up the remote, we need to control the GPIOs and see if everything is working as expected.

Again, there are multiple viable solutions to control your GPIO. One of the easiest is to install a utility named wiringPi (http://wiringpi.com) which allows you to read from and write values to the GPIOs, directly from your shell.

Setup can be done using git:

git clone git://git.drogon.net/wiringPi && cd wiringPi
./build

Just try the command gpio readall to see if the setup gone well. If so, you should see a mapping table displaying port by port the current mode (input or output) and the value associated.

Now we need to configure our GPIO #11 (logical port 0) in output mode and send voltage by executing the following commands:

gpio mode 0 out
gpio write 0 1

Yay! The remote led is blinking which means that from my laptop, via an ssh connection, I can now execute a shell command that sends a radio signal to open my building garage door on demand.

API

Now that we can send a radio signal from the PI to open the garage door, we need to create some API endpoint to send that very same radio signal with an HTTP request.

This part is from far the simplest. I used Silex PHP micro-framework and 5mns later everything was working. Of course, you should use whatever language or framework you’re comfortable with as long as you can execute shell commands from your program.

<?php

namespace GarageDoorHacks\Service;

use Symfony\Component\Process\Process;

class DoorService
{
    public function open()
    {
        $initGpioProcess = new Process('gpio mode 0 out');
        $initGpioProcess->run();

        $openCommandProcess = new Process('gpio write 0 1 && sleep 3 && gpio write 0 0');
        $openCommandProcess->run();
    }
}

You can find the complete repository here: https://github.com/bobey/garage-door-hacks

Access it from Internet

OK so, we have a basic API that allows us to control our door remote. Now is the time to make this API accessible through internet. As I said earlier, no trusted wifi is accessible from my garage. No worries, a 3g stick connected to the PI should do the trick.

I used a Huawei e169 well known for its compatibility with Raspbian and the PI. You can find one of those on Amazon for around €30.

To create the connection using GRPS/3G, you have, once again, multiple choices available. I chose to configure manually a new interface using ppp protocol.

To replicate the process, first, add a new network interface as follow:

#/etc/network/interfaces

auto ppp1
iface ppp1 inet ppp
provider sfr

I named my provider sfr because, guess what, I used a SFR sim card. Feel free to name it to whatever provider you wanna use.

Then, add the associated provider configuration in /etc/ppp/peers/sfr as follow:

user "sfr"
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T websfr"
/dev/ttyUSB0
noipdefault
defaultroute
replacedefaultroute
hide-password
noauth
persist
usepeerdns
unit 1

You may need to adapt this configuration to your own setup if you use a different provider. At this point, you should have a 3g interface named ppp1 that you can up or down using ifup or ifdown commands.

If everything work as expected, an ifconfig execution should output something as:

ppp1     Link encap:Point-to-Point Protocol
         inet addr:10.142.77.250  P-t-P:10.64.64.65  Mask:255.255.255.255
         UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
         RX packets:425 errors:37 dropped:0 overruns:0 frame:0
         TX packets:541 errors:0 dropped:0 overruns:0 carrier:0
         collisions:0 txqueuelen:3
         RX bytes:69244 (67.6 KiB)  TX bytes:111932 (109.3 KiB)

You might already know that mobile providers make use of CGN/LSN approach to optimize IPV4 distribution.

If you don’t, think of it as a NAT at large scale. Private IPs are allocated to customers and a network address translator maps multiple private IPs to one public IP. This technique is essentially used by internet providers to mitigate the IPv4 addresses exhaustion.

Like any form of NAT, it breaks the end-to-end principle and so, prevent us to use the public IP associated to our 3g stick to connect directly on our Raspberry, neither access the garage door API.

The once will not hurt, there are many ways to fix that issue all based around the same principle: SSH tunneling.

I already said how much I love the Raspberry and all the things you can do with it. I must say that I love the SSH protocol as well and I could write an entire article just about it. I know, it can sounds weird bug, if you never digged in advanced use cases of SSH, please do it. Tunneling, reverse tunneling, local port forwarding, remote port forwarding, socks proxy are part of the many amazing things SSH come pre-bundled with. And with them, an infinite of solutions to problems you might don’t even know you have 🙂

Here, I used a combination of ssh tunneling + remote port forwarding and a reverse HTTP proxy on my OVH server to make the API hosted on the Pi accessible worldwide. You might also use something like ngrok (https://ngrok.com/) to ease the process if needed.

The interesting part of the setup takes place in /home/pi/.ssh/config:

Host api-tunnel
    HostName      your.server.tld
    User          pi
    Port          22
    IdentityFile  ~/.ssh/id_rsa
    RemoteForward  2280 localhost:80
    ExitOnForwardFailure yes
    ServerAliveInterval 30
    ServerAliveCountMax 3
    StrictHostKeyChecking no
    UserKnownHostsFile=/dev/null

This file allows me to use the following command ssh -T -fN api-tunnel to create a tunnel which forwards my API local port on the public remote server configured.

To make it persistent and easily created at system startup, I used autossh command that you can add with a simple apt-get install autossh.

Then, all you have to do is to setup a reverse proxy (choose haproxy, nginx, apache or whatever solution fits your needs and preferences) on your public server.

And, get to that step, we can place our prototype around the garage door and call our public endpoint with something like curl -X PUT my.awesome.garage.api/door and, finally, tremble with joy while seeing the door opening.

Garage door opening with RPI

To go further

The main disadvantage of the prototype I described here is the fact that your RPI needs a 3g stick and the associated subscription if your garage is not wired with RJ45 network and if you can’t access any trusted wifi from there. This subscription could represents a money problem even if French (in my case) mobile providers such as SFR, Free or most MVNO now come with data plans cheaper than ever.

To fix that issue and build an even greater prototype, one of the first alternatives that comes to mind is a SMS based solution.

The idea is to command the garage door opening by sending a SMS directly to the Raspberry. This solution is quite cheaper of course because you only need a subscription to a mobile phone provider that allows you to receive SMS.

But the really cool part of that alternative is that you could now ask Siri “Please, send a message to My Garage and say ‘open now’” and see the magic happen without the need of developing any web-based interface.

And if you still really want something accessible through a REST API, an hybrid solution based on SMS to control the RPI + an API hosted on your own server should be a perfect alternative. When the API receives an HTTP request it then immediately sends a SMS to the PI via a “Twilio like” service for example. Still really cheap and embeddable in whatever app or service compatible with a REST API (IFTTT, Slack, you name it).

What’s the point, really?

Do I really need to open my garage door from Internet. Not so much actually. A few times a year, when my wife forget its own remote control or when my brother wants to park its moto inside my garage. But I had quite some fun building this prototype.

And after telling some of my colleagues and friends about the project and see their enthusiasm, I’m sure there are a lot of use cases that could be implemented with this kind of solution. Maybe you got one?

Entreprises, libérez la créativité de vos développeurs

Il y a plus d’un an que je n’ai pas pris le temps d’écrire sur ce blog. La dernière fois, je rédigeais un article sur Angular et l’implémentation d’un pattern Annuler / Rétablir. Quelques semaines plus tard, je quittais PMSIpilot à destination de RVIP pour changer d’air et d’horizon.

Beaucoup de choses ont changé depuis. L’univers du développement Web a évidemment continué sa course en avant. Nouveaux frameworks, nouveaux paradigmes, nouvelles tendances. Il y a un an, on suivait encore les propositions de specs pour Angular 2 sur Google docs, on entendait à peine parler de reactive programming ou de Typescript, ES2015 s’appelait encore ES6 et personne ne pensait à sérieusement mettre Docker en production.

Je me motive aujourd’hui pour rédiger un article un peu différent de ceux que j’écris habituellement sur ce blog puisque je ne vais pas parler de technique mais plutôt d’un retour d’expérience que nous avons mené au sein de nos équipes de développement chez RVIP.

Un poste typique de développeur

Je lisais ce matin-même un article intitulé, les développeurs sont devenus des cols bleus. Ci-dessous, un extrait qui illustre bien le propos de l’auteur quant à la vision du poste de développeur dans beaucoup d’entreprises :

Les développeurs sont souvent vus par leurs collègues et leur hiérarchie comme des ouvriers, des cols bleus. Leur tâche consistant à construire un outil qui servira le business mais qui ne constitue en soi aucune valeur. On me l’a d’ailleurs déjà dit en face: “en fait, ton boulot c’est de fabriquer nos outils”, comme si je ne participais pas à la création de valeur dans l’entreprise, que la seule valeur était produite par ceux qui utilisent l’outil pour faire du chiffre… Et vu sous cet angle, la technique n’est effectivement qu’un coût et rien d’autre pour l’entreprise. Coût qu’il convient de mesurer, de réduire et d’optimiser au mieux.

Je nuancerai tout de même le propos. J’ai de plus en plus la sensation qu’on avance dans le bon sens, que les entreprises hésitent moins à recruter des développeurs talentueux et passionnés plutôt que le premier venu dont le CV contient, au hasard d’une ligne, le nom du Framework utilisé en interne, ayant bien compris que tirer les salaires vers le bas n’est pas toujours rentable quand un business est fortement lié à la technique.

Dans tous les cas, il est vrai qu’on demande en général à un développeur de répondre aux besoins exprimés directement ou indirectement par un client (le PO par exemple), pas grand chose de plus.

Exemple d’une annonce de recrutement d’un développeur piochée au hasard (ici, au hasard = la première) sur RemixJobs :

Vos principales missions porteront sur :

  • L’analyse et la conception des nouvelles fonctionnalités définies par le Product Owner
  • Le développement de ces fonctionnalités
  • La recette et le suivi de mise en œuvre
  • La maintenance et la formation des équipes internes

Quand une entreprise recherche un développeur plus expérimenté, elle rajoute souvent à sa description de poste des mots clés tels que “Lead Developer”, “Référent Technique” ou encore “Architecte”. Mais rarement des mots clés type “Créativité”, “Liberté” ou “Recherche”. Ou alors, parfois, simplement pour décorer.

La créativité, pourquoi faire ?

Je suis un fervent défenseur des méthodologies agiles et notamment de SCRUM. Je suis bien sûr familier avec les concepts d’équipe, les rôles distincts de Product Owner, Développeur et Scrum Master et je ne les remet absolument pas en question.

Au sein de la méthode SCRUM, le développeur a un rôle crucial dans la réussite du projet et du produit sur lequel l’équipe travaille et il est fortement impliqué dans les différents processus de décision qui concernent ce projet / produit.

En revanche, je trouve que ces méthodes laissent en général trop peu de place aux “à-côté”.

L’équipe se concentre par itérations successives sur son projet et n’a pas souvent le temps de souffler. Les puristes de la méthode SCRUM sont d’ailleurs en général opposés aux pauses inter-sprint qui encouragent à rattraper le retard accumulé au sein de la dernière itération.

En fonction des organisations, l’équipe a même parfois du mal à inclure au sein des itérations des tâches qui n’apportent pas de valeur fonctionnelle visible pour le PO. Alors, imaginer proposer un développement ou une expérimentation qui n’est pas directement lié au projet en cours, c’est souvent mission impossible.

Pourtant, les développeurs n’ont pas forcément besoin d’avoir le rôle de PO pour avoir des idées géniales. Que ce soient des idées purement techniques (nouvelles technos, intégration d’outils tiers, API, …) des idées d’amélioration des produits existants (nouvelles fonctionnalités, nouveaux usages) ou même des idées de nouveaux produits (produit interne pour faciliter tel ou tel workflow, nouvelle offre destinée aux clients de l’entreprise).

Alors pourquoi ne pas leur laisser un peu de temps pour s’y consacrer ?

Expérience en organisation libre

Chez RVIP, nous avons mis en place au sein de nos équipes de développement une journée “libre-organisation”, qui a lieu tous les 15 jours.

L’objectif de cette journée tel que nous l’avons définit et présenté à l’ensemble de l’entreprise, est d’améliorer la productivité et le bien-être des équipes en encourageant notamment :

  • L’expérimentation
  • La créativité et l’innovation
  • L’auto-formation
  • Le partage de connaissances

Concrètement, c’est une période durant laquelle l’équipe sort du workflow classique du ou des projets sur lesquels elle travaille au quotidien pour se consacrer à d’autres activités. Une seule contrainte, envoyer un mail collectif à toute la R&D en fin de journée, pour expliquer ce sur quoi elle a travaillé et ce qu’elle en a retiré.

Libre à elle alors, de laisser son imagination et sa créativité dicter cette journée de travail.

Les résultats après quelques semaines

Après quelques semaines d’expérimentation, le bilan est dans l’ensemble extrêmement positif.

En vrac, les équipes ont pu s’essayer et monter en compétences sur :

  • Docker + écosystème (Rancher)
  • Les bases de données orientées graph (Neo4J, OrientDB)
  • Angular
  • Elastic Search / ELK

Mais également :

  • Entamer la refonte technique de différentes applications existantes
  • Réaliser des POC d’applications hors de la Roadmap produits
  • Prototyper une archi à base de Raspberry

Et certainement beaucoup d’autre choses que j’oublie.

Mais la montée en compétence sur un ensemble de technos n’est pas le seul bénéfice mesurable de ces journées en libre organisation. Elles sont presque toujours à l’origine d’un pic de motivation et de passion des équipes dont l’effet positif perdure bien au delà, au cours des semaines de développement SCRUM “classiques”.

Nous avons bien sûr aussi rencontré quelques écueils. Le principal étant sans doute le fait qu’il n’est pas facile de justifier et maintenir cette journée libre en période de rush.

D’autre part, il n’est pas non plus toujours facile de trouver une idée intéressante à expérimenter et de se lancer dessus sans le cadre d’une journée SCRUM classique.

Enfin, au delà de la R&D et des équipes produits, faire comprendre les objectifs et les intérêts de ce type d’expérimentation au reste de l’entreprise est un véritable challenge en soit.

Quelques pro tips

  • Entamer cette journée par un brainstorm rapide sur les idées de l’équipe + vote de chacun
  • Tout seul ou en équipe ? Privilégier le travail en équipe. C’est un moment de cohésion, presque de Team-Building
  • Revenir ASAP dans un workflow agile type SCRUM pour la maintenance et les évolutions des produits éventuellement prototypés durant une ou plusieurs journées libres
  • Faire des retours réguliers à la R&D mais aussi à l’ensemble de l’entreprise

Aller plus loin ?

Nous nous sommes concentrés jusqu’alors essentiellement sur des expérimentations purement techniques lors de ces journées libres.

Nous aimerions également en profiter pour tester de nouveaux workflows, de nouvelles méthodes de travail. Je pense par exemple au Mob Programming que Bertrand et son équipe ont expérimenté chez PMSI et dont le retour très intéressant est consultable ici.

Il serait également intéressant d’élargir l’expérience à d’autres services de l’entreprise. Je suis persuadé que, si les développeurs ont presque toujours des idées innovantes intéressantes pour l’entreprise, ils ne sont pas les seuls. Il serait génial de voir ce qu’une équipe commerciale par exemple ferait d’une journée en organisation libre…

Enfin, pourquoi ne pas aller encore plus loin en allouant un budget à ces journées libres et voir ce que les équipes sont capables de produire en ayant la responsabilité de le gérer et de l’utiliser dans le cadre définit ci-dessus : expérimentation, créativité et innovation, auto-formation et partage de connaissances.

AngularJS

Implémenter un « annuler / rétablir » avec AngularJS et le Command Pattern

Edit 18/11/2014

Une démo de l’implém. présentée dans cet article est visible à l’adresse suivante : http://bobey.github.io/angular-undo-redo/

Introduction

Les applications web que nous sommes capables de développer rivalisent depuis quelques années déjà avec leurs équivalents desktop. Il n’y a qu’à voir pour s’en convaincre, la puissance des outils en ligne tels que Google Docs ou Google Spreadsheet qui, notamment depuis leur refonte avec le Material Design de chez Google, n’ont vraiment plus grand chose à envier aux applis natives.

Pour arriver à un tel niveau d’interactivité au sein du navigateur, il a fallut évidemment compter sur Ajax, les librairies JS type prototype ou jQuery, l’émergence d’HTML 5 et, plus récemment, des frameworks frontend MV* type Backbone, EmberJS ou Angular. Avec ces derniers, nous sommes en mesure de développer des applications toujours plus complexes et toujours plus user-friendly. Les composants d’UI réutilisables open-sourcés (directives Angular, Components Ember ou React, …) sont légion sur Github et nous permettent à nous, développeurs, de nous concentrer sur la partie métier de notre application.

Mais si il y a bien une fonctionnalité qu’on a toujours du mal à implémenter au sein de nos applications, c’est le Annuler / Rétablir (ou le Undo / Redo, Cancel / Restore, …).

Les libs undo / redo pour Angular

Pour AngularJS, il existe plusieurs directives et services permettant d’ajouter un annuler / rétablir en observant automatiquement les modifications effectuées sur un objet du scope. Je pense notamment à Angular-Chronicle et Angular History. Ce type d’approches est très pratique lorsque les interactions de l’utilisateur avec l’application se limitent à la MAJ des propriétés de quelques objets de tel ou tel scope. En revanche, elles trouvent vite leur limite dans une application plus complexe où le frontend dialogue par exemple avec le backend via une API REST pour ajouter au supprimer des éléments dans une base de données quelconque.

Le Command Pattern

Pour répondre à cette problématique de manière plus globale qu’en observant automatiquement les changements sur un objet du scope, une des approches les plus connues est l’utilisation d’un dérivé du Pattern Command.

Sur Wikipedia, voilà ce qu’on dit en introduction pour expliquer ce qu’est le command pattern :

In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters.

Ce pattern est beaucoup utilisé pour implémenter les comportements d’UI (clic sur un bouton d’IHM) mais est aussi particulièrement bien adapté pour modéliser notre fameuse fonctionnalité undo / redo. Toujours pour citer Wikipédia :

If all user actions in a program are implemented as command objects, the program can keep a stack of the most recently executed commands. When the user wants to undo a command, the program simply pops the most recent command object and executes its undo() method.

Command Pattern et AngularJS

Nous allons voir comment architecturer notre application Angular autour du Command Pattern pour rendre possible l’annulation sur la création / suppression des éléments d’un listing d’utilisateurs.

Commençons par décrire le partial associé au listing des utilisateurs. Pour chaque utilisateur, on affiche simplement le username et un bouton de suppression à côté.
On place également un bouton d’ajout d’un nouvel utilisateur au listing, au dessus de ce dernier. On prévoit enfin deux boutons Annuler et Rétablir qui, dans une application réelle auraient plutôt leur place dans une directive spécifique, affichée de manière générique sur le layout global.

Ci-dessous, le code épuré associé :

// src/partials/users/list.html
<h1>Liste utilisateurs</h1>

<a href data-ng-click="listCtrl.undo()">Annuler</a> -
<a href data-ng-click="listCtrl.redo()">Rétablir</a> -
<a href data-ng-click="listCtrl.addUser()">Ajouter</a>

<ul>
    <li ng-repeat="user in listCtrl.users | orderBy:'name'">
        <a href ng-click="listCtrl.removeUser(user)">Supprimer</a> -
        <strong>{{ user.username }}</strong>
    </li>
</ul>

Associé à ce template, un controller Angular « classique » pourrait être implémenté de la manière suivante :

// src/js/controllers/user-list-controller.js
/** @ngInject */
function UserListController(users, User, UserService) {

    this.users = users;

    this.undo = function() {
        // TODO
    };

    this.redo = function() {
        // TODO
    };

    this.removeUser = function(user) {

        UserService.remove(user).then(function() {
            $state.reload();
        });
    };

    this.addUser = function() {

        // Dans un cas réel, ces données proviendraient sans doute de la soumission d'un formulaire quelconque...
        var user = new User({
            username: 'obalais',
            firstname: 'Olivier',
            lastname: 'Balais'
        });

        UserService.save(user).then(function() {
            $state.reload();
        });
    };
}

UserListController.resolve = {

    /** @ngInject */
    users: function(UserService) {
        return UserService.getUsers();
    }
};

angular
    .module('awesomeapp.controllers')
    .controller('UserListController', UserListController);

La logique de récupération / sauvegarde de nos utilisateurs est externalisée dans un service Angular UserService et notre controller ne fait qu’appeler ce service et mettre à jour son scope en fonction des réponses reçues. Le UserService en question peut échanger avec une API REST, via des Web sockets, stocker et retrouver les utilisateurs en mémoire ou dans le localStorage, les détails d’implémentation ne sont pas important…

Il est temps maintenant de modifier notre implémentation en s’inspirant du Command Pattern. Le principe théorique est relativement simple. Comme expliqué sur Wikipédia, il s’agit de représenter toutes les actions utilisateurs annulables sous forme de commandes implémentant la logique d’exécution et la logique d’annulation puis de conserver dans une stack la liste de ces commandes exécutées.

Voici donc une interface possible pour nos commandes :

function Command () {
}

Command.prototype = {
    execute: function() {
        // return promise
    },

    reverse: function() {
        // return promise
    }
};

Pour gérer l’empilage et dépilage de ces commandes, il nous faut définir un service UndoService.
Ce dernier expose trois méthodes executeCommand, undo et redo.
Une implémentation possible est détaillée ci-dessous :

/** @ngInject */
function UndoService ($q) {

    var undos = [],
        redos = [];

    /* jshint -W004 */
    var UndoService = {};

    UndoService.undo = function() {

        if (!undos.length) {

            var deferred = $q.defer();
            deferred.resolve('nothing to undo');

            return deferred.promise;
        }

        var command = undos.pop();

        return command.reverse().then(function(data) {
            redos.push(command);

            return data;
        });
    };

    UndoService.redo = function() {
        if (!redos.length) {
            var deferred = $q.defer();
            deferred.resolve();

            return deferred.promise;
        }

        var command = redos.pop();

        return command.execute().then(function(data) {
            undos.push(command);

            return data;
        });
    };

    UndoService.executeCommand = function(command) {
        return command.execute().then(function(data) {
            undos.push(command);

            return data;
        });
    };

    return UndoService;
}

angular.module('awesomeapp.services')
    .factory('UndoService', UndoService);

Parmi les quelques parti-pris, on note que l’exécution d’une commande réinitialise systématiquement la stack des redos. On constate également que suite à un undo, on vérifie que la promesse a bien été résolue avant d’ajouter la commande dans la stack des redos, et vice-versa.

Pour pouvoir refactorer notre UserListController, nous avons besoin d’implémenter deux commandes : UserCreateCommand et UserDeleteCommand.

Commençons par la commande UserCreateCommand :

// src/js/commands/user-create-command.js
/** @ngInject */
function UserCreateCommand (UserService) {

    /* jshint -W004 */
    function UserCreateCommand (user) {
        this.userToCreate = user;
        this.createdUser = null;
    }

    UserCreateCommand.prototype = {
        execute: function() {
            return UserService.save(this.user).then(function(createdUser) {

                this.createdUser = createdUser;
                return createdUser;
            }.bind(this));
        },

        reverse: function() {
            return UserService.remove(this.createdUser).then(function() {
                this.createdUser = null;
            }.bind(this));
        }
    };

    return UserCreateCommand;
}

angular
    .module('awesomeapp.commands')
    .factory('UserCreateCommand', UserCreateCommand);

Et maintenant la commande UserDeleteCommand :

// src/js/commands/user-delete-command.js
/** @ngInject */
function UserDeleteCommand (UserCreateCommand) {

    /* jshint -W004 */
    function UserDeleteCommand (user) {

        var userToCreate = Angular.copy(user);
        userToCreate.setNew(false);
        this.createCommand = new UserCreateCommand(userToCreate);
        this.createCommand.createdUser = user;
    }

    UserDeleteCommand.prototype = {
        execute: function() {
            return this.createCommand.reverse();
        },

        reverse: function() {
            return this.createCommand.execute();
        }
    };

    return UserDeleteCommand;
}

angular
    .module('awesomeapp.commands')
    .factory('UserDeleteCommand', UserDeleteCommand);

Comme vous pouvez le voir, l’implémentation de cette dernière repose sur la logique dores et déjà définie dans la commande UserCreateCommand, évitant ainsi de dupliquer inutilement du code.

Il ne nous reste alors plus qu’à modifier notre controller pour passer systématiquement par le UndoService :

// src/js/controllers/user-list-controller.js
/** @ngInject */
function UserListController(users, User, UserCreateCommand, UserDeleteCommand, UndoService) {

    this.users = users;

    this.undo = function() {
        UndoService.undo().then(function() {
            $state.reload();
        });
    };

    this.redo = function() {

        UndoService.redo().then(function() {
            $state.reload();
        });
    };

    this.removeUser = function(user) {

        var deleteCommand = new UserDeleteCommand(user);

        UndoService.executeCommand(deleteCommand).then(function() {
            $state.reload();
        });
    };

    this.addUser = function() {

        var user = new User({
            username: 'olivier.balais',
            firstname: 'Olivier',
            lastname: 'Balais'
        });

        var createCommand = new UserCreateCommand(user);

        UndoService.executeCommand(createCommand).then(function() {
            $state.reload();
        });
    };
}

UserListController.resolve = {

    /** @ngInject */
    users: function(UserService) {
        return UserService.getUsers();
    }
};

angular
    .module('awesomeapp.controllers')
    .controller('UserListController', UserListController);

Le mot de la fin

L’architecture de base est en place et nous avons vu deux commandes basiques pour créer et supprimer des utilisateurs.

Toute la difficulté désormais pour le développeur réside dans l’implémentation de ces différentes commandes notamment dans les cas plus complexes où la modification d’une entité a des impacts sur d’autres objets de l’application. Chaque commande doit contenir TOUTE la logique nécessaire à son exécution bien sûr, mais surtout à son reverse.

Vous pouvez retrouver le code de cet article sur ce dépôt Github.

ReactJS

React.js, Grunt, browserify et CDN

Lorsqu’on attaque le développement d’une application frontend en Javascript, plusieurs questions se posent immédiatement pour le développeur :

  • Quels frameworks et librairies (Angular, Ember, React et Flux, …)
  • Comment organiser les fichiers sources (organisation technique, organisation fonctionnelle, …)
  • Comment gérer les modules (AMD compliant, CommonJS compliant, …)
  • Quel outil de build (Grunt, Gulp, …)

Dans cet article, nous allons voir comment créer un HelloWorld avec ReactJs, en utilisant Grunt pour construire l’application à partir des sources et browserify pour gérer les dépendances entre nos modules front et libs externes.

Vous pouvez retrouver le code final et les différentes étapes dans ces quelques commits poussés sur Github.

Sur la documentation du site officiel de React, un HelloWorld ressemble typiquement à ça :

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/JSXTransformer.js"></script>
  </head>
  <body>
    <div id="content"></div>

    <script type="text/jsx">
      /** @jsx React.DOM */
      var hello = React.createClass({
        render: function() {
          return <div>Hello, {this.props.name}!</div>
        }
      });

      React.renderComponent(
        <hello name="World" />,
        document.getElementById('content')
      );
    </script>

  </body>
</html>

Notre composant React.js est implémenté en plein milieu de notre code HTML, le JSX est transformé en JS à la volée par JSXTransformer au sein du navigateur et le  tout, dans un seul fichier index.html. Pas terrible évidemment dans le cadre d’une « vrai » application.

Intégration de Grunt avec React

Facebook fournit un outil en ligne de commande très bien fait pour transformer côté serveur le JSX en JS.

npm install -g react-tools
jsx --watch src/ build/

Mais dès que l’application grossit, qu’on ajoute des assets, qu’on veut compiler des fichiers LESS ou SASS, minifier nos sources et autres joyeusetés, l’utilisation d’un outil de build tel que Grunt ou Gulp devient vite indispensable. Il y a beaucoup d’exemples d’intégration de React avec Gulp sur le net donc pour faire original (ou old school), nous allons utiliser Grunt.

npm install -g grunt-cli
npm install react --save-dev
npm install grunt --save-dev

# pour copier nos assets :
npm install grunt-contrib-copy --save-dev

Pour rappel, React utilise une extension au Javascript appelée JSX dont l’objectif est de faciliter l’implémentation d’un composent UI (~HTML) au sein de code javascript. Facebook a même proposé un draft de spécification de la syntaxe JSX :

JSX is a XML-like syntax extension to ECMAScript without any defined semantics. It’s NOT intended to be implemented by engines or browsers. It’s NOT a proposal to incorporate JSX into the ECMAScript spec itself. It’s intended to be used by various preprocessors (transpilers) to transform these tokens into standard ECMAScript.

Différentes approches sont possibles pour intégrer Grunt avec React. Il existe un plugin grunt-react très bien fait pour transpiler automatiquement les JSX en JS.

Ici, nous allons utiliser une autre approche en gérant nos dépendances entre modules avec Browserify et en intégrant la traduction des JSX via un transformer browserify.

Intégration de Browserify

Browserify est une lib de gestion de modules clients (browser) compatible CommonJS (« à la » nodejs).

Browserify lets you require(‘modules’) in the browser by bundling up all of your dependencies.

Evidemment, on retrouve un plugin grunt pour l’intégrer à notre build.

npm install grunt-browserify --save-dev
// Au sein de notre Gruntfile
grunt.loadNpmTasks('grunt-browserify');

Nous verrons la configuration proprement dite du plugin un peu plus loin.

Structuration du code

Maintenant que nous avons intégré browserify, nous pouvons structurer notre code source de manière un tout petit peu plus évolutive que dans le HelloWorld de base. Ci-dessous un exemple avec un dossier src/ et un dossier de build/. Les jsx (composants) sont regroupés dans src/jsx/ et l’index.html est à la racine du dossier src/. Cette structure n’a d’intérêt que pour montrer l’utilisation de browserify et de l’outil de build mais mériterait sans aucun doute d’être améliorée dans le cadre d’une application complexe avec plusieurs composants, différents domaines fonctionnels, etc. On trouve d’ailleurs quelques starter-kits intéressants sur Github pour commencer une application Flux / React.

structure

En reprenant le HelloWorld présenté au début de cet article et pour suivre la structure décrite ci-dessus, nous allons découpé les sources en trois fichiers :

  • src/jsx/hello.jsx qui contiendra l’implémentation du composant React hello
  • src/jsx/app.jsx qui contiendra la partie instanciation du composant
  • src/index.html qui contiendra le layout du HelloWorld

Le point d’entrée de notre application est app.jsx, au sein duquel nous chargeons nos dépendances (frameworks, lib, composants) avec Browserify (require(‘…’)).

ReactJs étant nativement compatible CommonJS, un require(‘react’) est suffisant :

// src/jsx/hello.jsx
var React = require('react');

var Hello = React.createClass({

  render: function() {
    return <div>Hello, { this.props.name }!</div>
  }
});

module.exports = Hello;

Pour rendre notre composant Hello importable depuis un autre fichier source, on utilise module.exports en fin de fichier. Il ne nous reste plus qu’à importer notre composant depuis app.jsx :

// src/jsx/app.jsx
var React = require('react'),
    Hello = require('./hello.jsx');

React.renderComponent(
  <Hello name="World" />,
  document.getElementById('content')
);

Notre layout index.html n’a alors plus qu’à importer app.js, bientôt buildé via grunt :

<!DOCTYPE html>
<html>
    <head>
        <title>Hello ReactJS</title>
    </head>
    <body>
        <div id="content"></div>
        <script src="js/app.js"></script>
    </body>
</html>

Branchement de l’ensemble avec notre Gruntfile

Pour brancher le tout, nous ajoutons à notre Gruntfile la configuration de browserify avec le transformer reactify et nous configurons également grunt-contrib-copy pour copier notre layout (et les autres assets éventuels) :

// Gruntfile
module.exports = function(grunt) {
    grunt.initConfig({
        browserify: {
            options: {
                debug: true,
                extensions: ['.jsx'],
                transform: ['reactify']
            },
            hello: {
                src: 'src/jsx/app.jsx',
                dest: 'public/js/app.js'
            }
        },
        copy: {
            all: {
                expand: true,
                cwd: 'src/',
                src: ['index.html'],
                dest: 'public/'
            }
        }
    });

    grunt.loadNpmTasks('grunt-browserify');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('default', ['browserify', 'copy']);
};

Il ne nous reste plus qu’à tester en exécutant la commande :

grunt

Normalement, on obtient alors auto-magiquement un gros fichier JS qui contient React, notre composant Hello et notre micro-application démo.

Et si on ouvre notre fichier index.html dans un navigateur, on obtient un beau HelloWorld sur fond blanc…

Et pour profiter d’un CDN ?

Comme vous avez pu le constater ci-dessus, lors du build, ReactJS se retrouve dans notre gros fichier app.js. Il peut être intéressant d’extraire les vendors pour différentes raisons :

  • Le temps nécessaire au build à chaque modification (pour peu qu’on utilise un plugin watch avec Grunt) augmente proportionnellement avec la taille des fichiers à parser. Ainsi, parser tout ReactJS n’est pas très optimisé
  • Les vendors changent moins souvent et peuvent avantageusement profiter d’une mise en cache navigateur. Pas besoin de recharger tout ReactJS à chaque modification d’une ligne de code dans notre application

Pour cela, on génère souvent au moins deux fichiers JS lors du build, un pour les sources de l’application et l’autre pour les vendors qui changent peu souvent. Dans notre exemple, la seule lib utilisée est ReactJS. Pourquoi alors ne pas profiter carrément d’un chargement de React via un CDN ? Tout en conservant nos require(‘react’) au sein de notre code…

Pour arriver à cela, une astuce constiste à spécifier à browserify que React est une librairie externe :

browserify: {
    options: {
        // [...],
        external: ['react']
    },
    // [...],
}

On utilise en parallèle le package npm browserify-shim, qui sert d’ordinaire à rendre browserifiables des libs qui ne le sont pas nativement :

npm install browserify-shim

On déplace alors les transform browserify directement au sein du package.json en rajoutant browserify-shim et en lui spécifiant l’emplacement du module React au sein de window.

Notre package.json ressemble alors à ça :

// package.json
{
    "browserify": {
        "transform": [
            "reactify", "browserify-shim"
        ]
    },
    "browserify-shim": {
        "react": "global:React"
    },
    "dependencies": {
        "reactify": "^0.14.0",
        "react": "^0.11.2"
    },
    "devDependencies": {
        "grunt": "^0.4.5",
        "grunt-browserify": "^3.1.0",
        "grunt-contrib-copy": "^0.7.0",
        "reactify": "^0.14.0",
        "browserify": "^6.2.0",
        "browserify-shim": "^3.8.0"
    }
}

Et notre fichier Gruntfile modifié complet :

// Gruntfile
module.exports = function(grunt) {
    grunt.initConfig({
        browserify: {
            options: {
                debug: true,
                extensions: ['.jsx'],
                external: ['react']
            },
            hello: {
                src: 'src/jsx/app.jsx',
                dest: 'public/js/app.js'
            }
        },
        copy: {
            all: {
                expand: true,
                cwd: 'src/',
                src: ['index.html'],
                dest: 'public/'
            }
        }
    });

    grunt.loadNpmTasks('grunt-browserify');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('default', ['browserify', 'copy']);
};

Il ne nous reste plus qu’à charger React au sein de notre layout, via un CDN, à l’ancienne :

<!DOCTYPE html>
<html>
    <head>
        <title>Hello ReactJS</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/react.js"></script>
    </head>
    <body>
        <div id="content"></div>
        <script src="js/app.js"></script>
    </body>
</html>

Pour aller plus loin

Le but de cet article était de montrer comment partir d’un HelloWorld typique ReactJS et l’étendre pour y intégrer une gestion des dépendances avec Browserify, Grunt comme outil de build et le tout en chargeant ReactJS via un CDN.

Pour aller plus loin, voici quelques axes d’amélioration en vrac :

  • Générer des sourcemaps pour nos fichiers JS / JSX
  • Minifier / uglifier notre application

Vous pouvez retrouver le code final et les différentes étapes dans ces quelques commits poussés sur Github.