I. Introduction▲
Derrière ce titre, a priori sans aucun sens, se cache une présentation de mon dernier coup de cœur : un framework web full stack pour Java.
Là je vous entends dire : tiens, tiens… il veut nous parler de Play! celui-là ?
Hé bien non, je veux vous parler de … Ninja web framework.
Projet open source initié en 2012, ce framework prendrait son origine chez des utilisateurs de Play 1 qui auraient trouvé Play 2 trop « Scala ». En résumé, cela donne un framework s'inspirant de Rails et Play mais avec une stack 100 % Java.
Il est depuis peu en version 3.0.0
https://twitter.com/ninjaframework | Ninja Framework Team @ninjaframework |
Ninja 3.0.0-rc1 released! Give it a try! | |
12:10 PM - 31 Janv 2014 |
Leur site web en a profité pour se faire une beauté, et compléter sa documentation.
II. Composition▲
Tout d'abord, votre projet Ninja est un projet Maven de facto, vous ne devriez donc pas être perdus.
La création du projet se fait effectivement via un archétype Maven et si on regarde la liste des fonctionnalités, on retrouve des noms bien connus du monde Java :
- HTML templating/rendering avec freemarker ;
- migration de base de données avec FlyWay ;
- injection de dépendances avec Guice ;
- validation avec la JSR 303 (Hibernate-validation) ;
- logs avec logback et slf4j ;
- sérialisation/désérialisation (JSON, XML) avec Jackson ;
- Mockito et FluentLenium.
On profite de surcroît de tous les avantages de Maven (gestion de dépendances, build, etc.).
À savoir qu'il ne s'agit pas seulement d'un archétype, mais bien d'une stack complète.
En effet, la couche Ninja apporte elle-même de nombreuses fonctionnalités sympathiques (i18n, scheduler, filtres) mais surtout de quoi tester dans tous les sens :
- NinjaRouterTest, pour tester ses routes ;
- NinjaTest, pour tester sur une application déployée ;
- NinjaDocTester, pour tester et documenter vos API ;
- pour le reste, vous avez Mockito à disposition.
III. Modules▲
À l'instar de Play, on peut agrémenter sa stack Ninja de modules. Pour l'instant la liste est courte, ce qui s'explique par la jeunesse du framework.
IV. Plus concrètement▲
Différents archétypes sont à votre disposition :
- ninja-core-demo-archetype, un showroom du framework : beaucoup de nettoyage à faire si vous l'utilisez comme base ;
- ninja-jpa-demo-archetype, un projet d'exemple plus simple, comprenant juste un écran avec lien vers base de données (dao, migration) : c'est celui que j'utilise pour créer mes projets ;
- ninja-appengine-blog-archetype, un moteur de blog basique fonctionnant sur app-engine.
Il existe d'autres archétypes (plusieurs variantes à chaque fois : blog, demo, core-demo) mais tous ne sont pas à jour avec la dernière version de Ninja, il faudra donc quelquefois se référer à la page de mise à jour de version.
Nous allons intégrer un projet Ninja dans un projet Maven existant composé d'un pom parent et d'un module contenant les dtos (potentiellement partagé avec une application cliente : android ou autre). Le modèle est composé d'une simple classe TaskDto (une tâche, afin de gérer une todolist).
Allons dans le root de ce projet Maven de type pom (packaging), et exécutons la commande suivante :
mvn archetype:generate -DarchetypeGroupId
=
org.ninjaframework -DarchetypeArtifactId
=
ninja-servlet-jpa-blog-archetype -DarchetypeVersion
=
2
.5
.1
Note : la version 3 des archétypes n'est pas encore sortie, mais la montée de version se fait aisément dans le pom.
La création étant interactive, nous renseignons les informations suivantes :
groupId : fr.arolla.ninja
artifactId : ninja-todo-backend
version : 1.0-SNAPSHOT
package : fr.arolla.ninja
Nous appelons ce sous-projet ninja-todo-backend, et vérifions qu'il est bien ajouté à la liste des modules du projet parent.
Un simple mvn clean install permet de vérifier que le sous-projet Ninja a été correctement intégré au projet parent.
Nous pouvons aussi vérifier que le projet Ninja se lance :
cd ninja-todo-backend mvn ninja:run
L'application est alors accessible à l'adresse http://localhost:8080/
Ouvrons le module ninja-todo-backend et découvrons différents répertoires :
/src/main/java:
- folder assets : répertoire contenant les ressources statiques telles que les images, les fichiers CSS, etc. ;
- folder conf : les fichiers de propriétés, l'internationalisation, les routes, etc. ;
- folder controllers : c'est le code Java sur lequel est mappé vos routes définies dans le fichier conf/Routes.java ;
- folder db/migration : les fichiers de migration SQL, un pour chaque version ;
- folder models : rien de bien original, ce sont les classes du modèle ;
- folder views : les vues au format freemarker, un sous-répertoire par Controller comprenant un template par fonction.
V. Une approche MVC▲
Commençons par ajouter notre module de modèle au projet ninja-todo-backend (dans le fichier pom.xml), puis ajoutons un écran affichant une liste de tâches à accomplir.
Commençons par ajouter notre table dans le fichier db/migration/V1__.sql, qui est le script initial joué par flyway.
create
table
Tasks (
id bigint
generated
by
default
as
identity
,
title varchar
(
255
)
,
description varchar
(
255
)
,
doneAt timestamp
,
dueDate timestamp
,
primary
key
(
id)
)
;
Attelons-nous ensuite à la partie C (le controller) en créant un simple controller avec une méthode retrieveAllTasks(), dans le répertoire controler :
@Singleton
public
class
TaskListController {
@Inject
Provider<
EntityManager>
entitiyManagerProvider;
@Inject
private
static
final
Logger LOGGER=
LoggerFactory.getLogger
(
TaskListController.class
);
private
Function<
Task,TaskDto>
TO_DTO =
new
Function<
Task, TaskDto>(
) {
@Override
public
TaskDto apply
(
Task task) {
TaskDto dto =
new
TaskDto
(
task.getDueDate
(
), task.getDescription
(
), task.getTitle
(
));
if
(
task.getDoneAt
(
) !=
null
) {
dto.done
(
);
}
return
dto;
}
}
;
@Transactional
public
Result retrieveAllTasks
(
) {
List<
TaskDto>
tasks=
retrieveTasks
(
);
return
Results
.html
(
)
.render
(
"tasks"
, tasks);
}
private
List<
TaskDto>
retrieveTasks
(
) {
EntityManager entityManager =
entitiyManagerProvider.get
(
);
TypedQuery<
Task>
query=
entityManager.createQuery
(
"SELECT x FROM Task "
, Task.class
);
List<
Task>
tasks =
query.getResultList
(
);
return
Lists.transform
(
tasks, TO_DTO);
}
}
Il nous manque à router ce controller, en mappant une route sur cette fonction dans le fichier conf/Routes.java :
router.GET
(
).route
(
"/tasks"
).with
(
TaskListController.class
, "retrieveAllTasks"
);
Le controller fait appel à la fonction 'render' pour générer le rendu au format html, qui prend en paramètre une liste nommée tags. Nous allons donc devoir créer la vue correspondante dans le répertoire views/{NomController}/{nomFonction}.ftl.html (un template freemarker), ce qui dans notre cas donnera le fichier views/TaskListController/retrieveAllTasks.ftl.html :
<#import
"../layout/defaultLayout.ftl.html"
as layout>
<@layout.myLayout
"Home page"
>
<h1>
Waiting tasks</h1>
<#if !tasks?has_content>
<p>
No Tasks entries yet</p>
<#else>
<h1>
Tasks</h1>
<#list tasks as task>
<div
class
=
"jumbotron"
>
<h2>
${task.title}</h2>
<p>
${task.description}</p>
</div>
</#list>
</#if>
</@layout.myLayout>
Accédez à votre page dans votre navigateur (http://localhost:8080/tasks). Il n'y a aucune tâche pour l'instant.
Ninja framework propose un tips assez utile. En effet, nous trouvons dans le package conf une classe StartupActions qui contient la fonction suivante :
@Start
(
order=
100
) //ordre de priorité basse
public
void
generateDummyDataWhenInTest
(
) {
if
(!
ninjaProperties.isProd
(
)) {
setupDao.setup
(
);
}
}
Nous allons donc pouvoir remplir avec quelques tâches notre base au démarrage (et uniquement en phase de développement) en rajoutant des données dans la classe SetupDao :
entityManager.persist
(
new
Task
(
"Article Blog Ninja"
, "faire un court article pour parler du framework ninja"
;));
entityManager.persist
(
new
Task
(
"Jam de code"
,"s'inscrire à la prochaine Jam de code Arolla"
));
Relancez le serveur, un liste de tâches apparaît maintenant.
VI. Exposez facilement des API REST▲
Effectivement, exposer les mêmes données en JSON est simple, il vous suffit d'ajouter une route :
router.GET
(
).route
(
"/tasks/json"
).with
(
TaskListController.class
, "retrieveAllTasksJson"
);
Ainsi qu'une fonction dans le controller retournant du JSON:
@Transactional
public
Result retrieveAllTasksJson
(
) {
List<
TaskDto>
tasks=
retrieveTasks
(
);
return
Results
.json
(
)
.render
(
tasks);
}
Et nous pouvons retrouver nos tâches au format json.
La documentation est très bien faite, il est donc facile de monter en compétence sur les différentes facettes du framework. Je n'en montrerai donc pas plus, car il serait assez difficile de faire mieux que le site officiel.
VII. Conclusion▲
Le framework Ninja est un bon framework, bien pensé avec lequel on est vraiment productif.
L'objectif est donc bien atteint.
Son intégration directement dans Maven est, je pense, un gros atout.
Il est juste dommage qu'il ne soit pas arrivé quelques années plus tôt, il aurait sûrement fait une entrée plus fracassante.
Dans un contexte où Play est vraiment implanté sur ce créneau, et à l'heure où certaines personnes se détournent de Java et de Maven, il répond cependant à un certain besoin et, à mon avis, il trouvera quand même son public.
Les sources sont disponibles ici.
VIII. Remerciements▲
Cet article a été publié avec l'aimable autorisation de la société Arolla. L'article original (Arrête de jouer, deviens un ninja !) peut être vu sur le blog/site de Arolla.
Nous tenons à remercier Fabien pour sa relecture orthographique attentive de cet article et Régis Pouiller pour la mise au gabarit.