Skip to content

Blogs de Développeurs: Aggrégateur de Blogs d'Informatique sur .NET, Java, PHP, Ruby, Agile, Gestion de Projet

Forum Logiciel

Forum Logiciel : diffusion de connaissance et d’informations sur toutes les activités liées au développement d’applications informatiques en entreprise.

Agrégateur de flux

Atlassian Summit Chronicles: USA 2017

Le blog de Valiantys - jeu, 09/21/2017 - 14:00

With both an Atlassian Summit in Barcelona and San Jose, this year you get double the fun with another edition of our Summit Chronicles – jam-packed with all the product information you need to know along with what the Valiantys team is bringing to the table. With Atlassian reaching 90,000 customers globally, it’s no wonder ...

The post Atlassian Summit Chronicles: USA 2017 appeared first on Valiantys - Atlassian Platinum Partner.

Catégories: Blog Société

Petit-déjeuner : Un éléphant qui se balançait … – jeudi 5 octobre 2017

Comment mettre en musique les big data et valoriser les données avec de nouveaux services.

Petit déjeuner - un éléphant qui se balançait

BNP Paribas viendra témoigner de sa démarche avec un retour sur la mise en œuvre de ces nouvelles architectures de données.

Un menu copieux pour cette rentrée des petits-déjeuner OCTO avec un focus sur les architectures de données, un témoignage de BNP Paribas, un retour sur la mise en œuvre de ces nouvelles architectures de données et, cerise sur le gâteau, une mise en perspective de la tendance vers des architectures de flux à l’occasion de la publication du livre blanc Digital Studies Vol.02 : La question du temps dans les architectures digitales.

Les données sont là, initialement éclatées dans différents silos applicatifs. Mais maintenant qu’elles commencent à alimenter un Data Lake sous Hadoop, que va-t-on en faire ? Comment les valoriser ? Comment créer de nouveaux services à valeur ajoutée ?

BNP Paribas viendra tĂ©moigner de sa dĂ©marche – initiĂ©e par des expĂ©rimentations autour des data – pour proposer dès Ă  prĂ©sent de nouveaux services (trois projets seront Ă©voquĂ©s).

OCTO vous prĂ©sentera le retour d’expĂ©rience sur la mise en Ĺ“uvre de ces nouvelles architectures de donnĂ©es, incluant les technologies Hadoop, Spark, Cassandra, Solr ainsi que des expĂ©rimentations sur le Machine Learning, tout en soulignant les mĂ©thodes de travail utilisĂ©es avec des Ă©quipes mixtes BNP Paribas / OCTO.

Ce petit-déjeuner sera aussi l’occasion de vous présenter et de vous remettre une version imprimée du livre blanc Digital Studies Vol.02, consacré aux questions d’architecture, notamment aux nouvelles architectures de flux.

 

Cliquez ici pour vous inscrire au petit-déjeuner « Un éléphant qui se balançait »

 

Articles suggested :

  1. Formation Data Science : Paris – Genève
  2. USI dans VOTRE entreprise avec MyUSI
  3. Formations OCTO : Novembre – DĂ©cembre

Catégories: Blog Société

Arduino et infrarouge

Developpef - Paul-Emmanuel Faidherbe - jeu, 09/21/2017 - 09:11

Aujourd'hui, un article rapide sous forme de mémo, pour mettre en place une communication série entre deux Arduinos en infrarouge. Ceci pour permettre de trouver rapidement le matériel et code nécessaires.

Avant tout, il faut savoir que dès lors qu'une communication série filaire (USB par ex) a été mise en place, il suffit de "remplacer" le fil par une liaison optique infrarouge, puisque l'encodage reste le même (UART pour de la communication série).

Code et montage

Puisqu'il est inutile de réinventer la roue, voici un très bon tutoriel de Zola lab expliquant quel montage et quel code utiliser pour mettre en place la liaison optique :

Matériel

Et pour se fournir le bon matériel pour réaliser ce montage, rendez-vous chez Lextronic :

Have fun!

Catégories: Blog Individuel

[PODOJO] Pourquoi et comment mettre de l’UX au sein de sa stratégie digitale ?

Agile Nantes - mer, 09/20/2017 - 21:53
Pourquoi et comment mettre de l’UX au sein de sa stratĂ©gie digitale ?   Je sais pas vous mais il me semble que ces derniers temps, les buzzwords qui agitent la sphère des Products Owner, Fonctionnels, chargĂ©s d’applis etc. tournent autour de « l’expĂ©rience utilisateur » ou des dĂ©veloppements « centrĂ©s utilisateurs » . Bref, faire rentrer les utilisateurs […]
Catégories: Association

Configurer une application web pour Google App Engine

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:30

GAE requiert une configuration bien précise, voir spécifique, de l'application web destinée à être déployée sur ces serveurs. Je vais essayer de détailler ci-dessous les éléments que j'ai rencontré.

Descripteur de déploiement

En plus du fichier web.xml commun à toutes les applis web, GAE demande la mise en place d'un nouveau fichier de description spécifique, appengine-web.xml présenté dans ce chapitre de la documentation.

Validation des fichiers XML

Le serveur de test peut parfois poser des problèmes lors de la phase de validation Spring des fichiers XML. Pour la désactiver, il faut indiquer dans le fichier web.xml une classe spécifique de configuration :


<context-param>
<param-name>contextClass</param-name>
<param-value>my.package.MyXmlWebApplicationContext</param-value>
</context-param>

Pour ce besoin, son contenu est assez simple :


import com.google.appengine.api.utils.SystemProperty;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.web.context.support.XmlWebApplicationContext;

public class MyXmlWebApplicationContext extends XmlWebApplicationContext {
@Override
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
super.initBeanDefinitionReader(beanDefinitionReader);
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Development) {
beanDefinitionReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
}
}
}
persistence.xml

Afin de lier notre application au datastore de Google, voici un exemple basique de fichier de persistence :


<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

<persistence-unit name="transactions-optional">
<provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
<properties>
<property name="datanucleus.NontransactionalRead" value="true"/>
<property name="datanucleus.NontransactionalWrite" value="true"/>
<property name="datanucleus.ConnectionURL" value="appengine"/>
</properties>
</persistence-unit>

</persistence>
DĂ©claration du bean "entityManagerFactory" via Spring

Afin de rendre effective la connexion au datastore, il est nécessaire d'ajouter au fichier application-context.xml de Spring :


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="entityManagerFactory" class="com.my.MyEntityManagerFactory"/>
</beans>

Voici le contenu de la classe indiquée :


import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;

public class MyEntityManagerFactory extends LocalEntityManagerFactoryBean {
@Override
protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
return EMF.get();
}
}

La classe EMF est tirée des préconisations Google (https://developers.google.com/appengine/docs/java/datastore/jpa/overview?hl=fr) :


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
private static final EntityManagerFactory emfInstance =
Persistence.createEntityManagerFactory("transactions-optional");

private static EntityManager entityManager = emfInstance.createEntityManager();

private EMF() {}

public static EntityManagerFactory get() {
return emfInstance;
}

public static EntityManager getEntityManager() {
return entityManager;
}
}

Avec ces éléments, vous devriez pourvoir démarrer votre application rapidement.

Catégories: Blog Individuel

Gestion de l'authentification sur Google App Engine

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:28

Lors de la création d'une application sur la plateforme GAE, il est possible de configurer l'usage des comptes Google pour s'y connecter, comme décrit ici : Configuration de l'authentification de votre application

En mode développement, le serveur de test simule automatiquement cette utilisation de compte Google et met en place un système de redirection vers une page de connexion spécifique (https://developers.google.com/appengine/docs/java/tools/devserver#Using_Users) :

L'utilisateur connecté sera alors accessible simplement avec :


import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.users.User;
...
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();

Pour ce qui est de la déconnexion, il faut créer une redirection vers l'URL gérée par Google. Pour ce faire, voici un exemple de servlet :


import com.google.appengine.api.users.UserServiceFactory;
import java.io.IOException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogoutController {

@RequestMapping(method = RequestMethod.GET)
public void logout(HttpServletRequest request, HttpServletResponse response) {

SecurityContextHolder.clearContext();

// Remove the cookie
Cookie terminate = new Cookie(
TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
null);
terminate.setMaxAge(-1);
terminate.setPath(request.getContextPath() + "/");
response.addCookie(terminate);
try {
response.sendRedirect(UserServiceFactory.getUserService().createLogoutURL("/"));
} catch (IOException ex) {
Logger.getLogger(LogoutController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

Hope this helps!

Catégories: Blog Individuel

Spring et DWR

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:27

Direct Web remoting est un framework Ajax permettant un lien direct entre Javascript et Java coté serveur.

D'apparence assez simple, son intégration avec Spring peut rapidement devenir compliquée, notamment à cause de la gestion des namespaces dans le fichier XML de configuration. En effet, les versions récentes de DWR fournissent un namespace facilitant l'écriture des éléments spécifiques :


<dwr:controller id="dwrController" debug="true"/>
<dwr:configuration>...</dwr:configuration>

Mais puisque tout n'est pas rose, j'ai été confronté à une erreur récurrente au démarrage du contexte Spring : No bean named '__dwrConfiguration' found...

Quelque soit la version du namespace utilisé, pas moyen de m'en sortir... Voici donc comment revenir à une configuration plus simple basée sur des éléments XML plus classiques :

application-context-dwr.xml :


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.directwebremoting.org/schema/spring-dwr
http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">

<dwr:url-mapping />

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

<bean id="myDwrServerBean" class="my.package.MyDwrServerBean">
<dwr:remote javascript="serverBridge"></dwr:remote> <!-- nom de l'objet javascript utilisable -->
</bean>



<bean id="__dwrController" class="org.directwebremoting.spring.DwrController">
<property name="configurators">
<list>
<ref bean="__dwrConfiguration"/>
</list>
</property>
<property name="debug" value="true"/>
</bean>

<bean id="__dwrConfiguration" class="org.directwebremoting.spring.SpringConfigurator">
<property name="creators">
<map>
<entry key="firstBean">
<bean class="org.directwebremoting.spring.CreatorConfig">
<property name="creator">
<bean class="org.directwebremoting.spring.BeanCreator">
<property name="bean" ref="myDwrServerBean"/>
<property name="javascript"><value>serverBridge</value></property>
</bean>
</property>
</bean>
</entry>
</map>
</property>
<property name="converters">
<map>
<entry key="my.package.MyDwrServerBean">
<bean class="org.directwebremoting.spring.ConverterConfig">
<property name="type">
<value>bean</value>
</property>
</bean>
</entry>
</map>
</property>
</bean>
</beans>

Il est Ă©galement possible de configurer DWR via un bean dans le contexte Spring en utilisant le FluentConfigurator.

En espérant vous avoir fait économiser quelques heures de recherches!

Catégories: Blog Individuel

Arduino : XBee Ethernet data logger

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:25

Après plusieurs semaines de galère, j'ai décidé de rassembler ici tous les éléments qui m'ont permis, tant bien que mal, de mettre en place l'installation suivante :

D'un côté, un module XBee transmettant les informations d'un capteur de température (LM35). De l'autre, un second XBee relié à un Seeduino ATMega 2560 (similaire à un Arduino) qui enregistre toutes les mesures reçues sur une carte SD et expose le fichier créé via une connexion Ethernet. Voici le schéma du montage :

Avant d'aller plus loin, il est indispensable de faire un point sur la notion de SPI afin de comprendre le fonctionnement de notre montage.

SPI est un bus de communication basé sur les principes de Maitre et Esclave. Il est composé de 4 canaux. Pour ne pas plagier un très bon cours existant sur le site rocketnumbernine, voici en quelques mots son fonctionnement :

Each end of a SPI connection is acting in one of two roles - Master or Slave. The master is responsible for initiating and controlling the communication.

SPI Master/Slave Connections

The basic mode of operation is very simple: When the master wishes to initiate transfer of data:
  1. It sets the SS (Slave Select - often called CS - chip select) pin low to tell the slave that communication is about to start
  2. The master writes a bit of information onto the MOSI (Master Out Slave In) wire (sets it to 0 or 1) and the slave does the same on the MISO wire (either of these can be omitted if the data transfer is one way)
  3. As the master ticks the clock line SCLK it will read the value of MISO (Master In Slave Out) wire (which the Slave has written) and the slave will read the value of the MOSI wire (whether the data is sampled as the clock rises or falls depends on which mode is in operation)
  4. The process is repeated from (b), transferring a bit of data on each pulse of the clock until all data is transferred

Ce bus SPI est accessible via les broches ICSP (Arduino UNO et Mega 2560) ET les broches 11,12,13 (pour le UNO) ou 50,51,52 (pour le Mega 2560).

Un élément important est le Chip Select : il s'agit de la broche qui permet de choisir le périphérique cible de l'échange. Pour désactiver un périphérique, il faut passer la broche en niveau haut et pour l'activer, en niveau bas. Un seul périphérique ne peut être activé à la fois car les trois broches de communication MOSI, MISO et SCK sont partagées.

Sur l'Arduino UNO (Revision 3), le Mega 2560 (Rev 3) et sur le shield Ethernet (Rev 3), on trouve le CS :

  • sur la broche A4 pour la carte SD
  • sur la broche A10 pour l'Ethernet

C'est grâce à cette correspondance des broches qu'il est possible de simplement empiler les cartes sans avoir à faire de connexions supplémentaires. Il est tout de même possible, si vous ne souhaitez pas empiler les cartes, de relier les broches comme indiqué sur le schéma. Attention cependant, ceci est impossible sur le shield Ethernet car seules les broches CS sont utilisées et reliées directement à l'ICSP. Toutes les autres broches ne servent qu'à transférer les connexions à une éventuelle autre carte supperposée.

Revenons donc à notre projet. La carte Ethernet est apposée sur l'ATMega 2560, le module XBee principal (coordinateur) relié directement au Serial1 : broches RX1 et TX1 (croisées avec les RX/TX du XBee).

Côté logiciel maintenant : l'application doit, au démarrage, aller lire un fichier de configuration sur la carte SD et initialiser une connexion Ethernet. Pour bien démarrer, voici le code de la fonction setup() :


void setup() {
pinMode(10, OUTPUT);
pinMode(4, OUTPUT);
//
pinMode(53, OUTPUT);
digitalWrite(53, HIGH);
/* disable all slaves */
// disable Ethernet
digitalWrite(10, HIGH);
delay(5);
// disable SD
digitalWrite(4, HIGH);
delay(5);

/* read config file */
// init SD
boolean sd = SD.begin(4);
delay(5);
// ... read file ...
// disable SD
digitalWrite(4, HIGH);
delay(5);
}

A noter qu'il est très important de désactiver à la main l'Ethernet après son initialisation car la librairie ne le fait pas! Sur un ATMega 2560, il faut également obligatoirement définir la broche de Hardware SS (53) en sortie.

Ensuite, afin que le programme puisse à la fois recevoir en continu des paquets de données XBee à écrire sur la carte SD et assurer son rôle de serveur web, voici, sous forme algorithmique, le déroulement du code à mettre en place :


void loop() {
- Vérifier la présence d'un packet XBee
- si un paquet a été reçu :
- traiter les informations du paquet
- désactiver l'Ethernet : digitalWrite(10, HIGH)
- activer la carte SD : digitalWrite(4, LOW)
- Ă©crire sur la carte SD
- désactiver la carte SD : digitalWrite(4, HIGH)
- Vérifier la réception d'une requete Ethernet :
- si une requete a été reçue :
- désactiver la carte SD : digitalWrite(4, HIGH)
- activer l'Ethernet : digitalWrite(10, LOW)
- générer la réponse HTTP
- fermer la connexion client
- désactiver l'Ethernet : digitalWrite(10, HIGH)
}

Normalement, la gestion de l'activation/désactivation de chaque périphérique SD ou Ethernet est gérée en interne par les librairies correspondantes, mais je vous conseille de ne pas hésiter à le faire également manuellement afin de renforcer votre programme (on n'est jamais mieux servi que par soi-même!).

Un conseil supplémentaire : il est préférable d'ajouter quelques délais après chaque dés/activation de périphérique (via la méthode delay()), au moins pendant la phase de développement, afin d'éviter tout conflit de communication sur le bus SPI (par les broches partagées) qui pourrait engendrer, par exemple, la destruction de la carte SD (qui arrive plus vite qu'on ne le croit!).

Ci-dessous sont décrites les erreurs les plus communes qui peuvent être rencontrées :

  • Impossible de dĂ©marrer la carte SD :
    Si SD.begin() renvoie false, il faut dans un premier temps vérifier les connexions SPI (MOSI, MISO) dans le cas où elles auraient été réalisées autrement que par les broches ICSP et dans un second temps s'assurer que seule sa broche CS est active.
  • Impossible de PING la carte Ethernet :
    Les requêtes ping répondent "Impossible de joindre l'hôte" ("Host unreachable") et la LED TX de la carte ne s'allume jamais : vérifier également que seule la broche CS de la carte Ethernet est active et qu'aucun autre périphérique ne tente d'accéder au bus SPI.

Vous voilà prêts! J'ai essayé de regrouper ici les connaissances de base dont j'ai eu besoin et pour lesquelles j'ai du écumer nombre de blogs et forums. J'espère que tout cela vous sera utile!

Catégories: Blog Individuel

Introduction au NoSQL avec Apache CouchDB

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:24

Je vais vous montrer comment prendre en main facilement une base de données NoSQL, grâce à un exemple simple.

Nous allons créer une petite application utilisant Ektorp, une API de mapping par annotations et CouchApp, un utilitaire pour déployer des applications HTML5.

Création des données

Pour commencer, il suffit de créer un simple POJO représentant un objet métier. Comme illustré dans la documentation Ektorp ici, il n'est même pas nécessaire de mapper les attributs à enregistrer. C'est même le contraire, Ektorp part du principe que tous les attributs seront sauvegardés sauf ceux explicitement annotés comme ignorés! Un bon gain de temps...

Ensuite, quelques lignes suffisent pour implémenter les fonctionnalités primaires de CRUD :

Here's how a SofaRepository implemented with the generic repository looks like


public class SofaRepository extends CouchDbRepositorySupport {

public SofaRepository(CouchDbConnector db) {
super(Sofa.class, db);
}

}

This repository will have the following methods "out of the box":


SofaRepository repo = new SofaRepository(db);

repo.add(Sofa s);
repo.contains("doc_id");
Sofa sofa = repo.get("doc_id");
repo.update(Sofa s);
repo.remove(Sofa s);
List repo.getAll();

A ce stade, nous avons déjà de quoi mettre en place une moulinette pour insérer des données. Une fois ceci fait, il est possible de consulter le contenu de la base avec Futon, une interface web fournie par CouchDB, à l'adresse http://localhost:5984/_utils/ :

Initier l'application web

CouchDB propose une interface REST très pratique. Ce qui signifie que n'importe quel outil effectuant des requêtes HTTP peut interroger directement la base, y compris un navigateur et... Ajax!

Seulement, comme certains yeux affûtés l'auront remarqué, CouchDB démarre par défaut sur le port 5984, ce qui rend impossible les accès directs via Ajax, par raison de sécurité. Si vous n'avez pas la main sur le serveur hébergeant la base afin de configurer le mécanisme HTTP "CORS", comme indiqué sur cette page, il vous faudra passer par CouchApp afin de déployer votre application sur le serveur interne de CouchDB (comme c'est le cas de Futon).

Un tutoriel très concis explique comment générer et déployer une application basique :

En réalité, tous le fichiers composant cette application sont des documents au sens NoSQL que CouchDB gère comme tels. Tous les fichiers "statiques" (images, css, js...) sont des "_attachments".

Lister les documents

Imaginons que nos documents possèdent un attribut "date", par lequel nous voulons les filtrer, entre une date de début et une de fin.

Nous allons commencer par créer une vue, qui va nous retourner la liste des documents identifiés par une clé issue de notre champ "date".

Ceci se fait par l'intermédiaire des fonctions view, fonctions Javascript pures appelées pour chaque document, pouvant servir de filtre (le cas échéant). Créons par exemple un fichier "by_date.js" dans le répertoire "/views/by_date/" de notre application. Voici donc un exemple de ce que nous cherchons :


function(doc) {
if(doc.date) {
emit(doc.date,doc);
}
}

Le résultat de notre fonction est accessible à l'adresse (objets JSON) : GET /[db]/_design/[appName]/_view/by_date/


{
"total_rows": 2,
"offset": 0,
"rows": [
{
"key": "2014/01/15 15:52:20",
"value": {...}
},

{
"key": "2014/01/30 18:04:11",
"value": {...}
}

]
}
Utiliser JQuery

Maintenant nous pouvons compléter le fichier "index.html" (situé dans "_attachments") afin d'ajouter des fonctionnalités. Mais il faut savoir que CouchDB dispose d'un plugin JQuery pour faciliter les interactions. Par exemple, pour interroger notre vue :


$.couch.db("[db]").view("[appName]/by_date", {
success: function(data) {
console.log(data);
},
error: function(status) {
console.log(status);
}
});

Enfin, pour ajouter simplement des filtres sur les dates, il suffit d'intégrer les mots clés : "[appName]/by_date?startkey=[date1]&endkey=[date2]"

Ainsi, avec REST et HTML5/Javascript, vous voilà équipé pour jouer avec CouchDB! :) Enjoy!

Sources :
Catégories: Blog Individuel

Logstash et MongoDB en 10 minutes

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:23

Lorsqu'il s'agit de mettre en place des outils de gestion de logs applicatifs, Logstash est souvent mis en avant.

Il est souvent proposé en combinaison avec d'autres utilitaires comme ElasticSearch et Kibana, qui, bien que remarquables, posent deux soucis :

  1. l'effet "usine à gaz" : en effet, 3 outils à prendre en main d'un coup et à déployer, cela peut paraître intimidant.
  2. "Quels sont mes besoins ?" : ces outils sont essentiellement destinés à la création/analyse de statistiques extraites des informations loggées.

Dans l'exemple que j'ai choisi ici, la structure des fichiers de logs ne permet pas ce genre d'utilisation : en effet, une ligne de log en elle-même ne comporte que peu d'information (entrée dans un service, appel d'une procédure stockée...). Mais c'est plutôt en ensemble de lignes qui permet de reconstituer un scénario dans l'application : ce que l'utilisateur a fait, par où est passé le code, etc. En somme une analyse contextuelle (verticale) des logs plutôt que du contenu (horizontale).

Nous allons donc voir comment il est tout de même possible d'utiliser Logstash et MongoDB pour créer une application facilitant l'analyse de fichiers de logs volumineux.

Tout d'abord, pourquoi ces outils?

Logstash : il est prévu pour traiter des volumes de données importants, de manière efficace, même pour des fichiers au contenu spécifique grâce à son moteur d'expressions régulières Grok. Sa configuration est également très simple car il embarque des composants tout prêts, comme l'accès aux bases MongoDB.

MongoDB : NoSQL pour une mise en place rapide (schemaless orientée document), sans configuration, gérant également des grands volumes de données (au format JSON, utile pour les applis web).

C'est parti, mise en place!

Logstash

Il se présente sous la forme d'un Jar autonome exécutable et ne nécessite qu'un fichier de configuration. En voici un exemple, l'explication par la suite :


input {
file {
type => "serveur"
path => "C:/log/serveur_*.log"
}

file {
type => "client"
path => "C:/log/client_*.log"
}
}

filter {
grok {
match => [ "message", "%{DATESTAMP:date} %{LOGLEVEL:level} ?(<context>[a-Z0-9_]*) %{GREEDYDATA:message}" ]
}
}

output {
mongodb {
collection => "collec-%{type}"
database => "local"
uri => "mongodb://localhost"
}
}

En détails :

  • Les entrĂ©es (input) de type "fichier" :
    • type : une Ă©tiquette qui permet de diffĂ©rencier nos sources de fichiers (logs serveur d'un cĂ´tĂ© et client de l'autre)
    • path : le chemin d'accès, utilisant des wildcards
  • filter : la traitement a effectuer sur les entrĂ©es, ici un dĂ©coupage via une expression rĂ©gulière Grok
    • %{DATESTAMP:date} : on crĂ©e un champ nommĂ© "date" constituĂ© d'un timestamp, quel que soit le format (trop fort!)
    • %{LOGLEVEL:method} : un champ nommĂ© "level" contenant les constantes classique de niveau de log : INFO, ERROR...
    • ?(<context>[a-Z0-9_]*) : un champ nommĂ© "context" basĂ© sur une expression rĂ©gulière personnalisĂ©e
    • %{GREEDYDATA:message} : un champ nommĂ© "message" contenat... tout et n'importe quoi!
  • Les sorties (output) de type "mongodb", avec les infos de connexion basiques. Petite astuce : il est possible de rendre dynamiques les Ă©lĂ©ments de configuration pour chaque ligne de log traitĂ©e.

Il est possible de se faciliter l'écriture de l'expression Grok avec Grok Debugger. Comme vous le voyez, très efficace!

Ces quelques lignes placées dans un fichier "ma-config.conf", Logstash se lance simplement avec :

java -jar logstash-1.3.2-flatjar.jar agent -f ma-config.conf

MongoDB

LĂ , rien de plus simple (la doc ici) :

  1. Lancer "mongod.exe" pour démarrer la base
  2. Optionnel : lancer "mongo.exe" pour ouvrir la console d'administration
  3. Dans notre exemple, pas besoin de créer une base de données, "local" est déjà créée par défaut.

Et voilà! Vous pouvez d'ores-et-déjà déposer des fichiers dans les répertoires indiqués, Logstash va lancer nos traitements pour fournir les documents correspondant dans notre base...

Comme je vous sens coupé dans votre élan, allons plus loin.

API Java

Bien évidemment, MongoDB vient avec une API Java très simple d'utilisation. Quelques lignes de code déployées par exemple sur un serveur Glassfish exposant des webservices REST via Jersey et vous avez une application web pour consulter et requêter dans vos fichiers de logs!

Petits exemples, avec JQuery datatables et un tableau de bord avec gridster :

Plus loin...

Ils vous est également possible de pousser un peu plus loin ce système en utilisant cette extension de log4j, log4mongo, afin d'écrire vos logs en temps réel dans la base MongoDB depuis votre application!

Catégories: Blog Individuel

SQLServer, JDBC et procédures stockées

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 20:17

On ne le repètera jamais assez, migrer une base de données n'a rien d'anodin... Surtout lorsque l'application s'appuie sur des procédures stockées. En effet, chaque SGBD en propose son propre moteur et aucun driver JDBC ne se ressemble...


Le diable est dans les détails

Voici donc quelques exemples de "détails" qui vous éviteront de vous arracher les cheveux. La configuration utilisée ici est SQL Server Express 2014 et le driver JDBC Microsoft 4.0.

Un message peut en cacher un autre

Prenons l'exemple de la procédure stockées suivante :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

UPDATE MATABLE SET COL1 = 'VALEUR'

select 'data1' as RET1, 'data2' as RET2, 'data3' as RET3

END

Rien de bien compliqué, un UPDATE et un SELECT dont le but est de renvoyer un ResultSet avec 3 colonnes de données.

Appelons maintenant cette procédure avec un outil type Squirrel (configuré pour utiliser le driver JDBC) :

Tout se passe bien, le retour est bien constitué des 3 colonnes du SELECT.

Cependant, lorsque cette même procédure est appelée dans une application Java (via le même driver JDBC), l'erreur suivante est renvoyée :


com.microsoft.sqlserver.jdbc.SQLServerException: L'instruction n'a pas renvoyé le jeu de résultat.

En somme, le driver n'a pas pu remonter le ResultSet qu'il attendait...

Ceci est en fait du à la présence de l'instruction UPDATE. Son exécution génère l'émission d'un message appelé DONE_IN_PROC, qui sert à indiquer le nombre de lignes modifiées. Et c'est donc ce message qui est en priorité capté comme retour par le driver JDBC, ce qui l'empêche ensuite de récupérer le vrai ResultSet final.

Pour corriger ce problème, il existe l'instruction

SET NOCOUNT ON
qui permet de désactiver l'envoi des messages DONE_IN_PROC par la base. Notre procédure devient donc :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

SET NOCOUNT ON
UPDATE MATABLE SET COL1 = 'VALEUR'
SET NOCOUNT OFF

select 'data1' as RET1, 'data2' as RET2, 'data3' as RET3

END

Il est également possible de configurer directement le serveur de base de données pour ne pas avoir à écrire cette instruction dans toutes les procédures (voir les liens plus bas).

Du bon usage des types utilisateur

Les UDT (user defined type) fonctionnent comme des alias, permettant de créer des types de données personnalisés. Exemple :


CREATE TYPE [dbo].[T_DATEHEURE] FROM [datetime] NULL

Cette instruction va créer un type de données T_DATEHEURE basé sur le type primitif datetime, utilisable dans la base de données courante.

Prenons donc maintenant l'instruction suivante (toujours dans une procédure stockée) :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

CREATE TABLE #TABLETEMP (ID int, DATE T_DATEHEURE);

...

END

Cette fois-ci, l'erreur suivante sera levée :


Msg 2715, Level 16, State 7, Line 1
Colonne, parametre, ou variable #2: Type de données T_DATEHEURE introuvable.

Ceci provient de la nécessité de déclarer également le type de données T_DATEHEURE dans la base de données tempdb, utilisée par SQLServer pour créer les tables temporaires. Ainsi, une fois l'ordre "CREATE TYPE" exécuté sur tempdb, tout fonctionnera normalement...

Hope this helps!

Sources :
Catégories: Blog Individuel

Tester une API REST avec Swagger, Postman et Jenkins

Developpef - Paul-Emmanuel Faidherbe - mer, 09/20/2017 - 08:03

Avec l'essor de l'IoT vient l'âge d'or des APIs (et de REST), pour ouvrir facilement et de manière universelle des services.

La gestion des APIs devient donc une affaire de pros et doit être de plus en plus rigoureuse. Nous allons voir comment maîtriser les évolutions d'une API à partir d'outils spécialisés.

Les basiques

Je pense qu'il n'est plus nécessaire de présenter Postman (extension Chrome pour tester une API) ni Jenkins (serveur d'intégration continue). Mais il est intéressant de voir comment les deux peuvent travailler ensemble pour devenir une suite de tests automatisée d'API, via l'utilitaire en ligne de commande Newman et cette très bonne documentation officielle.

Swagger, la documentation simple et efficace!

Le meilleur moyen de s'assurer de la cohérence d'une API reste une bonne documentation. Pour ce faire, il existe Swagger (et sa version en ligne SwaggerHub) qui est l'outil de référence en la matière. En effet, il est très simple d'écrire rapidement une documentation pertinente, avec qui plus est une présentation agréable :

Putting it all together

Maintenant, voyons comment combiner tout cela.

Avec Swagger, vous avez la possibilité de définir, en plus de vos URLs, le formalisme des objets métier que vous manipulez. Pour cela il faut utiliser la section "definitions". Par exemple :


paths:
...
definitions:
Asset :
type: object
additionalProperties: false
required: ['name','owner','type']
properties :
name:
type: string
description: Nom de l'Asset.
owner:
type: string
description: Propriétaire du Device (User / domaine métier)
type:
type: string
description: Type de l'objet
Zone :
type: object
additionalProperties: false
required: ['name','geojson']
properties :
name:
type: string
description: Nom de la Zone.
geojson :
type: object
description: Géométrie au format GeoJSON
properties:
type:
type: string
description: Feature
geometry:
type: object
properties:
type:
type: string
description: Point
coordinates:
type: array
description: tableau de points GPS
items:
type: number
country:
type: string
description: Pays de la Zone, issu du référentiel Country.

Parallèlement, il est possible dans les tests Postman de vérifier que le contenu d'une réponse correspond à un schéma particulier (via TinyValidator). Et surprise, le formalisme des "schémas" utilisés par Postman correspond parfaitement à celui de nos "definitions" Swagger! Cela vous donne des idées?? Alors allons-y!

Tout d'abord, nous allons récupérer notre définition Swagger en l'exportant au format JSON pour pouvoir l'utiliser avec Postman. Cependant, à l'heure actuelle, il n'est pas possible dans Postman de charger un fichier externe, autre qu'un fichier de données de tests. Donc nous allons devoir faire un copié/collé de notre définition dans Postman. Mais pour essayer de faire cela proprement, nous allons coller notre Swagger dans une variable d'environnement de Postman, afin qu'elle soit accessible dans tous les tests :

Ensuite, nous allons écrire un test de vérification de schéma qui va se baser sur la définition d'un de nos objets métier Swagger :

var swaggerJson = JSON.parse(environment.Swagger);// test schema var jsonData = JSON.parse(responseBody);var schema = {type:array,items: swaggerJson.definitions.Asset};tests[Valid Data] = tv4.validate(jsonData, schema);if(tv4.error) {   console.log(tv4.error);}

Comme vous le voyez, nous chargeons en JSON notre définition Swagger, puis définissons un schéma de type tableau d'Assets, en récupérant la définition des Assets avec swaggerJson.definitions.Asset. Et en plus c'est facile!

Maintenant, lors de l'exécution de votre requête, les tests seront lancés et la réponse sera validée en fonction du schéma indiqué! Il est important à ce stade de disposer d'une définition d'objet stricte, notamment via l'utilisation des propriétés Swagger additionalProperties:false et required: ['name','owner','type'], sans quoi n'importe quelle réponse passera la validation.

Vous pouvez même aller jusqu'à utiliser votre définition Swagger pour récupérer les URLs à tester. Pour cela, il faut utiliser la partie "Pre-request Script" de Postman et parcourir de la même façon notre Swagger :

Ici, nous définissons une variable d'environnement 'url' à l'aide des attributs 'host' et 'basePath' de Swagger. Cette variable est ensuite utilisée par Postman via la syntaxe {{url}} dans sa barre d'adresse.

Remarque :

Il est également possible d'importer directement la définition Swagger dans Postman (via le bouton Import) pour récupérer automatiquement toutes les URLs à tester. Cependant, si vous avez besoin de réimporter votre définition suite à des modifications, cela va écraser tout ce qui existe déjà, y compris les tests!.

Pour terminer, il ne vous reste plus qu'à mettre tout cela dans Jenkins, via l'export de votre collection et de l'environnement Postman que vous passerez en paramètres de Newman! Enjoy!

Catégories: Blog Individuel

Les KProperty2 ou la réflexion signée Kotlin

En parcourant la bibliothèque standard de Kotlin, section réflexion, on peut tomber sur des types tels que KProperty0, KProperty1 et KProperty2.

On comprend assez rapidement que les types KProperty* sont des types qui reprĂ©sentent et permettent de manipuler, par rĂ©flexion, des propriĂ©tĂ©s i.e. des variables dĂ©clarĂ©es dans un package ou dans une classe. On comprend, en revanche, un peu plus difficilement pourquoi des propriĂ©tĂ©s peuvent ĂŞtre reprĂ©sentĂ©es par plusieurs types (et surtout par trois types diffĂ©rents). En Java, par exemple, l’Ă©quivalent d’une propriĂ©tĂ© – un champ – est toujours reprĂ©sentĂ© par le type Field, qu’il soit statique ou non.

Cet article vous propose de dĂ©couvrir ce que les diffĂ©rents types KProperty* reprĂ©sentent. Nous commencerons donc par Ă©tudier les types KProperty0 et KProperty1 qui se rĂ©vèlent, au final, assez simples. Nous pourrons ensuite mieux aborder le type KProperty2, la configuration un peu inhabituelle qu’il reprĂ©sente et surtout en quoi il peut bien nous ĂŞtre utile.

Le type KProperty0

Comme le montre le code ci-dessous (lien gist), le type KProperty0 reprĂ©sente une variable immuable dĂ©clarĂ©e au niveau d’un package (sans aucun contexte).

package com.github.sergiords.kproperties

import kotlin.reflect.KProperty0

val name: String = "Bob"

fun main(args: Array<String>) {

    val kProperty0: KProperty0<String> = ::name

    val value: String = kProperty0() // value of the property "name"

    println(value) // prints "Bob"

}

Dans cet exemple, pour obtenir une instance du type KProperty0 reprĂ©sentant la variable immuable name on utilise l’opĂ©rateur « :: » (ligne 9) appliquĂ© Ă  la variable elle-mĂŞme. On voit que le type KProperty0 est paramĂ©trĂ© par le type de la propriĂ©té qu’il reprĂ©sente (String). L’instance kProperty0 peut ensuite ĂŞtre utilisĂ©e pour rĂ©cupĂ©rer la valeur de la variable (ligne 11).

Le type KProperty1

Le code ci-dessous (lien gist) met quant Ă  lui en Ă©vidence le type KProperty1 qui reprĂ©sente une variable immuable dĂ©clarĂ©e au niveau d’une classe (dans le contexte d’un type User).

package com.github.sergiords.kproperties

import kotlin.reflect.KProperty1

class User {

    val name: String = "Bob"

}

fun main(args: Array<String>) {

    val kProperty1: KProperty1<User, String> = User::name

    val user: User = User()

    val value: String = kProperty1(user) // value of the property "name" inside "user" instance

    println(value) // prints "Bob"

}

Pour obtenir une instance du type KProperty1 reprĂ©sentant la variable immuable name dans une instance du type User on utilise l’opĂ©rateur « :: » appliquĂ© au type User (ligne 13). On voit que le type KProperty1 est paramĂ©trĂ© par le type contenant la variable (User) et par le type de la propriĂ©té qu’il reprĂ©sente (String). L’instance kProperty1 peut ensuite ĂŞtre utilisĂ©e pour rĂ©cupĂ©rer la valeur de la variable immuable dans une instance du type User (ligne 17).

Le type KProperty2

Nous venons de voir les types KProperty0 et KProperty1, ils reprĂ©sentent respectivement une variable immuable dĂ©clarĂ©e au niveau d’un package (zĂ©ro contexte) et au niveau d’un type donnĂ© (un seul contexte, l’instance). Pour le type KProperty2 on suit le mĂŞme raisonnement en ajoutant un contexte. Le type KProperty2 reprĂ©sente donc une variable immuable dĂ©clarĂ©e dans deux contextes. Comment est-ce possible ? Avec les extensions de propriĂ©tĂ©s, mises en Ă©vidence ci-dessous (lien gist).

package com.github.sergiords.kproperties

import kotlin.reflect.KProperty2
import kotlin.reflect.full.declaredMemberExtensionProperties

class Magic {

    val String.magicLength: Int   // add property magicLength to String type inside Magic class only
        get() = this.length * 42

    fun length(arg: String): Int = arg.magicLength

}

fun main(args: Array<String>) {

    @Suppress("UNCHECKED_CAST")
    val kProperty2: KProperty2<Magic, String, Int> = Magic::class.declaredMemberExtensionProperties.single() as KProperty2<Magic, String, Int>

    val magic = Magic()

    val abcdValue: Int = kProperty2(magic, "ABCD") // value of the property "magicLength" inside "ABCD" and "magic" instances
    println(abcdValue) // since "ABCD".length = 4, prints 4 * 42 = 168

    val abcdeValue: Int = magic.length("ABCDE") // value of the property "magicLength" inside "ABCDE" and "magic" instances
    println(abcdeValue) // since "ABCDE".length = 5, prints 5 * 42 = 210

}

La syntaxe un peu particulière des lignes 8 et 9 permet de rattacher une variable immuable magicLength, au type String, uniquement accessible dans une instance du type Magic. Le this de la ligne 9 reprĂ©sente bien l’instance du type String qui reçoit cette nouvelle variable immuable. Cette syntaxe est une extension de propriĂ©tĂ© un peu particulière car elle est dĂ©clarĂ©e dans le contexte d’un autre type et c’est cette configuration de variable immuable que reprĂ©sente le type KProperty2.

Pour obtenir une instance du type KProperty2, on doit cette fois s’appuyer sur les extensions de propriĂ©tĂ©s dĂ©clarĂ©es au niveau du type Magic (ligne 18). On voit bien que le type KProperty2 est paramĂ©trĂ© par le type dans lequel il est dĂ©clarĂ© (Magic), par le type qui reçoit l’extension de propriĂ©tĂ© (String) et par le type de la propriĂ©tĂ© qu’il reprĂ©sente (Int). L’instance de kProperty2 peut ensuite ĂŞtre utilisĂ©e pour obtenir la valeur de la variable immuable magicLength d’une chaĂ®ne « ABCD » dans une instance donnĂ©e du type Magic (ligne 22).

Une utilisation plus standard de ces propriétés est la fonction Magic::length qui utilise cette extension de propriété, mais cette fois, directement dans le type Magic (déclaration à la ligne 11 et appel à la ligne 25).

Conclusion

L’objectif de cet article Ă©tait avant tout de comprendre ce que les diffĂ©rents types KProperty* pouvaient bien reprĂ©senter comme configuration. Si les types KProperty0 et KProperty1 reprĂ©sentent une configuration assez classique, il n’en est rien pour le type KProperty2.

La question qui nous vient alors naturellement Ă  l’esprit est la suivante : « mais Ă  quoi cela peut-il bien servir ? ». On peut voir le type KProperty2 comme la possibilitĂ© de reprĂ©senter des propriĂ©tĂ©s qui ne sont valables que lorsque deux contextes sont rĂ©unis. On peut, par exemple, reprĂ©senter la position d’une forme dans un conteneur avec le code ci-dessous (lien gist).

package com.github.sergiords.kproperties

class Form(val x: Int, val y: Int)

class Container(val x: Int, val y: Int) {

    val Form.xInContainer: Int
        get() = this.x - this@Container.x

    val Form.yInContainer: Int
        get() = this.y - this@Container.y

}

Dans l’exemple ci-dessus, une forme peut ainsi avoir une position absolue (Form.x, Form.y) et une position relative dĂ©finie dans un conteneur (Form.xInContainer, Form.yInContainer). Cette dernière position bien que rattachĂ©e au type Form, n’est dĂ©finie que dans le contexte du type Container. En dehors de ce contexte elle n’a pas de sens et n’est d’ailleurs pas accessible.

Catégories: Blog Société

How the right DevOps tools can help bring teams together

Le blog de Valiantys - mar, 09/19/2017 - 14:23

Adopting DevOps is an accumulation of a number of small steps towards a common direction. In this short article, I like to cover one of those small steps in making DevOps work for you – getting your teams to use the same platform. DevOps started with software teams switching successfully to Agile methodologies. Development cycles ...

The post How the right DevOps tools can help bring teams together appeared first on Valiantys - Atlassian Platinum Partner.

Catégories: Blog Société

Pour une vague d’adrénaline

ekito people - mar, 09/19/2017 - 11:03

Le secret ? Tout ce qu’il y a autour.

Si tu commences l’aventure sans apprécier souffrir, ramer, attendre, avoir froid, douter, cela va être laborieux.

Parce que dans ce domaine le ratio résultat sur effort n’est pas bon. Bien pire qu’ailleurs.

 

Photo by Roger Mosley.

Beaucoup de travail.

Des journées à avoir l’impression que rien ne fonctionne. Que malgré tous tes efforts, tu n’es jamais au bon endroit au bon moment. Pour un détail mal ajusté, tu réapprends l’humilité. Violemment. L’impression de manquer d’air.

Le contrĂ´le.

Et sa possibilité de le perdre. Ce rappel permanent que face aux éléments tu es vulnérable. Cette absence de certitude qui fait que chaque vague est magique. Ce moment où le temps s’arrête et se compresse. Cette descente infinie, ce sentiment de puissance gravé à jamais dans ton esprit par la vitesse. Cette éphémère omnipotence.

C’est pour tout ça que tu y retournes malgré tout. Pour cette adrénaline.

Pour ces 5 secondes d’éternité.

The post Pour une vague d’adrénaline appeared first on ekito people.

Catégories: Blog Société

Tester du code Spark – 2 – La pratique

Que de la thĂ©orie. Les tests du code Spark semblent abonnĂ©s Ă  cette rĂ©alitĂ© : tout reste thĂ©orique. Ă€ croire qu’en rĂ©aliser est impossible. Il est grand temps de remĂ©dier Ă  ce problème et dĂ©montrer que le sujet est abordable par tous.

Quitter la thĂ©orie, c’est dĂ©buter par les bonnes pratiques. Des rĂ©flexes sur la spĂ©cification des jeux de tests jusqu’aux extensions des DSL Scala et Python qui simplifient les assertions autour des DataFrames en passant par l’utilisation de Spark-Testing-Base, cet article couvre le minimum nĂ©cessaire Ă  l’Ă©criture des tests en Spark.

AmĂ©liorer la lisibilitĂ© du contexte d’exĂ©cution du test

Dans un cas idĂ©al, un test contient trois sections : « Given », « When », et « Then ». Le « Given » dĂ©fini clairement le contexte d’exĂ©cution du test qui le rend spĂ©cifique. C’est ce contexte, appliquĂ© Ă  notre mĂ©thode testĂ©e (le « When ») qui provoque la sortie attendue (le « Then »). Il est important de chercher Ă  rĂ©duire la quantitĂ© d’informations prĂ©sentes dans ce contexte d’exĂ©cution dans l’objectif d’amĂ©liorer sa lisibilitĂ©.

Premièrement, oĂą doivent se situer les donnĂ©es de test ? Doivent-elles ĂŞtre externalisĂ©es dans un fichier dĂ©diĂ© ? Ou bien doivent-elles ĂŞtre insĂ©rĂ©es directement dans le code du test ? Mon prĂ©cĂ©dent article prĂ©conise de les intĂ©grer dans le code du test, Ă  l’exception des tests de validation d’un modèle de Machine Learning. Ă€ première vue, la solution du fichier permet de fournir un jeu de test plus consĂ©quent. Cependant, augmenter la taille du jeu de test en entrĂ©e entraĂ®ne de la difficultĂ© en termes de maintien de la cohĂ©sion entre le test et ses donnĂ©es.

Deuxièmement, comment initialiser un DataFrame contenant potentiellement beaucoup de colonnes ? La solution est en fait plutôt simple.

En Scala, la mĂ©canique de « case class » associĂ©e Ă  la mĂ©thode implicite d’une session Spark permet de conserver un maximum de simplicitĂ© comme le montre l’exemple suivant.

import org.apache.spark.sql.DataFrame
import org.scalatest.{FlatSpec, GivenWhenThen}

case class TestRow(column1: String = "", column2: String = "")

class SimpleSpec extends FlatSpec with GivenWhenThen with SparkSessionProvider {
  import sparkSession.implicit._
  
  "A Unit test" should "have a readable Given" in {
    Given("A specific value on column 1")
    val inputDataFrame: DataFrame = List(
      TestRow(column1 = "specific value")
    ).toDF()
    
    ???
  }
  
}

En Python, le « kwargs » fourni quelque chose de semblable Ă  la version Scala. Bien que plus lĂ©gère Ă  première vue, cette version convient principalement Ă  des cas simples de valeurs prĂ©cises. Toute inclusion d’une valeur nulle (None) impose d’initialiser et de fournir un schĂ©ma SparkSql (une instance de StructType).

import unittest2

spark_context = SparkContext()
spark_session = SparkSession(spark_context).builder.master("local[*]").appName("Unit tests").getOrCreate()
 
class TestSimple(unittest2.TestCase):
 
  def test_shoud_have_readable_give(self):
    # Given
    input_data_frame = spark_session.createDataFrame([
      Row(column1 = "specific value")
    ])
    ....

La comprĂ©hension du test est simple, en Python comme en Scala : le DataFrame d’entrĂ©e contient une ligne dont la particularitĂ© porte sur la colonne « column1 ».

Spark-Testing-Base, de nombreuses fonctionnalités pour différents langages

Une des bibliothèques les plus connues autour du test de programmes Spark est sans conteste Spark-Testing-Base. Cette bibliothèque propose de nombreuses fonctionnalitĂ©s pour diffĂ©rents langages (Scala, Java, Python) parmi lesquelles des classes d’aide Ă  la rĂ©daction d’assertions sur les RDD, DataFrames et DataSets et de tests basĂ©s sur l’utilisation d’un Mini-Cluster. Elle propose Ă©galement du support autour de ScalaCheck.

Une configuration simple

En Scala comme en Python, la procédure de configuration est simple.

En Scala, elle est dĂ©crite directement dans le readme du dĂ©pĂ´t Github du projet. De base, un ajout de dĂ©pendance Maven ou SBT et le tour est jouĂ© (il est toutefois recommandĂ© de correctement dimensionner la mĂ©moire car l’utilisation d’un contexte Spark local est assez gourmand). Attention Ă  bien prĂ©ciser la version voulue de Spark dans la dĂ©pendance.

val sparkVersion = "2.2.0"
val sparkTestingBaseVersion = "0.7.4"
libraryDependencies += "com.holdenkarau" %% "spark-testing-base" % s"${sparkVersion}_$sparkTestingBaseVersion" % "test"
    <dependency>
        <groupId>com.holdenkarau</groupId>
        <artifactId>spark-testing-base_${scala.version}</artifactId>
        <version>${spark.version}_${sparktestingbase.version}</version>
        <scope>test</scope>
    </dependency>

En Python, le package est disponible sur le dépôt PyPi comme tout autre package. Il est donc installable via PIP par la commande pip install spark-testing-base. Il suffit ensuite de placer la dépendance dans le fichier setup.py dans la zone tests_requires :

from setuptools import setup
 
setup(
  ...
  tests_requires=['spark-testing-base']
  ...
)

Cette bibliothèque propose trop de contenu pour qu’il soit totalement couvert dans cet article. Probablement sa fonctionnalitĂ© la plus utile, la classe DataFrameSuiteBase fourni le nĂ©cessaire Ă  un test simple dans Spark, en particulier des fonctions utilitaires pour la comparaison de DataFrames. Voici comment les utiliser :

class SimpleSpec extends FlatSpec with DataFrameSuiteBase {
  "Simple Spec" should "show how to use DataFrameSuiteBase" in {
    // Given
    val df = List(
      Balance(income=3, outcome=2)
    ).toDF()
 
    // When
    val result = df.filter(positiveAmount(_))
 
    // Then
    assertDataFrameEquals(df, result)
  }
 
  it should "also show to to deal with errors" in {
    // Given
    val df = List(
      Balance(income=2, outcome=3)
    ).toDF()


    // When
    val result = df.filter(positiveAmount(_))


    // Then
    intercept[TestFailedException]
      assertDataFrameEquals(df, result)
  }
}

Une fonctionnalité semblable est disponible dans la version Python de la bibliothèque. Il faut utiliser la classe SQLTestCase. Voici comment faire :

from pyspark.sql import Row
from sparktestingbase.sqltestcase import SQLTestCase
import unittest2

class SimpleTest(SQLTestCase):
  def test_do_nothing(self):
    rdd = self.sc.parallelize([Row(
        column1="foo"
    )])
    df = rdd.toDF()
    
    self.assertDataFrameEqual(df, df)
À utiliser pour des besoins spécifiques

Il est indĂ©niable qu’un framework tel que Spark-Testing-Base rend le code plus lisible. La quantitĂ© de mĂ©thode type « helpers » apporte une facilitĂ© d’Ă©criture des tests. Sur un petit projet, la simplicitĂ© de mise en Ĺ“uvre est flagrante.

Son utilisation apporte nĂ©anmoins quelques complexitĂ©s un peu cachĂ©es au premier abord. L’utilisation de cette bibliothèque par Maven/SBT provoque souvent le tĂ©lĂ©chargement d’une quantitĂ© astronomique de dĂ©pendances afin d’avoir Spark-Testing-Base Ă  disposition. Il arrive d’ailleurs parfois d’avoir des conflits de dĂ©pendances apportĂ©s par les dĂ©pendances transitives de Spark-Testing-Base, mais rien d’insoluble. Dans de nombreux cas, le besoin se situe au niveau d’une ou deux classes assez simples de cette bibliothèque. Beaucoup d’Ă©lĂ©ments seront Ă  ramener pour finalement quelques lignes de code. PrĂ©conisation personnelles : rĂ©Ă©crire le petit morceau de code Ă©quivalent de cette bibliothèque pour rĂ©pondre Ă  son besoin.

Enfin, un dernier point nĂ©gatif de ce framework est qu’il est moins fourni en Python, de quoi satisfaire principalement les utilisateurs de Spark en Scala.

En définitive, ce framework est à utiliser pour des besoins spécifiques. Certaines classes utilitaires sont assez complexes (telles que celles relatives à ScalaCheck) et il ne serait pas judicieux de les réécrire.

Simplifier l’Ă©criture des tests Spark en Scala

Lors de l’Ă©criture des tests Spark en Scala, il est classique d’utiliser les matchers de ScalaTest pour valider les attendus. Ceci fonctionne très bien pour valider des Ă©lĂ©ments tels que le contenu des schĂ©mas (les champs sont une collection de StructTypes que l’on peut valider) ou le contenu des DataFrames. Il s’agit d’une collection de Row. Puisque dans les tests, la quantitĂ© de donnĂ©e est limitĂ©e, la mĂ©thode collect est utilisable. Il est alors possible de tout ramener sur le driver pour utiliser les collections de base de Scala, donc les matchers de base, sans exploser la consommation mĂ©moire.

Voici un exemple type de test de schéma et de contenu de DataFrame :

class SimpleSparkAndScalaTest extends FlatSpec with Matchers with SparkSessionProvider {
  import sparkSession.implicit._

  def onlyPositiveBalance(inputDataFrame: DataFrame): DataFrame = {
    def positiveBalance(input: Row): Boolean = {
      input.getInt(input.fieldIndex("income")) - input.getInt(input.fieldIndex("outcome")) > 0
    }
    inputDataFrame.filter(positiveBalance(_))
  }
  
  case class Balance(id: Int, income: Int = 0, outcome: Int = 0)
  
  "Simple test" should "do something" in {
    // Given
    val inputDF = List(
      Balance(id = 1, income = 3, outcome = 2),
      Balance(id = 2, income = 3, outcome = 4)
    ).toDF()
    
    // When
    val result = onlyPositiveBalance(inputDF)
    
    // Then
    result.schema.map(_.name) should contain allOf ("income", "outcome")
    result.count() shouldBe 1
    result.map(row => row.getInt(row.fieldIndex("id"))).head shouldBe 1
  }

}

La couverture de test est Ă  un bon niveau puisque tout le code peut ainsi ĂŞtre testĂ©. Mais Ă  la lecture, il y a quelque chose de très dĂ©rangeant. La dernière ligne est trop complexe et est donc difficile Ă  lire. Il est grand temps de simplifier l’Ă©criture des tests en Scala.

Pour répondre à ce besoin, quelques lignes de code suffisent à créer un peudo DSL qui simplifie la lecture des tests. Par exemple, pour que les deux lignes de code suivantes soient équivalentes :

result.map(row => row.getString(row.fieldIndex("column_to_test"))).head shouldBe "correct_value"
field("column_to_test") of result.head() shouldBe "correct_value"

Les quelques lignes suivantes permettent cette syntaxe :

def field(name: String) = FieldMatcherOfRow(name)

case class FieldMatcherOfRow(fieldName: String) {
  def of(row: Row) = row.get(row.fieldIndex(fieldName))
}

Ainsi, la lecture se fait de façon proche du langage naturel : « Le champ « column_to_test » de la première ligne de mon DataFrame doit ĂŞtre « correct_value » ». Il faut ensuite laisser libre court Ă  son imagination pour crĂ©er d’autres contenus de ce type afin de rendre lisible tous les tests !

Simplifier l’Ă©criture des tests Spark en Python

Afin de simplifier Ă©galement l’Ă©criture de ces mĂŞmes tests Spark en Python, le manque de DSL est encore plus flagrant. En gĂ©nĂ©ral le framework PyHamcrest propose, selon mes critères (très subjectifs donc), la syntaxe la plus lisible et comprĂ©hensible. Ă€ l’instar de la version Scala, il est possible de crĂ©er des DSL qui rendent nos tests lisibles en Python. L’un des Ă©lĂ©ments importants Ă  tester sur un DataFrame est sa taille. La matcher PyHamcrest correspondant s’Ă©crit de la manière suivante :

class DataFrameCountMatcher(BaseMatcher):

    def __init__(self, expected_count):
        assert isinstance(expected_count, int), 'Provided count is not an int but %s' % type(expected_count)
        self.expected_count = expected_count

    def _matches(self, item):
        if not isinstance(item, DataFrame):
            return False
        return item.count() == self.expected_count

    def describe_to(self, description):
        description.append_text('Given DataFrame has count %d' % self.expected_count)

    def describe_mismatch(self, item, mismatch_description):
        if isinstance(item, DataFrame):
            mismatch_description.append_text('has count %d' % item.count())
        else:
            mismatch_description.append_text('%s is not a DataFrame' % type(item))


def has_count(expected_count):
    return DataFrameCountMatcher(expected_count)

Ce matcher est ensuite très facilement utilisable et produit une syntaxe claire :

class TestDataFrameCountMatcher(unittest.TestCase):

    def setUp(self):
        self.df = sql_context.createDataFrame([
            Row(key='value'),
            Row(key='value2')
        ])

    def test_has_the_right_count(self):
        assert_that(self.df, has_count(2))
Les tests sont l’affaire de tous

La prochaine fois qu’un collègue vous donnera une fausse bonne excuse pour ne pas Ă©crire ses tests en Spark, rappelez-lui que les tests sont l’affaire de tous et qu’il est possible d’Ă©crire des tests simples, maintenables et indĂ©pendants en utilisant ce framework.

Maintenant, même nos amis Data Scientists pourront écrire leurs tests unitaires !

Le prochain et dernier article de cette série traitera des tests de Spark en utilisant le Property Based Testing.

PROCHAIN ARTICLE, bientĂ´t

Tester du code Spark avec du Property Based Testing

Catégories: Blog Société

Sans Engagement fort du Leader… Pas de Transformation Agile Réussie

Qualitystreet - Jean Claude GROSJEAN - mar, 09/19/2017 - 01:35

ou Ce que j’ai appris #3: L’engagement du Top Management Troisième volet de notre sĂ©rie d’articles consacrĂ©s aux transformations agiles d’organisation. Volet 1: Des transformations Agiles enfin… agiles Volet 2: Des Saisons pour rythmer votre Transformation Agile L’engagement du Top Management dans le projet de transformation Agile est un facteur clĂ© de succès. Au delĂ  du soutien, qui…

The post Sans Engagement fort du Leader… Pas de Transformation Agile RĂ©ussie appeared first on QualityStreet - Blog Agile depuis 2007.

Catégories: Blog Individuel

De l'utilisation de Scenic pour gérer nos vues SQL

L'actualité de Synbioz - lun, 09/18/2017 - 23:00

Aujourd’hui, je souhaiterais aborder avec vous le sujet des vues SQL. Une vue, au sens SQL, peut être considérée comme un agrégat, partiel ou non, de tables accessibles en lecture uniquement. Toute altération de donnée au sein d’une table sera instantanément reflétée dans les vues qui lui sont associées. Prenons un exemple pour illustrer mon propos.

Nous avons à présent deux tables authors et books qui représentent, sans surprise, des auteur·e·s et des livres écrits par ces dernier·ère·s.

Lire la suite...

Catégories: Blog Société

Le nomadisme numérique

Barre Verte ! - dim, 09/17/2017 - 23:00

L’agriculture aurait rendu l’homme sédentaire. A l’avènement d’internet des voix ont prédit que des formes de nomadismes reviendraient.

La première conférence DNX, en 2014Ces dernières années des conférences sont nées sur ce thème comme 7in7 en 2016, Nomad Summit en 2015 ou DNX en 2014 dont la newsletter totalise 20000 abonnés et qui se tient à guichets fermés.

Ces conférences sur le nomadisme numérique adoptent des formes très classiques, avec des salles et des amphithéâtres. De la même manière que les premières conférences agiles suivaient ces formes classiques. Ensuite la forme de l’Open Space est apparue comme une manière plus “agile” de faire une conférence agile.

A la lumière de ce qui s’est passé pour l’agilité se pose donc la question : comment faire une conférence d’une manière plus nomade ?

La valeur des intermèdes

Lors d’une conférence, qu’elle soit open space ou plus classique je me déplace de salle en salle. Je trouve de la valeur à la fois dans ce que j’entends dans les salles et dans ce que j’entends en dehors des salles, dans les intermèdes, les errances, les pauses.

Le lieu que j’ai vu le plus propice à ces intermèdes ? Le chalet de la Porte Jaune où se déroule tous les ans la conférence Agile France. J’ai par exemple participé il y a deux ans de manière totalement impromptue à la discussion la plus intéressante que j’ai entendue à ce jour sur la dichotomie entre Lean Management et Agilité.

Puisque ces intermèdes-déplacements génèrent autant de valeur, la question se pose : si on leur donnait une dimension plus grande, une raison d’être à part entière, quelle forme cela prendrait-il ?

Des conférences nomades ?

J’ai participé cet été à deux conférences qui explorent ce concept.

Walking Dev

Walking Dev est une conférence open space d’une journée. Les salles de conférences ? Des cafés, un restaurant, une librairie, des jardins, un cloître. Une découverte de Toulouse jusque dans des lieux inconnus des Toulousains eux-mêmes. Et entre ces lieux, la marche à travers la ville.

Ce n’était pas une première : un participant du Sud-Ouest avait déjà utilisé ce format pour une formation à Paris pour laquelle il avait eu de très bon retours.

Nomad Open

Nomad Open se veut une conférence plus ambitieuse dans sa forme puisque les déplacements à pieds deviennent des voyages en train et chaque étape est une ville différente. Le sujet s’avère également plus radical puisqu’il s’agit du nomadisme lui même : la conférence est un espace de coworking en mouvement où les participants travaillent à leurs tâches quotidiennes comme s’ils étaient sédentaires. A la dichotomie de walking dev entre les sessions plénières statiques et les discussions informelles en marchant, nomad open substitue un triptyque :

  • coworking en mouvement
  • rencontres de communautĂ©s locales
  • confĂ©rence open space

J’ai été surpris de constater que pour certaines tâches je devenais plus productif dans un train que dans un bureau. D’autres participants ont partagé cette constatation. Ces formes éphémères de travail me paraissent donc intéressantes à explorer. Cet article ouvre une série d’articles qui relate mes apprentissages sur le sujet.

Catégories: Blog Individuel

LCC 177 - Interview sur les devs Ă  Singapour Ă  Voxxed Days Eponyme

A l’occasion de Voxxed Days Singapore, Guillaume et Emmanuel discutent avec des expatriés de Singapour (Alan, Germain, Mathieu, Nida) sur leur vie de développeur là bas.

Enregistré le 18 juillet 2017

Téléchargement de l’épisode LesCastCodeurs-Episode–177.mp3

Interview Ta vie ton Ĺ“uvre

Alan Menant Redmart Marina Bay Sands

Germain Potet Zenika Singapore

Mathieu François While 42

Nida Bouzid Active Viam

Voxxed Days Singapore

Voxxed Days Singapore

Singapour

While 42 Singlish Work holiday pass Minostry of manpower

Niveaux de vie:

  • salaire
  • loyer
  • nourriture
  • retraite / santĂ©
  • jours de vacances (14 Ă  21 jours)
  • voiture
G1 et le retour d’expérience Active Viam

Jean-Claude Van Damme malloc

Nous contacter

Faire un crowdcast ou une crowdquestion Contactez-nous via twitter https://twitter.com/lescastcodeurs sur le groupe Google https://groups.google.com/group/lescastcodeurs ou sur le site web https://lescastcodeurs.com/ Flattr-ez nous (dons) sur https://lescastcodeurs.com/ En savoir plus sur le sponsoring? sponsors@lescastcodeurs.com

 

Catégories: Blog Individuel

Partagez la connaissance

Partagez BlogsdeDeveloppeurs.com sur les réseaux sociaux