Guide de JavaLite - Création d'une application CRUD RESTful

1. Introduction

JavaLite est un ensemble de frameworks pour simplifier les tâches courantes que chaque développeur doit gérer lors de la création d'applications.

Dans ce didacticiel, nous allons examiner les fonctionnalités de JavaLite axées sur la création d'une API simple.

2. Configuration

Tout au long de ce didacticiel, nous allons créer une application RESTful CRUD simple. Pour ce faire, nous utiliserons ActiveWeb et ActiveJDBC - deux des frameworks avec lesquels JavaLite s'intègre.

Alors, commençons et ajoutons la première dépendance dont nous avons besoin:

 org.javalite activeweb 1.15 

L'artefact ActiveWeb inclut ActiveJDBC, il n'est donc pas nécessaire de l'ajouter séparément. Veuillez noter que la dernière version activeweb se trouve dans Maven Central.

La deuxième dépendance dont nous avons besoin est un connecteur de base de données . Pour cet exemple, nous allons utiliser MySQL, nous devons donc ajouter:

 mysql mysql-connector-java 5.1.45 

Encore une fois, la dernière dépendance mysql-connector-java peut être trouvée sur Maven Central.

La dernière dépendance que nous devons ajouter est quelque chose de spécifique à JavaLite:

 org.javalite activejdbc-instrumentation 1.4.13   process-classes  instrument    

Le dernier plugin activejdbc-instrumentation peut également être trouvé dans Maven Central.

Après avoir mis tout cela en place et avant de commencer avec des entités, des tables et des mappages, nous nous assurerons que l' une des bases de données prises en charge est opérationnelle . Comme nous l'avons déjà dit, nous utiliserons MySQL.

Nous sommes maintenant prêts à commencer avec le mappage objet-relationnel.

3. Mappage objet-relationnel

3.1. Cartographie et instrumentation

Commençons par créer une classe Product qui sera notre entité principale :

public class Product {}

Et créons également le tableau correspondant :

CREATE TABLE PRODUCTS ( id int(11) DEFAULT NULL auto_increment PRIMARY KEY, name VARCHAR(128) );

Enfin, nous pouvons modifier notre classe Product pour faire le mappage :

public class Product extends Model {}

Il suffit d'étendre la classe org.javalite.activejdbc.Model . ActiveJDBC déduit les paramètres de schéma de base de données à partir de la base de données . Grâce à cette capacité, il n'est pas nécessaire d'ajouter des getters et des setters ou aucune annotation .

De plus, ActiveJDBC reconnaît automatiquement que la classe Product doit être mappée à la table PRODUCTS . Il utilise des inflexions anglaises pour convertir une forme singulière d'un modèle en une forme plurielle d'un tableau. Et oui, cela fonctionne également avec des exceptions.

Il y a une dernière chose dont nous aurons besoin pour faire fonctionner notre cartographie: l'instrumentation. L'instrumentation est une étape supplémentaire requise par ActiveJDBC qui nous permettra de jouer avec notre classe Product comme si elle avait des getters, des setters et des méthodes de type DAO.

Après avoir exécuté l'instrumentation, nous pourrons faire des choses comme:

Product p = new Product(); p.set("name","Bread"); p.saveIt();

ou:

List products = Product.findAll();

C'est là qu'intervient le plugin activejdbc-instrumentation . Comme nous avons déjà la dépendance dans notre pom, nous devrions voir des classes instrumentées pendant la construction:

... [INFO] --- activejdbc-instrumentation:1.4.11:instrument (default) @ javalite --- **************************** START INSTRUMENTATION **************************** Directory: ...\tutorials\java-lite\target\classes Instrumented class: .../tutorials/java-lite/target/classes/app/models/Product.class **************************** END INSTRUMENTATION **************************** ...

Ensuite, nous allons créer un test simple pour nous assurer que cela fonctionne.

3.2. Essai

Enfin, pour tester notre cartographie, nous allons suivre trois étapes simples: ouvrir une connexion à la base de données, enregistrer un nouveau produit et le récupérer:

@Test public void givenSavedProduct_WhenFindFirst_ThenSavedProductIsReturned() { Base.open( "com.mysql.jdbc.Driver", "jdbc:mysql://localhost/dbname", "user", "password"); Product toSaveProduct = new Product(); toSaveProduct.set("name", "Bread"); toSaveProduct.saveIt(); Product savedProduct = Product.findFirst("name = ?", "Bread"); assertEquals( toSaveProduct.get("name"), savedProduct.get("name")); }

Notez que tout cela (et plus) est possible en n'ayant qu'un modèle et une instrumentation vides.

4. Contrôleurs

Maintenant que notre cartographie est prête, nous pouvons commencer à réfléchir à notre application et à ses méthodes CRUD.

Pour cela, nous allons utiliser des contrôleurs qui traitent les requêtes HTTP.

Créons notre ProductsController :

@RESTful public class ProductsController extends AppController { public void index() { // ... } }

Avec cette implémentation, ActiveWeb mappera automatiquement la méthode index () à l'URI suivant:

//:/products

Les contrôleurs annotés avec @RESTful , fournissent un ensemble fixe de méthodes automatiquement mappées à différents URI. Voyons ceux qui seront utiles pour notre exemple CRUD:

Méthode du contrôleur Méthode HTTP URI
CRÉER créer() PUBLIER // hôte: port / produits
LIRE UN spectacle() AVOIR // hôte: port / produits / {id}
LIS TOUT indice() AVOIR // hôte: port / produits
METTRE À JOUR mettre à jour() METTRE // hôte: port / produits / {id}
EFFACER détruire() EFFACER // hôte: port / produits / {id}

Et si nous ajoutons cet ensemble de méthodes à notre ProductsController :

@RESTful public class ProductsController extends AppController { public void index() { // code to get all products } public void create() { // code to create a new product } public void update() { // code to update an existing product } public void show() { // code to find one product } public void destroy() { // code to remove an existing product } }

Avant de passer à notre implémentation logique, nous allons jeter un coup d'œil sur quelques éléments que nous devons configurer.

5. Configuration

ActiveWeb is based mostly on conventions, project structure is an example of that. ActiveWeb projects need to follow a predefined package layout:

src |----main |----java.app | |----config | |----controllers | |----models |----resources |----webapp |----WEB-INF |----views

There's one specific package that we need to take a look at – app.config.

Inside that package we're going to create three classes:

public class DbConfig extends AbstractDBConfig { @Override public void init(AppContext appContext) { this.configFile("/database.properties"); } }

This class configures database connections using a properties file in the project's root directory containing the required parameters:

development.driver=com.mysql.jdbc.Driver development.username=user development.password=password development.url=jdbc:mysql://localhost/dbname

This will create the connection automatically replacing what we did in the first line of our mapping test.

The second class that we need to include inside app.config package is:

public class AppControllerConfig extends AbstractControllerConfig { @Override public void init(AppContext appContext) { add(new DBConnectionFilter()).to(ProductsController.class); } }

This codewill bind the connection that we just configured to our controller.

The third class willconfigure our app's context:

public class AppBootstrap extends Bootstrap { public void init(AppContext context) {} }

After creating the three classes, the last thing regarding configuration is creating our web.xml file under webapp/WEB-INF directory:

   dispatcher org.javalite.activeweb.RequestDispatcher  exclusions css,images,js,ico   encoding UTF-8    dispatcher /*  

Now that configuration is done, we can go ahead and add our logic.

6. Implementing CRUD Logic

With the DAO-like capabilities provided by our Product class, it's super simple to add basic CRUD functionality:

@RESTful public class ProductsController extends AppController { private ObjectMapper mapper = new ObjectMapper(); public void index() { List products = Product.findAll(); // ... } public void create() { Map payload = mapper.readValue(getRequestString(), Map.class); Product p = new Product(); p.fromMap(payload); p.saveIt(); // ... } public void update() { Map payload = mapper.readValue(getRequestString(), Map.class); String id = getId(); Product p = Product.findById(id); p.fromMap(payload); p.saveIt(); // ... } public void show() { String id = getId(); Product p = Product.findById(id); // ... } public void destroy() { String id = getId(); Product p = Product.findById(id); p.delete(); // ... } }

Easy, right? However, this isn't returning anything yet. In order to do that, we have to create some views.

7. Views

ActiveWeb uses FreeMarker as a templating engine, and all its templates should be located under src/main/webapp/WEB-INF/views.

Inside that directory, we will place our views in a folder called products (same as our controller). Let's create our first template called _product.ftl:

{ "id" : ${product.id}, "name" : "${product.name}" }

It's pretty clear at this point that this is a JSON response. Of course, this will only work for one product, so let's go ahead and create another template called index.ftl:

[]

This will basically render a collection named products, with each one formatted by _product.ftl.

Finally, we need to bind the result from our controller to the corresponding view:

@RESTful public class ProductsController extends AppController { public void index() { List products = Product.findAll(); view("products", products); render(); } public void show() { String id = getId(); Product p = Product.findById(id); view("product", p); render("_product"); } }

In the first case, we're assigning products list to our template collection named also products.

Then, as we're not specifying any view, index.ftl will be used.

In the second method, we're assigning product p to element product in the view and we're explicitly saying which view to render.

We could also create a view message.ftl:

{ "message" : "${message}", "code" : ${code} }

And then call it form any of our ProductsController‘s method:

view("message", "There was an error.", "code", 200); render("message");

Let's now see our final ProductsController:

@RESTful public class ProductsController extends AppController { private ObjectMapper mapper = new ObjectMapper(); public void index() { view("products", Product.findAll()); render().contentType("application/json"); } public void create() { Map payload = mapper.readValue(getRequestString(), Map.class); Product p = new Product(); p.fromMap(payload); p.saveIt(); view("message", "Successfully saved product id " + p.get("id"), "code", 200); render("message"); } public void update() { Map payload = mapper.readValue(getRequestString(), Map.class); String id = getId(); Product p = Product.findById(id); if (p == null) { view("message", "Product id " + id + " not found.", "code", 200); render("message"); return; } p.fromMap(payload); p.saveIt(); view("message", "Successfully updated product id " + id, "code", 200); render("message"); } public void show() { String id = getId(); Product p = Product.findById(id); if (p == null) { view("message", "Product id " + id + " not found.", "code", 200); render("message"); return; } view("product", p); render("_product"); } public void destroy() { String id = getId(); Product p = Product.findById(id); if (p == null) { view("message", "Product id " + id + " not found.", "code", 200); render("message"); return; } p.delete(); view("message", "Successfully deleted product id " + id, "code", 200); render("message"); } @Override protected String getContentType() { return "application/json"; } @Override protected String getLayout() { return null; } }

At this point, our application is done and we're ready to run it.

8. Running the Application

We'll use Jetty plugin:

 org.eclipse.jetty jetty-maven-plugin 9.4.8.v20171121 

Find latest jetty-maven-plugin in Maven Central.

And we're ready, we can run our application:

mvn jetty:run

Let's create a couple of products:

$ curl -X POST //localhost:8080/products -H 'content-type: application/json' -d '{"name":"Water"}' { "message" : "Successfully saved product id 1", "code" : 200 }
$ curl -X POST //localhost:8080/products -H 'content-type: application/json' -d '{"name":"Bread"}' { "message" : "Successfully saved product id 2", "code" : 200 }

.. read them:

$ curl -X GET //localhost:8080/products [ { "id" : 1, "name" : "Water" }, { "id" : 2, "name" : "Bread" } ]

.. mettre à jour l'un d'entre eux:

$ curl -X PUT //localhost:8080/products/1 -H 'content-type: application/json' -d '{"name":"Juice"}' { "message" : "Successfully updated product id 1", "code" : 200 }

… Lisez celui que nous venons de mettre à jour:

$ curl -X GET //localhost:8080/products/1 { "id" : 1, "name" : "Juice" }

Enfin, nous pouvons en supprimer un:

$ curl -X DELETE //localhost:8080/products/2 { "message" : "Successfully deleted product id 2", "code" : 200 }

9. Conclusion

JavaLite dispose de nombreux outils pour aider les développeurs à mettre en place une application en quelques minutes . Cependant, si le fait de baser les choses sur des conventions aboutit à un code plus propre et plus simple, il faut un certain temps pour comprendre la dénomination et l'emplacement des classes, des packages et des fichiers.

Ce n'était qu'une introduction à ActiveWeb et ActiveJDBC, trouvez plus de documentation sur leur site Web et recherchez notre application de produits dans le projet Github.