Créer un service cloud en moins de 500 lignes

En voici un titre racoleur ! Du moins dans le monde l’IT… J’espère ne pas lancer une mode qui s’apparenterait aux solutions pour maigrir en moins de 7 jours ou d’apprendre le Javanais en moins d’un mois mais je relève néanmoins le défi énoncé dans le titre.

Par la formulation “créer un service”, je souhaite proposer l’optique du service dans son ensemble. Cela comprend le langage de programmation, les frameworks employées mais aussi le système de stockage des données, la diffusion du service en terme de plateformes et enfin son hébergement.
Pour n’en dire qu’un mot, l’IT évolue beaucoup actuellement. Nous étions encore récemment concentrés sur les nouveaux frameworks pour l’infrastructure en place, aujourd’hui toutes les briques sont remises en cause : langages de programmation (Scala, Groovy, JavaScript, Dart, TypeScript…), bases de données (MongoDB, Neo4j, Redis…), et l’hébergement avec tout ce qui touche au cloud. En somme, sans oser avancer que cette application est l’application du futur, aborder ce que pourrait être l’application du futur nécessite une perspective très large.

Qu’est ce qu’un service cloud

Comme beaucoup de notion marketing de l’IT, le cloud est certainement plus une idée qu’une technologie à proprement parlé. Plutôt que de le définir de façon absolue, je proposerai donc ici ma vision, ou au moins celle qui a guidé le design de l’application.
Un service cloud est un service accessible via Internet sur un nombre important de plateformes : ordinateurs, tablettes et smartphones (je n’ai volontairement cité aucune marque ou système d’exploitation). Chaque utilisateur du service peut y trouver un espace qui lui est personnel et communiquer avec les autres utilisateurs du service. Lorsqu’il passe d’un appareil à un autre pour utiliser le service, il y retrouve son espace.
La traduction technique de ce cahier des charges correspond à un service proposant une interface accessible sur un maximum de plateformes (Web, Android, iOS…) proposant un hébergement et un stockage de données aux performances et au cout très extensible afin d’être capable de gérer le succès du service (on ne sait jamais !).

Le vif du sujet

Derrière un projet comme celui-là, il faut une idée de service. Pour cet article, je vous en propose une qui, à priori, n’est ni révolutionnaire, ni inintéressante, donc plutôt adapté à son utilisation ici. Il s’agit de créer des espaces graphiques partagés permettant d’échanger avec d’autres utilisateurs de façon graphique. Je l’ai simplement appelé “Shared Whiteboard”. Le service repond aux exigences du cloud par la persistance du contenu des “tableaux”, la diffusion du service sur le Web, les tablettes et les mobiles (les interfaces tactiles permettent de dessiner avec le doigt). Les échanges se font par le partage d’un même “tableau”.

Parlons technique

Nous voici arrivés au moment de choisir tous les composants techniques de notre service. Bien sûr, chaque choix pourrait être soumis à débat, je vous décrirai simplement les choix qui ont été faits et leurs justifications.

Côté serveur

Le serveur n’a pratiquement aucune règle de gestion à implémenter, son rôle sera uniquement de recevoir les modifications sur les tableaux et de dispatcher ces modifications aux autres clients connectés au même tableau et au système de stockage. Node.js apparait alors comme un choix tout désigné : très performant sur de petites tâches d’I/O, facile à héberger et très facile d’utilisation pour faire des WebSockets.
Quelques lignes Node.js pour démarrer un serveur HTTP (avec express.js)

var _ = require("underscore"); var express = require("express"); ... var expressServer = express.createServer() expressServer.use(express.bodyParser()); expressServer.use(express.errorHandler()); expressServer.use(express.static(__dirname + "/public")); … var port = parseInt(process.env.PORT, 10) || 1337; expressServer.listen(port); console.info("listen port ", port);

Pour communiquer avec l’interface Web et sachant qu’il faudra du Push, c’est bien évidemment Socket.io que nous allons utiliser. Configurer, connecter et transmettre les évènements de modification des tableaux se font encore une fois quelques lignes.

var io = socketio.listen(expressServer, { 	"log level": 2 }); … io.sockets.on("connection", function (socket) {   	socket.on("connect", function(data) {// void ci dessous   	});   	socket.on("clear", function(data) {// void ci dessous   	});   	socket.on("element", function(data) { 		console.log("element message, receiving new", data.type, "element for", data.whiteboard, "whiteboard"); 		socket.broadcast.emit("element", data); 		… // void ci dessous 	}); });

Côté stockage, nous pouvons facilement identifier nos tableaux pour être des documents MongoDB avec une intégration Node.js rapide et un hébergement facile également. Le fonctionnement asynchrone de MongoDB permet de s’intégrer rapidement dans les évènements socket.io déjà préparés :

var mongo = mongoose.createConnection('mongodb://whiteboard:***@alex.mongohq.com:10007/shared-whiteboard'); ... var WhiteBoardElement = mongo.model('WhiteBoardElement', new mongoose.Schema({ 	whiteboard: String, 	type: String }));   io.sockets.on("connection", function (socket) {   	socket.on("connect", function(data) {   		WhiteBoardElement.find({   			whiteboard: data.whiteboard   		}, function(err, docs) {   			if(!err) {   				socket.emit("init", docs); 				console.log("connect message, whiteboard", data.whiteboard, "return", docs.length, "docs"); 			} else { 				console.error("connect message, error from mongodb", err); 			}   		});   	});   	socket.on("clear", function(data) {   		console.log("clear message, deleting elements for whiteboard", data); 		WhiteBoardElement.find({whiteboard: data}).remove();   	});   	socket.on("element", function(data) { 		console.log("element message, receiving new", data.type, "element for", data.whiteboard, "whiteboard"); 		socket.broadcast.emit("element", data); 		var instance = new WhiteBoardElement(data, false); 		instance.save(function(err) { 			if(err) { 				console.error("error on adding the new element in mongodb", err); 			} 		}); 	}); });

Nous voila avec un code serveur complet pour seulement 54 lignes et facilement hébergeable: https://github.com/Swiip/shared-whiteboard/blob/master/app.js

Côté client

Avec 500 lignes et l’objectif de cibler un maximum de plateformes mobiles, autant dire que les applications codées en natif pour chaque plateforme sont totalement inaccessibles. L’interface générique avec des technologies Web est donc incontournable.
Il va falloir également un framework de mise en forme qui s’adapte à tous les devices en un seul code. jQuery Mobile est pensé pour cet objectif, c’est lui que nous allons utiliser. Nous avons besoin de trois pages: Accueil, Tableau et Options que nous mettons dans un seul document HTML comme le propose l’outil.

<!DOCTYPE html> <html> 	<head> 		<title>Shared Whiteboard</title> 		<meta name="viewport" content="width=device-width, initial-scale=1"> 		<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css"/> 		<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script> 		<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script> 		... // a venir 		 <script type="text/javascript"> 			… // a venir 		</script> 	</head> 	<body> 		<div data-role="page" id="home"> 			<div data-role="header"> 				<h1>Choose your whitboard</h1> 			</div>                 	<div data-role="content"> 				<h1>Whiteboard's name:</h1> 				<input class="whiteboard-name" type="text" value="" /> 				<a class="open-button" data-role="button" href="#whiteboard">Open</a> 			</div> 		</div> 		<div data-role="page" id="whiteboard"> 			<div data-role="header"> 				<h1>Whiteboard</h1> 				<a data-icon="check" class="ui-btn-right image-ok" style="display: none;">Ok</a> 				<a href="#options" data-icon="gear" class="ui-btn-right options">Options</a> 			</div> 			<div id="editor"></div> 		</div> 		<div data-role="page" id="options"> 			<div data-role="header"> 				<a href="#whiteboard" data-icon="arrow-l">Ok</a> 				<h1>Options</h1> 			</div> 			<a data-role="button" class="add-picture-button">Add a picture</a> 			<fieldset data-role="controlgroup" data-type="horizontal"> 				<legend>Color</legend> 				<input type="radio" name="color" id="color-black" value="black" checked="checked" /> <label for="color-black">Black</label> 				<input type="radio" name="color" id="color-red" value="red" /> <label for=
"color-red">Red</label> 				<input type="radio" name="color" id="color-blue" value="blue" /> <label for="color-blue">Blue</label> 				<input type="radio" name="color" id="color-green" value="green" /> <label for="color-green">Green</label> 			</fieldset> 			<label for="width">Width:</label> 			<input type="range" name="width" id="width" value="5" min="1" max="30" /> 			<a data-role="button" data-icon="delete" class="clear-whiteboard-button">Clear whiteboard</a> 		</div> 	</body> </html>

Le tableau en lui même doit être une zone graphique. Dans les technologies Web, cela se concrétise par l’utilisation des nouveaux composants HTML5 SVG ou Canvas. Dans ce cas je me suis dirigé vers le framework SVG RaphaelJS et une petite librairie construite sur RaphaelJS qui s’appelle Raphael Sketchpad et qui permet justement de tracer au doigt ou à la souris des trait avec Raphael. http://ianli.com/sketchpad/
Les puristes pourront mettre ma démonstration à défaut à cet endroit précis et j’en ai conscience, je préfère de ce fait annoncer la chose clairement. Une librairie si peu répandue et dans laquelle en plus j’ai fait des modifications pourrait compter dans le nombre de ligne de code globale ce qui me ferais dépasser les 500 lignes (d’une centaine ou deux). Mais c’est néanmoins du code que j’ai réutilisé dans sa grande majorité, que je n’étais pas obligé de modifier en dehors de questions de performances et des photos (voir ci-dessous) et je suis loin d’avoir modifié autant de ligne que j’aurai de marge à la fin du compte, donc je me permettrai de continuer en considérant Sketchpad comme une librairie externe.

<head> 	... 	<script src="js/raphael.whiteboard.js"></script> 	... 	<script type="text/javascript"> 		... 		function startWhiteboard() { 			window.whiteboard = Raphael.whiteboard("editor", { 				width: "100%", 				height: "100%", 				editing: true 			}); 			whiteboard.addChangeHandler(function(attr) { 				if(listening) { 					attr.whiteboard = whiteboardName; 					socket.emit("element", attr); 				} 			}); 		} 		... 	</script> </head>

Se connecter avec le serveur que nous avons créé précedemment demande d’utiliser l’interface cliente de socket.io. Ensuite, il s’agit d’écouter les modifications arrivant par la WebSocket et transmettre celles qui sont faites sur l’interface.

<head> 	... 	<script src="js/socket.io.min.js"></script> 	... 	<script type="text/javascript"> 		var socket, whiteboardName, listening = true;   		$(document).bind('pageinit', function(event) { 			socket = io.connect(""); 			... 		}); 		... 		function startWhiteboard() { 			... 			socket.emit("connect", { 				whiteboard: whiteboardName 			}); 			socket.on("init", function(elements) { 				listening = false; 				var result = whiteboard.paper().add(elements); 				listening = true; 			}); 			socket.on("element", function(attr) { 				if(attr.whiteboard == whiteboardName) { 					listening = false; 					console.log("receiving new element of type ", attr.type); 					whiteboard.paper().add([attr]); 					listening = true; 				} 			}); 		} 	</script> </head>

Dans la définition du projet, j’avais parlé de distribution sur toutes les plateformes mobiles. En pratique, l’application actuelle fonctionne déjà sur la plupart des navigateurs mobiles mais il est possible d’aller plus loin en créant des applications susceptibles d’être distribué sur les markets avec la solution PhoneGap. Pratiquement aucun code supplémentaire n’est nécessaire, il suffit de saisir l’adresse du repository GitHub dans https://build.phonegap.com/.
Pour réellement intégrer l’application pour le mobile, on peut ajouter la fonctionnalité de prendre du photo pour l’intégrer sur le tableau. On ajoute alors le code suivant :

<head> 	... 	<script src="cordova-1.8.1.js"></script> 	<script type="text/javascript"> 	... 	$(".add-picture-button").live("click", function(event) { 		navigator.camera.getPicture(function(data) { 			console.log("camera success ", data);   			var src; 			//Hook for PhoneGap simulation stub 			if(data == "chrome-logo.png") { 				src = data; 			} else { 				src = "data:image/jpeg;base64," + data; 			} 			whiteboard.addImage(src); 			$.mobile.changePage("#whiteboard"); 			$("#whiteboard a.image-ok").css("display", "block"); 			$("#whiteboard a.options").css("display", "none"); 		}, function(data) { 			console.log("camera fail " + data); 		}, { 			quality: 20, 			destinationType: Camera.DestinationType.DATA_URL 		}); 	}); 	... 	$("#whiteboard a.image-ok").live("click", function(event) { 		$("#whiteboard a.image-ok").css("display", "none"); 		$("#whiteboard a.options").css("display", "block"); 		whiteboard.validateImage(); 	}); 	... 	</script> </head>

Et nous voici au bas d’un document HTML très complet, qui fait appel à de nombreuses librairies, de nombreuses technologies mais qui ne fait pourtant que 172 lignes ! https://github.com/Swiip/shared-whiteboard/blob/master/public/index.html

Conclusion

Un peu plus de 200 lignes en plus des modifications dans la librairie Sketchpad, nous sommes encore loin des 500 lignes et pourtant les critères d’un service cloud sont respectés. Bien sûr, c’est un embryon de service, les bases techniques fonctionnent mais nombre de questions se poseront à l’intrépide qui voudra lancer son service cloud : scalabilité (même si sur le papier tout est prévu), marketing, design, communication, rentabilisation. Le but était de démontrer que les technologies actuelles permettent d’aller extremement loin pour en quelques lignes de code. Bien sûr, encore faut il comprendre chaque technologies, s’en servir à bon escient et réussir à les intégrer.
Les sources du projet sont toutes disponibles sur GitHub https://github.com/Swiip/shared-whiteboard
Le projet manque de documentation et de finalisation mais il n’est pas voué à devenir une référence en la matière. Toutefois, si certains s’y intéressent, je répondrais à toutes les questions s’y rapportant.
Article paru dans l’édition de janvier du magazine Programmez !

Une réflexion sur “Créer un service cloud en moins de 500 lignes

  • 8 août 2013 à 10 h 55 min
    Permalien

    titre racoleur en effet, pour nous proposer la mise en oeuvre (intéressante) d’une application web moderne. Ce que tu nomme un service cloud n’est autre qu’une application web « accessible via Internet sur un nombre important de plateformes » !

    Qu’est-ce qu’il y a de « cloud » là dedans ? Où sont l’élasticité, le multi-tenant, le paiement à l’usage ? Où est le côté self-service sur l’allocation des ressources ? Où sont les APIs ? Qui maintient la bête à ma place ? etc

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :