Spring Boot est un nouveau framework créé par l'équipe de chez Pivotal, conçu pour simplifier le démarrage et le développement de nouvelles applications Spring. Le framework propose une approche dogmatique de la configuration, qui permet d'éviter aux développeurs de redéfinir la même configuration à plusieurs endroits du code. Dans ce sens, Boot se veut d'être un acteur majeur dans le secteur croissant du développement d'applications rapide.
La plateforme Spring IO a vivement été critiquée au fil des années à cause de la configuration XML volumineuse et d'une gestion des dépendances complexe. Durant la conférence SpringOne 2GX de l'année dernière, le CTO de Pivotal, Adrian Colyer, a reconnu ces critiques et a insisté sur le fait que l'objectif de la plate-forme était d'offrir une expérience de développement ne nécessitant aucune configuration XML. Boot relève le défi à l'extrême, ne se contentant pas uniquement de décharger les développeurs de l'utilisation de XML, mais aussi, dans certains cas, de leur permettre de se soulager de certains imports fastidieux. Dans les jours qui ont suivi sa publication en version bêta, Boot a acquis une popularité virale en démontrant la simplicité du framework avec un exemple d'application web exécutable qui tient sur 140 caractères, partagé dans un tweet.
Pourtant, Spring Boot n'est pas une alternative à ces nombreux projets qui incluent la couche "Fondation" de la plate-forme Spring IO. L'objectif de Spring Boot n'est pas d'apporter de nouvelles solutions pour les nombreux problèmes qui ont déjà été résolus, mais plus d'influencer la plate-forme en favorisant une expérience de développement qui simplifie l'utilisation de technologies déjà existantes. Ceci fait de Boot un choix idéal pour les développeurs qui sont familiers avec l'écosystème Spring, mais aussi pour les nouveaux adoptants en leur permettant d'appréhender les technologies de Spring de manière simplifiée.
Dans la lignée de cette expérience de développement simplifiée, Spring Boot - et, en fait, l'ensemble de l'écosystème Spring - a adopté le langage de programmation Groovy. Le puissant protocole MetaObject de Groovy, son processus de transformation AST pluggable, et son moteur de résolution de dépendances embarqué sont des éléments qui facilitent beaucoup des raccourcis que Boot offre. Au coeur de son modèle de compilation, Boot utilise Groovy pour compiler les fichiers du projet, afin de décorer le bytecode généré en y ajoutant les imports courants et les méthodes standards, telles que la méthode main. Ceci permet aux applications écrites à l'aide de Boot de rester concises tout en offrant beaucoup de fonctionnalités.
Installer Boot
A son niveau le plus fondamental, Spring Boot est juste un peu plus qu'un ensemble de bibliothèques qui peuvent être exploitées par n'importe quel système de build. Afin de simplifier les choses, le framework propose aussi une interface en lignes de commandes, qui peut être utilisée pour exécuter et tester des applications Boot. La distribution du framework, qui inclut le client intégré, peut être téléchargée et installée manuellement à partir du dépôt Spring. Une approche encore plus pratique consiste à utiliser le Groovy enVironment Manager (GVM), qui se chargera de l'installation et de la gestion des versions de Boot. Boot et ses clients peuvent être installés à l'aide de la ligne de commande GVM, gvm install springboot
. Des formules sont disponibles pour installer Boot sur OS X avec le gestionnaire de paquets Homebrew. Pour se faire, commencez par souscrire au dépôt Pivotal à l'aide de brew tap pivotal/tap
, suivi de la commande brew install springboot
.
Les projets destinés à être packagés et distribués devront se reposer sur un moteur de production tel que Maven ou Gradle. Pour simplifier le graphe de dépendances, les fonctionnalités de Boot sont modulaires, et des groupes de dépendances peuvent être ajoutés à un projet en important les "starter" modules. Pour gérer simplement les versions des dépendances et s'appuyer sur la configuration par défaut, le framework expose un POM parent, dont les projets peuvent hériter. Un exemple de POM pour un fichier Spring Boot est défini ci-dessous.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>1.0.0-SNAPSHOT</version>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.0.RC1</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Pour une configuration du build plus simple, les développeurs peuvent utiliser Gradle qui s'appuie sur le DSL de Groovy.
buildscript {
repositories {
maven { url "http://repo.spring.io/libs-snapshot" }
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-actuator:1.0.0.RC1'
}
Afin d'aider au démarrage de projets Boot, Pivotal propose l'interface web "Spring Initializr", qui peut être utilisée pour générer des configurations Maven ou Gradle pré-construites. Les projets peuvent aussi être rapidement démarrés en utilisant un template Lazybones, qui créera la structure de projet adéquate ainsi que le fichier de construction Gradle en exécutant la ligne de commnade lazybones create spring-boot-actuator my-app
.
Développer une application Spring Boot
L'exemple le plus populaire d'une application Spring Boot est celui qui a été posté sur Twitter juste après l'annonce de la sortie publique du framework. Comme on peut le voir ci-dessous, un simple fichier Groovy permet de réaliser une puissante application basée sur Spring.
@RestController
class App {
@RequestMapping("/")
String home() {
"hello"
}
}
Cette application peut être lancée à partir du client Spring Boot, en exécutant la commande spring run App.groovy
. Boot analyse le fichier - à l'aide de divers identifiants connus sous le nom "compiler auto-configuration" - et détermine que l'application est de type web. Ensuite, Boot démarre le contexte Spring à l'intérieur d'un conteneur Tomcat embarqué sur le port par défaut 8080. En ouvrant un navigateur web et en se connectant à l'URL fournie, on obtient une page qui contient une simple réponse au format texte, "hello". Ce procédé, qui consiste à fournir au développeur un contexte applicatif par défaut et un conteneur embarqué, permet au développeur de se concentrer sur le processus de développement applicatif et sur la logique métier, et le déleste de la contrainte d'écrire la configuration technique très répétitive.
Ce qui fait de Boot un outil puissant pour le développement d'applications rapide est sa capacité à établir la fonctionnalité d'une classe. Lorsque les applications sont exécutées à l'aide du client Boot, elles sont compilées à l'aide du compilateur Groovy intégré, ce qui leur permet d'inspecter et de modifier la classe à la génération du bytecode. De cette manière, les développeurs qui utilisent le client sont non seulement affranchis de la définition de la configuration par défaut, ils sont en plus déchargés de certains imports qui peuvent être reconnus et ajoutés lors de la compilation. De plus, lorsque les applications sont exécutées à l'aide du client, le gestionnaire de dépendances intégré à Groovy, "Grape", est utilisé pour résoudre les dépendances nécessaires aux environnements de compilation et d'exécution, comme le déterminent les mécanismes d'auto-configuration du compilateur de Boot. Cette approche n'a pas pour unique but d'être conviviale, elle permet en outre à Spring Boot d'être associé à des versions spécifiques des bibliothèques de la plate-forme Spring IO, ce qui veut dire que les développeurs n'ont plus besoin de gérer des graphes de dépendances et un processus de gestion des versions complexe. De plus, ceci permet de mettre en oeuvre la création rapide de prototypes et autres preuves de concept.
Pour les projets qui ne sont pas créés à partir du client, Boot propose un ensemble de "starter" modules, qui définissent des collections de dépendances qui peuvent être ajoutées au système de build afin de résoudre les librairies spécifiques nécessaires pour le framework et sa plate-forme. Par exemple, la dépendance spring-boot-starter-actuator
remonte un ensemble de dépendances Spring de base qui permettent d'obtenir rapidement une application configurée et qui tourne. L'objectif de cette dépendance est de développer des applications web, et plus spécifiquement des web services RESTful. Lorsqu'elle est associée à la dépendance spring-boot-starter-web
, elle fournit la configuration automatique d'un conteneur Tomcat embarqué, ainsi qu'un ensemble d'endpoints utiles pour un micro-service, tels que les informations du serveur, les métriques de l'application ou des détails du contexte. En plus, lorsque le module spring-boot-starter-security
est ajouté, le mécanisme configure automatiquement Spring Security pour ajouter à l'application un système d'authentification basique ainsi que d'autres fonctionnalités de sécurité plus avancées. Pour n'importe quel type d'application, il ajoute aussi un framework interne d'audit qui peut être utilisé pour le reporting ou pour des besoins spécifiques à l'application, tels que le développement d'une politique d'exclusion en cas d'erreur d'authentification.
L'exemple ci-dessous montre comment démarrer une application web basée sur Spring, à partir d'un projet Java sous Maven.
package com.infoq.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
public class Application {
@RequestMapping("/")
public String home() {
return "Hello";
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
La présence de l'annotation @EnableAutoConfiguration
sur la classe Application informe Boot qu'il doit adopter une approche dogmatique de la configuration pour l'application. Cette option fixe la configuration aux valeurs par défaut définies par le framework, ce qui permet d'obtenir une application fonctionnelle le plus rapidement possible. La classe Application
est exécutable, ce qui veut dire que l'application, et son conteneur embarqué, peuvent être démarrés et développés activement en choisissant de démarrer la classe en tant qu'application Java.
Lorsque l'heure de construire l'archive de distribution du projet arrive, les plugins Maven et Gradle de Boot enrichissent les systèmes de packaging par défaut pour produire un "fat jar" exécutable, qui embarque l'ensemble des dépendances du projet et qui peut être lancé en tant que jar exécutable. Packager une application Boot avec Maven est aussi simple que lancer la commande mvn package
, de même qu'avec Gradle, exécuter la commande gradle build
produira un jar exécutable.
Développer des Micro-Services
En se basant sur les simplifications que Boot apporte au développement d'applications Spring - sa faculté d'importer des dépendances de manière modulaire, l'accent qui est mis sur le développement de web services RESTful, et sa capacité à produire un jar exécutable - le framework est clairement un outil formidable pour le développement de micro-services. Comme le démontrent les exemples précédents, obtenir une application démarrée et prête à fonctionner est une tâche triviale avec Boot. Mais pour réaliser le réel potentiel de Boot, nous allons démontrer les subtilités du développement d'un micro-service RESTful complet. Les micro-services sont de plus en plus populaires dans les architectures applicatives d'entreprise, dans la mesure où ils permettent un développement rapide, des bases de codes réduites, et des livrables modulaires. Il y a beaucoup de frameworks qui visent ce type de développement vertical, et cette section va analyser l'utilisation de Boot dans ce but.
Accès à une base de données
Les micro-services peuvent être créés pour des objectifs très variés, mais ce qui est sûr, c'est que la plupart auront besoin de lire et d'écrire dans une base de données. Spring Boot permet de mettre en place l'intégration d'une base de données de manière très simple en offrant un accès Spring Data aux bases de données auto-configurable. En incluant simplement le module spring-boot-starter-data-jpa
dans votre projet, le moteur d'auto-configuration de Spring Boot détecte que votre projet nécessite un accès à une base de données, et crée les beans nécessaires dans votre contexte applicatif Spring, afin que vous puissiez utiliser des référentiels de données et des services. Pour illustrer cette fonctionnalité plus précisément, considérons le fichier de build Gradle ci-dessous, qui met en avant la structure de build d'un micro-service web Boot, basé sur Groovy, qui utilise le support Spring Data JPA pour l'accès à la base de données.
buildscript {
repositories {
maven { url "http://repo.spring.io/libs-snapshot" }
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
}
}
apply plugin: 'groovy'
apply plugin: 'spring-boot'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
}
ext {
springBootVersion = "1.0.0.RC1"
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.2.1'
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}
Dans cette configuration, l'actuator
de Boot fournit une dépendance sur une base hsqldb
, et met en place toute la configuration nécessaire - dont la création du schéma - afin que Spring Data puisse utiliser la base de données relationnelle en mémoire en tant que source de données. Ce raccourci dispense les développeurs de la création et de la maintenance d'une datasource XML compliquée, et permet de passer rapidement au développement du micro-service. La configuration est aussi automatique si les bases de données embarquées Derby et H2 sont référencées dans le classpath. Une simplification supplémentaire offerte par Boot est la possibilité de créer rapidement et simplement un schéma de base de données contenant des données. Ceci est très utile pour le développement, où une base de données peut être en mémoire ou temporaire, et où les développeurs ont besoin d'être sûrs qu'un certain nombre de données sont disponibles au démarrage de l'application. Pour illustrer ceci, considérons l'entité JPA ci-dessous, qui représente la structure de données d'un "User" que le micro-service fournira.
@Entity
class User {
@Id
@GeneratedValue
Long id
String username
String firstName
String lastName
Date createdDate
Date lastAccessed
Boolean isActive = Boolean.TRUE
}
Pour insérer des données qui représentent des objets User
au démarrage de l'application, vous pouvez simplement créer un fichier nommé schema.sql
ou data.sql
et l'inclure dans votre classpath. Ce fichier sera exécuté après la création du schéma. Donc, en considérant l'entité User, nous pouvons créer un compte utilisateur avec une instruction SQL.
insert into user(username, first_name, last_name, created_date) values ('danveloper', 'Dan', 'Woods', now())
Durant le démarrage de l'application, le code SQL fourni sera exécuté, et nous serons sûrs que nous disposerons d'un compte de tests avec lequel nous pourrons travailler. A présent, notre micro-service dispose de données avec lesquelles il peut démarrer. On peut à présent, en suivant le pattern de développement Spring Data, créer une interface Repository
qui fera office d'objet d'accès aux données pour l'entité User.
public interface UserRepository extends CrudRepository<User, Long> {
}
L'interface CrudRepository
fournit les méthodes de base pour créer, lire, mettre-à-jour et supprimer des objets ou des ensembles d'objets. Toute fonctionnalité spécifique dont notre application pourrait avoir besoin peut être définie en suivant les conventions de développement des référentiels Spring Data. Une fois l'interface UserRepository
créée, la couche spring-data-jpa
la détectera et l'ajoutera automatiquement au contexte applicatif Spring, lui permettant d'être injectée automatiquement dans les contrôleurs et les services. Cette configuration automatique intervient uniquement lorsque l'application Boot choisit explicitement une approche dogmatique, qui est déclenchée par la présence de l'annotation @EnableAutoConfiguration
. Le micro-service peut à présent définir un endpoint RESTful qui permet aux consommateurs d'extraire la liste des utilisateurs ou un seul utilisateur, à travers l'implémentation suivante.
@RestController
@EnableAutoConfiguration
@RequestMapping("/user")
class UserController {
@Autowired
UserRepository repository
@RequestMapping(method=[RequestMethod.GET])
def get(Long id) {
id ? repository.findOne(id) : repository.findAll()
}
public static void main(String[] args) {
SpringApplication.run UserController, args
}
}
Au démarrage, on pourra observer dans les logs la création de la structure de la base de données définie par l'entité User
et l'importation des données à partir du fichier schema.sql
à la fin de l'initialisation de l'application.
Il est important de noter l'utilisation de l'annotation @RequestMapping
pour développer le micro-service. Il ne s'agit pas d'une annotation qui est spécifique à Boot. Quoi qu'il en soit, parce que Boot installe ses propres endpoints afin de permettre la supervision des performances de l'application, de son état et de sa configuration, nous voulons nous assurer que notre application n'entre pas en conflit avec la résolution de ces services par défaut. En partant de là, quand notre endpoint s'appuie sur la résolution d'une propriété (dans notre cas l'id
de l'utilisateur) à partir de l'URL de la requête, nous devons porter une attention particulière aux éventuels conflits que cette résolution dynamique pourrait engendrer avec le reste des fonctionnalités du micro-service. Dans ce cas, mapper simplement le contrôleur vers l'endpoint /user
le sort du contexte racine et permet d'éviter les conflits avec les endpoints internes de Boot.
Toutes les données servies par notre micro-service peuvent ne pas être adaptées à une approche relationnelle. Pour répondre à ce type de besoins, Spring Boot propose des modules permettant de travailler avec les projets Spring Data pour MongoDB et Redis, tout en conservant l'approche dogmatique dans leur configuration. L'abstraction de haut niveau que propose Spring Data pour définir des Data Access Objects permet de permuter plus simplement entre des sources de données JPA et non-JPA. L'exemple ci-dessous propose un UserRepository
conçu pour utiliser MongoDB à la place de JPA.
public interface UserRepository extends MongoRepository<User, Long> {
}
L'interface MongoRepository
étend CrudRepository
, ce qui permet au contrôleur du micro-service élaboré précédemment de ne pas être modifié. Afin de mettre en oeuvre l'intégration de MongoDB, il faut simplement ajouter spring-boot-starter-data-mongodb
au classpath de l'application. Le bloc de dépendances du fichier de configuration Gradle subit un léger changement.
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.2.1'
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}
Maintenant que la dépendance MongoDB a été ajoutée au classpath, Boot va configurer automatiquement Spring Data afin de se connecter sur localhost
, par défaut sur la base de données test
. A partir de là, la collection User
sera créée automatiquement (ce qui est standard avec MongoDB), et le micro-service s'appuie désormais sur une base MongoDB. Injecter des données d'initialisation avec des sources de données non JPA est un peu moins simple, mais c'était prévisible dans là mesure ou ça n'aurait pas eu vraiement de sens d'exécuter un script SQL sur une base de données orientée document telle que MongoDB, ou sur une base de données orientée clé-valeur telle que Redis. Dans la mesure où Spring Data utilisera des versions persistantes de ces bases de données, ceci implique que les données créées en développement survivront à un redémarrage. Pour commencer à travailler avec des données persistantes, nous devons modifier le contrôleur de notre micro-service afin de lui permettre de créer des instances de User
. Nous pouvons aussi faire évoluer notre UserControlleur
afin qu'il soit conforme à une architecture RESTful, en permettant au contrôleur de gérer différentes méthodes HTTP de manières différentes. L'exemple ci-dessous montre comment ajouter au contrôleur la possibilité de créer des instances de User
.
@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {
@Autowired
UserRepository repository
@RequestMapping(method=[RequestMethod.GET])
def get(Long id) {
id ? repository.findOne(id) : repository.findAll()
}
@RequestMapping(method=[RequestMethod.POST])
def create(@RequestBody User user) {
repository.save user
user
}
public static void main(String[] args) {
SpringApplication.run UserController, args
}
}
Quand le consommateur d'un micro-service effectue un POST HTTP sur l'endpoint de l'application, Spring transforme le corps de la requête en une instance de User
. L'application utilise ensuite le UserRespository
pour stocker l'objet dans une collection MongoDB. Ci-dessous, un exemple qui utilise curl
pour créer une instance de User
.
curl -v -H "Content-Type: application/json" -d "{ \"username\": \"danveloper\", \"firstName\": \"Dan\", \"lastName\": \"Woods\", \"createdDate\": \"2014-02-02T00:00:00\" }" http://localhost:8080/user
Avec Boot qui propose une vision sur la manière dont la source de données MongoDB doit être configurée, la nouvelle instance de User
sera par défaut persistée dans une collection user
dans la base de données test
sur l'instance MongoDB qui tourne sur localhost
. Si nous ouvrons un navigateur web et effectuons une GET HTTP vers le micro-service, nous verrons que le user que nous venons de créer est dans la liste.
Configuration
Spring Boot vous laisse assez simplement le champ libre lorsque vous désirez surcharger sa configuration par défaut. La configuration de l'application peut être définie par défaut à l'aide d'un fichier application.properties
placé à la racine du classpath de l'application. Une meilleure approche consiste à utiliser la configuration YAML, qui est plus structurée et permet de créer une configuration arborescente. A partir du moment ou vous ajoutez la dépendance snakeyaml
dans le classpath de l'application, le projet peut être configuré à l'aide d'un fichier application.yml
. L'exemple ci-dessous est un fichier de configuration YAML montrant différents paramètres qui sont disponibles pour le serveur HTTP embarqué de l'application (par défaut Tomcat, optionnellement Jetty).
# Server settings (ServerProperties)
server:
port: 8080
address: 127.0.0.1
sessionTimeout: 30
contextPath: /
# Tomcat specifics
tomcat:
accessLogEnabled: false
protocolHeader: x-forwarded-proto
remoteIpHeader: x-forwarded-for
basedir:
backgroundProcessorDelay: 30 # secs
La possibilité de surcharger la configuration automatique de Boot vous permettra de passer simplement votre application du prototype à la version de production, et Boot vous permet de le faire au sein d'un même fichier application.yml
. Les directives de configuration automatique sont conçues pour être les plus courtes possibles, ainsi, lorsque l'on construit un micro-service avec actuator
, un endpoint de configuration de propriétés /configprops
est installé, et il est possible de s'y référer pour déterminer quelle directive peut être surchargée. Lorsque nous somme prêts pour que notre micro-service utilise une source de données persistante, comme MySQL, nous pouvons simplement ajouter le Driver Java MySQL au classpath, et ajouter la configuration suivante au fichier application.yml
.
spring:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/proddb
username: root
password
Dans les cas qui nécessitent une configuration plus flexible, Boot vous permet de surcharger l'essentiel de sa configuration par défaut en utilisant des propriétés système Java. Par exemple, si votre application s'appuie sur un user base de données différent en production, la propriété username peut être passée à l'application en ajoutant une propriété système Java standard à la ligne d'exécution, -Dspring.datasource.username=user
. Un autre cas pratique est le déploiement chez un hébergeur de type cloud tel que Cloud Foundry ou Heroku. Ces plate-formes nécessitent que l'application démarre sur un port HTTP spécifique, qui est disponible au travers d'une variable d'environnement disponible sur le système de l'hébergeur. La capacité de Boot d'extraire sa configuration à partir des propriétés système permet à l'application de configurer son port HTTP à l'aide de la ligne de commande, en utilisant l'argument -Dserver.port=$PORT
. Cette fonctionnalité du framework est particulièrement utile dans le développement de micro-services, parce qu'elle permet au micro-service d'être exécuté dans des environnements très variés.
Externaliser la configuration
Une fonctionnalité très importante qui doit pouvoir être supportée par un micro-service est l'externalisation de la configuration. Cette configuration peut embarquer des paramètres très variés, d'un message d'un formulaire à une configuration de base de données, et l'architecture sous-jacente doit être prévue au moment de la planification initiale et du prototypage d'une application. Plusieurs stratégies existent actuellement pour importer une configuration dans la plate-forme Spring IO, cependant, supporter plusieurs moyens d'importer la configuration mène souvent à un couplage programmatique fort.
Une fonctionnalité de niche de Boot est sa capacité à gérer automatiquement une configuration externalisée, et à la transformer en une structure d'objets qui peut être ensuite utilisée dans le contexte de l'application. En créant un Plain Old Java/Groovy Ojbect, et en le décorant à l'aide de l'annotation @ConfigurationProperties
, cet objet sera initialisé à l'aide des propriétés définies sous la clé name
de la structure de la configuration Boot. Considérons le POGO ci-dessous, il contient les paramètres de configuration présents sous la clé application
.
@ConfigurationProperties(name = "application")
class ApplicationProperties {
String name
String version
}
Lorsque l'objet ApplicationProperties
est créé dans le contexte Spring, Boot reconnaît cet objet comme étant un objet de configuration, cet objet est initialisé à l'aide des paramètres de configuration issus du fichier application.properties
ou du fichier application.yml
présent dans le classpath. En partant de là, nous avons ajouté un bloc application
dans le fichier appication.yml
de notre application, nous allons pouvoir accéder aux propriétés de manière programmatique dans le reste de notre application.
application:
name: sb-ms-custdepl
version: 0.1-CUSTOMER
Ces paramètres de configuration peuvent être utilisés à des fins très diverses, la seule condition pour pouvoir y accéder étant que le POJO/POGO qui les représente fasse partie du contexte Spring. Boot nous permet de facilement gérer l'ajout des beans de configuration dans le contexte de l'application en nous permettant d'utiliser un contrôleur comme un objet de configuration, comme dans l'exemple ci-dessous.
@RestController
@Configuration
@RequestMapping("/appinfo")
@EnableAutoConfiguration
class AppInfoController {
@Autowired
ApplicationProperties applicationProperties
@RequestMapping(method=[RequestMethod.GET])
def get() {
[
name: applicationProperties.name,
version: applicationProperties.version
]
}
@Bean
ApplicationProperties applicationProperties() {
new ApplicationProperties()
}
public static void main(String[] args) {
SpringApplication.run UserController, args
}
}
Le code ci-dessus est un exemple artificiel, les principes d'accès aux propriétés de configuration en utilisant Boot sont exactement les mêmes. Les classes de configuration peuvent aussi supporter des graphes d'objets imbriqués afin de donner plus de profondeur et de sens aux données issues de la configuration. Par exemple, si nous voulions disposer de propriétés pour nos métriques à la racine application
, nous pourrions ajouter un objet imbriqué au POGO ApplicationProperties
pour représenter ces valeurs.
@ConfigurationProperties(name = "application")
class ApplicationProperties {
String name
String version
final Metrics metrics = new Metrics()
static class Metrics {
String dbExecutionTimeKey
}
}
A présent, notre fichier application.yml
peut être conçu en incluant la configuration metrics
sous le bloc application
.
application:
name: sb-ms-custdepl
version: 0.1-CUSTOMER
metrics:
dbExecutionTimeKey: user.get.db.time
Quand nous devons accéder à la valeur de application.metrics.dbExecutionTimeKey
, nous pouvons y accéder programmatiquement à l'aide de l'objet ApplicationProperties
.
Les paramètres de configuration qui sont présents dans les fichiers application.properties
ou application.yml
n'ont pas nécessairement besoin d'être associés à un graphe d'objets pour être utilisés dans l'application. En effet, Boot propose aussi le contexte applicatif Spring avec un PropertySourcesPlaceholderConfiguration
qui permet aux propriétés d'être extraites du fichier application.properties
ou du fichier application.yml
ou d'une surcharge par une propriété système Java qui peut être utilisée pour alimenter les propriétés. Ce mécanisme interne à Spring permet d'injecter la valeur d'une propriété dans un attribut en utilisant une syntaxe spécifique, Spring résoud ensuite cette valeur si la propriété correspondante est présente dans la configuration. Dans l'exemple ci-dessous, on utilise l'annotation @Value
pour accéder directement à la propriété application.metrics.dbExecutionTimeKey
dans notre contrôleur.
@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {
@Autowired
UserRepository repository
@Autowired
GaugeService gaugeService
@Value('${application.metrics.dbExecutionTimeKey}')
String dbExecutionKey
@RequestMapping(method=[RequestMethod.GET])
def get(Long id) {
def start = new Date().time
def result = id ? repository.findOne(id) : repository.findAll()
gaugeService.submit dbExecutionKey, new Date().time - start
result
}
public static void main(String[] args) {
SpringApplication.run UserController, args
}
}
Nous approfondirons les subtilités de la manipulation des métriques plus tard, mais pour l'instant, la chose à retenir est comment l'annotation @Value
peut être associée à un placeholder Boot pour auto alimenter la valeur qui correspond aux besoins spécifiques de notre micro-service.
Sécurité
Lors du développement d'un micro-service, le besoin de disposer d'un contexte de sécurité complet se fait vite ressentir. Pour adresser ce besoin, Boot s'appuie sur le puissant et complet Spring Security et fournit une configuration automatique qui permet de mettre en place rapidement et simplement une couche de sécurité. La simple présence du module spring-boot-starter-security
dans le classpath de l'application active certaines des fonctionnalités de sécurité de Boot telles que la protection contre le cross-site scripting et l'ajout de headers permettant d'empêcher le click-jacking. De plus, en ajoutant la propriété ci-dessous à la configuration de l'application, vous pouvez sécuriser votre application avec de l'authentification basique.
security:
basic:
enabled: true
Boot fournit un compte utilisateur par défaut : user
; un rôle par défaut : USER
; et affiche un mot de passe généré automatiquement dans la console au démarrage de l'application. Comme pour la plupart des autres modules Boot, il est simple de paramétrer un nom d'utilisateur et un mot de passe différents du compte user
proposé par défaut à l'aide de valeurs explicites, comme nous pouvons le voir ci-dessous.
security:
basic:
enabled: true
user:
name: secured
password: foo
Les possibilités offertes par Boot de mettre en place rapidement une authentification basique dans votre micro-service peut s'avérer très utile pour des applications internes et simples, ou pour le développement de prototypes. Au fur et à mesure de l'évolution de votre besoin, votre application nécessitera sans doute une politique de sécurité permettant une granularité plus fine, telle que la possibilité d'associer des endpoints à des rôles spécifiques. En partant de cette perspective, nous pouvons vouloir sécuriser des données afin qu'elles soient accessibles en lecture seule (les requêtes GET) pour les utilisateurs qui sont associés avec le rôle USER
, tandis que les données accessibles en lecture-écriture (les requêtes POST) devraient être sécurisées avec le rôle ADMIN
. Pour permettre ceci, nous allons désactiver la configuration automatique de l'authentification basique dans le fichier application.yml
du projet, et nous allons définir nos propres comptes user
et admin
ainsi que leurs rôles respectifs. Ceci est un autre exemple qui montre qu'il est simple de contourner Boot lorsque vous avez besoin d'aller au-delà de sa configuration par défaut. Pour démontrer ceci de manière pratique, voyons l'exemple ci-dessous. Cet exemple permet de souligner le potentiel de Spring Security et peut être un point de départ pour mettre en place des stratégies d'authentification plus élaborées telles que JDBC-backed, OpenID ou Single-Sign On.
@RestController
@RequestMapping("/user")
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableAutoConfiguration
class UserController extends WebSecurityConfigurerAdapter {
@Autowired
UserRepository repository
@RequestMapping(method = [GET])
@Secured(['ROLE_USER'])
def get(Long id) {
id ? repository.findOne(id) : repository.findAll()
}
@RequestMapping(method = [POST])
@Secured(['ROLE_ADMIN'])
def create(@RequestBody User user) {
repository.save user
user
}
@Override
void configure(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser "user" password "password" roles "USER" and() withUser "admin" password "password" roles "USER", "ADMIN"
}
@Override
void configure(HttpSecurity http) throws Exception {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint()
entryPoint.realmName = "Spring Boot"
http.exceptionHandling().authenticationEntryPoint(entryPoint)
http.requestMatchers().antMatchers("/**").anyRequest()
.and().httpBasic().and().anonymous().disable().csrf().disable()
}
public static void main(String[] args) {
SpringApplication.run UserController, args
}
}
En partant de l'exemple ci-dessus, l'authentification de l'application est à présent configurée pour permettre l'accès à des comptes utilisateurs user
et admin
, tous les deux disposant du mot de passe password
, et associés respectivement aux rôles USER
et ADMIN
. Les endpoints GET et POST du micro-service sont sécurisés pour les rôles USER
et ADMIN
, ce qui veut dire que les données en lecture seule sont accessibles pour les utilisateurs normaux tandis que les opérations de lecture-écriture nécessitent d'être authentifier avec l'utilisateur admin
.
L'authentification basique est un bon choix pour les micro-services parce qu'il s'agit d'un protocole d'authentification simple et largement supporté. En d'autres termes, la plupart des consommateurs de l'API, comme par exemple des applications mobiles, peuvent très simplement utiliser ce protocole. Si votre authentification a besoin de plus que de l'authentification basique (ie. OpenID, OAuth), votre micro-service peut alors s'appuyer sur les fonctionnalités offertes par Spring Security pour remplir votre besoin.
Intégration d'un service de Messaging
Le messaging est un outil très utile pour n'importe quel type d'applications, et les micro-services ne font pas exception à cette règle. Développer ces applications sur des architectures orientées événements leur permet d'être à la fois scalables et réutilisables. Spring Boot permet aux développeurs d'écrire des micro-services qui s'appuient sur le messaging comme composant principal dans l'architecture, dans la mesure où il utilise l'implémentation de la plate-forme Spring IO des patterns d'intégration en entreprise, Spring Integration. Spring Integration fournit la structure permettant de développer des architectures orientées messages, de même que des modules permettant l'intégration avec les plate-formes d'entreprises distribuées. Cette fonctionnalité permet aux micro-services de s'appuyer sur les objets métiers d'une source de messages abstraite, que cette source fasse partie de l'application ou qu'elle soit fournie par un autre service de l'organisation.
Bien que Boot ne fournisse pas de configuration automatique du contexte Spring, il propose un start module pour Spring Integration, qui permet d'ajouter automatiquement les dépendances du projet Spring Integration. Ces dépendances incluent les bibliothèques du noyau de Spring Integration, son module HTTP (pour une intégration d'entreprise orientée HTTP), son module IP (pour les intégrations basées sur des Socket), son module File (pour l'intégration du système de fichiers) et son module Stream (qui permet de travailler avec des flux, comme stdin et stdout). Ce start module offre aux dévelopeurs une solide boite à outils de fonctionnalités de messaging, ce qui leur permet d'adapter une infrastructure existante à une API de micro-services.
En plus de ce start module, Boot se charge de configurer automatiquement le compilateur pour que les applications soient construites en mode client. Ce qui offre des raccourcis aux développeurs qui créent des prototypes de micro-services et qui veulent faire une étude de faisabilité. Les applications qui s'appuient sur une plate-forme d'entreprise peuvent être rapidement développées, et évaluées, avant qu'elles soient intégrées à un projet et un système de build formel. Créer un micro-service orienté messages qui s'appuie sur Spring Boot et sur Spring Integration est aussi simple que le montre l'exemple ci-dessous.
@RestController
@EnableIntegrationPatterns
class App {
@Bean
def userLookupChannel() {
new DirectChannel()
}
@Bean
def userTemplate() {
new MessagingTemplate(userLookupChannel())
}
@RequestMapping(method=[RequestMethod.GET])
def get(@RequestParam(required=false) Long id) {
userTemplate().convertSendAndReceive( id ? id : "")
}
}
class User {
Long id
}
@MessageEndpoint
class UserLookupObject {
@ServiceActivator(inputChannel="userLookupChannel")
def get(Long id) {
id ? new User(id:id) : new User()
}
}
Utiliser l'approche orientée messages pour un micro-service apporte une très bonne réutilisabilité du code et un bon découplage par rapport à l'implémentation du service sous-jacent. Dans un scénario plus proche de la réalité, le code ci-dessus peut être utilisé pour composer des données issues d'une base de données avec celles d'un service extérieur au sein d'une organisation d'entreprise. Spring Integration dispose de fonctionnalités intégrées pour la répartition de charge et de chaînage des handlers, ce qui en fait une solution attrayante pour la composition de données, pour laquelle notre micro-service serait le parfait fournisseur.
Fournir des métriques
Une des fonctionnalités les plus importantes d'un micro-service est sa capacité à fournir des métriques à un agent de supervision. Contrairement aux applications web plus denses, les micro-services sont légers et ne sont pas conçus avec l'intention de fournir des écrans de reporting ou autres interfaces élaborées pour analyser l'activité du service. Il est souvent préférable de laisser ce type d'opérations aux applications qui sont destinées à l’agrégation et à l'analyse des données pour mesurer la stabilité, les performances ou encore dans le domaine de l'informatique décisionnelle. En partant de ce principe, un micro-service doit fournir des endpoints qui sont consommés par ces outils de reporting pour récolter simplement les données liées à son activité. A partir de là, il est de la responsabilité de l'outil de reporting de composer les données dans une vue ou dans un rapport qui a du sens pour les gens qui exploitent les données.
Alors que certaines métriques d'un micro-service, telles que la stabilité ou la performance peuvent être généralisées entre toutes les applications, les métriques liées aux opérations métiers doivent être gérées de manière spécifique dans l'application. Dans ce but, le module actuator
de Spring Boot expose un mécanisme qui permet aux développeurs d'exposer programmatiquement des détails sur l'état du micro-service à l'aide du endpoint /metrics
. Boot décompose les métriques en "compteurs" et en "jauges", un compteur représente n'importe quelle métrique qui peut est représentée comme un nombre entier, alors qu'une jauge est une métrique qui mesure des calculs à double précision. Afin de travailler simplement avec les métriques pour les développeurs de micro-services, Boot expose un CounterService
et un GaugeService
qui peuvent être injectés automatiquement dans le contexte de l'application. L'exemple ci-dessous propose une utilisation de CounterService
.
@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {
@Autowired
UserRepository repository
@Autowired
CounterService counterService
@RequestMapping(method = [GET])
def get() {
get(null)
}
@RequestMapping(value="/{id}", method = [GET])
def get(@PathVariable Long id) {
counterService.increment id ? "queries.by.id.$id" : "queries.without.id"
id ? repository.findOne(id) : repository.findAll()
}
}
En appelant le endpoint /user
avec ou sans ID, le endpoint /metrics
ajoutera des clés sous la racine counter
. Par exemple, si nous appelons simplement le endpoint /user
sans ID, alors la métrique counter.queries.without.id
sera ajoutée et disponible. De la même manière si nous fournissons un ID, la clé counter.queries.by.id.<id>
nous indiquera combien de requêtes ont été faite sur l'ID qui a été fournit. Ces métriques peuvent nous donner une idée de l'objet User
auquel les consommateurs du services accèdent le plus souvent, et permettent de mettre en place des actions telles que la mise en place d'un cache ou de l'indexation de la base de données. De la même manière, qu'il permet d'incrémenter une métrique, le CounterService
permet de décrémenter une métrique jusqu'à zéro. Cette fonctionnalité peut être très utile pour le suivi des connexions ouvertes ou pour tout autre mesure effectuée dans le temps.
Les jauges sont des métriques légèrement différentes, dans le sens où elles fournissent des heuristiques pour des valeurs pré-calculées ou calculées à la demande. Comme l'indique la JavaDoc de GaugeService
, les métriques destinées à être mesurées par une jauge peuvent être n'importe quoi du temps d'exévution d'une méthode à la température de la salle de réunion. Ces types de mesures sont particulièrement bien adaptées pour le GaugeService
lorsqu'on veut les exposer à l'aide d'un outil de reporting. Les métriques de type jauge sont disponibles à l'aide du endpoint /metrics
et sont préfixés par gauge.
. Elles ne sont pas alimentées de la même manière que les compteurs, comme dans l'exemple ci-dessous.
@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {
@Autowired
UserRepository repository
@Autowired
GaugeService gaugeService
@RequestMapping(method = [GET])
def get() {
get(null)
}
@RequestMapping(value="/{id}", method = [GET])
def get(@PathVariable Long id) {
def start = new Date().time
def result = id ? repository.findOne(id) : repository.findAll()
def time = new Date().time - start
gaugeService.submit("user.get.db.time", time.doubleValue())
result
}
}
Par défaut, les métriques sont stockées dans une base de données non persitante, en mémoire, mais l'ajout d'un MetricsRepository
au contexte de l'application, permet de mettre en place la persistance des métriques. Boot fournit un RedisMetricsRepository
qui peut être injecté automatiquement pour stocker des métriques dans une base Redis. En outre, n'importe quelle implémentation de n'importe quelle base de données peut être conçue pour sauvegarder les métriques.
Boot intègre par ailleurs un support pour la bibliothèque Coda Hale Metrics, et convertit certaines métriques qui suivent une convention de nommage définie aux types de Métriques correspondants. Par exemple, si une métrique commence par histogram.
, alors Boot fournit cette valeur sous la forme d'un objet de type Histogram
. Ce typage automatique fonctionne aussi avec les clés meter.
et timer.
, alors que les autres métriques sont envoyées avec le type Gauge
.
Une fois que les métriques d'un micro-service ont été enregistrées avec Boot, elles peuvent être retrouvées à l'aide du endpoint /metrics
. On peut ajouter le nom de la métrique au endpoint /metrics
afin de chercher une métrique en particulier. Par exemple, pour accéder seulement à la jauge "user.get.db.time", l'outil de reporting peut utiliser la requête /metrics/gauge.user.get.db.time
.
Distribution des applications Boot
Comme nous l'avons évoqué plus haut, Boot propose des plugins à la fois pour Maven et pour Gradle, qui permettent la création d'un "fat jar", qui contient l'ensemble des dépendances du projet, lors de la phase de packaging du système de build. Lorsque ce fat jar est exécuté, le code de l'application sera exécuté dans le même conteneur embarqué que celui qui a été utilisé lors du développement. Ce raccourci permet aux développeurs d'avoir l'esprit tranquille sur le fait que leur livrable dispose des mêmes dépendances et du même environnement d'exécution que lors du développement. Ce qui a l'avantage de permettre aux équipes de production de ne pas être confrontées à des scénarios dans lesquels un conteneur mal configuré dispose d'une version dépendance alors que le développement s'est basé sur une autre version.
Pour effectuer le packaging à l'aide de Maven, il suffit d'exécuter la commande mvn package
. Le plugin Spring Boot fera une sauvegarde du jar initialement créé sous la forme d'un fichier préfixé par ".original". A partir de là, le jar exécutable sera disponible, en suivant les conventions de nommage des artefacts Maven, et peut ensuite être déployé de la manière la plus appropriée en fonction du projet. Construire un projet Boot à l'aide de Gradle est tout aussi simple, il suffit d'exécuter la commande gradle build
. De la même manière que pour Maven, le plugin Boot ajoute un événement au cycle de vie qui s'exécute à la suite de la tâche de packaging standard de Gradle, et qui produira le fat jar dans le répertoire build/libs
. Une analyse rapide du fat jar produit nous révèle que les jars dépendance se trouvent dans le répertoire lib/
de l'archive.
Une fois l'archive créée, le fat jar peut être exécuté à l'aide d'une ligne de commande comme n'importe quel jar exécutable, en utilisant la commande $JAVA_HOME/bin/java -jar path/to/myproject.jar
. Au démarrage, les logs de l'application apparaîtront dans la console.
Pour les applications qui doivent pouvoir être déployées dans un conteneur de servlets traditionnel, Boot propose un moyen pour initialiser programmatiquement la configuration web. Pour permettre ceci, Boot offre un WebApplicationInitializer
qui référence l'application dans le conteneur de servlets à l'aide de l'API Servlet 3.0 qui permet d'ajouter des servlets programmatiquement au ServletContext. En dérivant la classe SpringBootServletInitializer
, les applications Boot peuvent ajouter leur configuration au contexte embarqué qui est créé à l'initialisation du conteneur, comme le montre l'exemple ci-dessous.
@RestController
@EnableAutoConfiguration
class Application extends SpringBootServletInitializer {
@RequestMapping(method = RequestMethod.GET)
String get() {
"home"
}
static void main(String[] args) {
SpringApplication.run this, args
}
@Override
SpringApplicationBuilder configure(SpringApplicationBuilder application) {
application.sources Application
}
}
La méthode configure
qui est surchargée dans la classe Application
est utilisée pour ajouter le code de l'application dans le contexte Spring embarqué. Dans un scénario plus réaliste, cette méthode serait utilisée pour ajouter une classe de configuration Java Spring, qui définirait l'ensemble des beans pour tous les contrôleurs et les services de l'application.
Pour packager une application destinée à être déployée dans un conteneur de servlets, le projet doit être construit sous la forme d'une archive war. Pour ajouter ce comportement au build Maven, le plugin Boot doit être supprimé, et la propriété packaging doit être fixée à "war".
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.0.RC1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
</project>
En exécutant la commande mvn install
pour ce projet, on obtient la création d'un fichier myproject-1.0.0-SNAPSHOT.war
dans le répertoire target
. Les projets qui sont construits à l'aide de Gradle peuvent s'appuyer sur le Gradle War Plugin, qui propose une tâche war
qui permet de construire un fichier war. De la même manière que pour la configuration Maven, les projets Boot Gradle doivent supprimer le plugin Boot.
apply plugin: 'java'
apply plugin: 'war'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
}
ext {
springBootVersion = '1.0.0.BUILD-SNAPSHOT'
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
}
En lançant la tâche war
sur ce build, on obtient un fichier war dans le répertoire build/libs
.
Que ce soit dans la configuration Maven ou dans la configuration Gradle, une fois que le fichier war a été créé, il peut être déployé dans n'importe quel conteneur de servlets compatible Servlet 3.0. Tomcat 7+, Jetty 8, Glassfish 3.X, JBoss AS 6.x/7.x et Websphere 8 font partie des conteneurs compatibles.
Pour aller plus loin
L'équipe Spring Boot a produit une collection complète de guides et d'exemples pour illustrer les possibilités du framework. Des articles de blogs, une documentation de référence et la documentation de l'API peuvent être trouvés sur le site Spring IO. Des projets exemples sont disponibles sur la page GitHub du projet, et une documentation détaillée peut être trouvée dans le manuel de référence Spring Boot. La chaîne Youtube SpringSourceDev propose un webinar sur Spring Boot qui souligne les objectifs du projet et ses fonctionnalités majeures. Pendant le Groovy & Grails Exchange de l'an dernier à Londres, David Dawson a fait une démonstration de développement de micro-services avec Spring Boot.
A propos de l'Auteur
Daniel Woods est Ingénieur en développement senior chez Netflix, où il développe des outils de livraison continue et de déploiement dans le cloud. Il est spécialisé dans les technologies basées sur la JVM et est très actif dans les communautés Groovy, Grails et Spring. Daniel peut être contacté via mail à danielpwoods@gmail.com ou via Twitter : @danveloper.