Formation BACKEND TRINITY

Rappel

Backend

Frontend

+

+

Trinity

Resource oriented architecture (ROA) & rest

REST est à ROA ce que SOAP est à SOA

Les concepts sont issus de la thèse de doctorat en philosophie de M. Roy T. Fielding en 2000...

C'était aussi mon sujet d'examen probatoire au Cnam...

a retenir

Tout est ressource !

 

principes fondamentaux :

 

- l'identification des ressources : URI, URL, URN

- la manipulation des ressources au travers de leur représentation, en particulier via le protocole HTTP

- la description des ressources : MIME

...

FOCUS HTTP

FOCUS HTTP

@RequestMapping(value="/customer/{id}" ,headers = "Accept=application/json","application/xml")
public ResponseEntity<Customer> getCustomerById(@PathVariable String id) {
    Customer customer;
    
    try {
    
        customer = customerService.getCustomerDetail(id);
    
    } catch (CustomerNotFoundException e) {
    
        return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
    
    }
    
    return new ResponseEntity<Customer>(customer,HttpStatus.OK);

}

Exemple :

RECOMMANDATIONS

dans confluence...

Il y a aussi les fiches pratiques...

BACKEND

BACKEND

BACKEND

Ce qui nous intéresse plus particulièrement :

Spring Boot

Support de containers embarqués pour la production rapide d'applications auto-exécutables.

Spring CLOUD

Spring WEB

Support REST pour la création d'APIs RESTful

BACKEND

Ce qui nous intéresse plus particulièrement (suite et fin) :

Spring DATA

Data Relational Access

Let's GET STARTED !

premiere Application

  1. Ouvrir eclipse
  2. [File] --> [New] --> [Spring Starter Project]

premiere Application

3. Renseigner les caractéristiques du projet

premiere Application

4. Choisir les fonctionnalités Spring :

5. Clic sur [Finish]

4a. développer [Web]

4b. cocher [Web]

premiere Application

6. Lancer l'application :

 

        6.a Clic droit sur le projet, [Run As] --> [Spring Boot App]

 

        ou

 

        6.b Lancement depuis un terminal, à la racine du projet :

mvn spring-boot:run

        ou

 

        6.c Packaging et utilisation de la commande java :

mvn clean verify
java -jar ./target/{$artifactId}-{$version}.jar 

premiere Application

Mais elle ne fait rien !?

premier CONTROLLER

1. Créer un package

2. Créer une classe HelloController

/**
 * 
 */
package nc.gouv.dtsi.etudes.axi.formation.hello;

import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author didier
 *
 */
@RestController
public class HelloController {

	private static final String hello = "Hello World !";

	@RequestMapping(value = "/api/v1/hello", 
                        method = RequestMethod.GET, 
                        produces = MediaType.APPLICATION_JSON_VALUE)
	public HttpEntity<String> sayHello() {

		return new HttpEntity<String>(hello);

	}

}

premier CONTROLLER

@RestController

Depuis Spring 4

Equivaut dans les version précédentes à la combinaison des annotations :

- @Controller

- @ResponseBody

 

C'est une version spécialisée du Controller et elle doit être utilisée pour la création d'APIs RESTful.

 

Cf. documentation officielle.

premier CONTROLLER

@RequestMapping

Permet de mapper les requêtes HTTP vers les méthodes concernées des controllers.

 

value

déclare le chemin à utiliser dans l'URL pour appeler la méthode concernée.

Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, toutes les méthodes de la classe hériteront de ce chemin.

 

premier CONTROLLER

@RequestMapping

 

method

Restreint la méthode HTTP :

GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE

 

Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, toutes les méthodes de la classe hériteront de cette restriction.

premier CONTROLLER

@RequestMapping

produces

Définit le(s) type(s) de media que peut produire la requête. Peut contenir des valeurs multiples :

(...) 
     produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
(...)

L'expression peut être négative :

(...) 
     produces = !MediaType.APPLICATION_XML_VALUE 
(...)

Dans ce cas, toutes les requêtes acceptant autre chose que du XML seront acceptées.

Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, la déclaration au niveau de la méthode surchargera la déclaration au niveau de la classe.

premier CONTROLLER

- Lancer l'application

- Tester :

        - via CURL :

curl -i http://localhost:8080/api/v1/hello

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 13
Date: Sun, 12 Feb 2017 21:46:21 GMT

Hello World !

        - via Postman :

premier CONTROLLER

@RequestMapping avec variable(s)

 

Modifier la classe HelloController comme suit :

(...)
@RestController
@RequestMapping("/api/v1")
public class HelloController {

	private static final String helloToSomeone = "Hello ";
	private static final String helloWorld = helloToSomeone + "World !";

	@RequestMapping(value = "/hello", 
                        method = RequestMethod.GET, 
                        produces = MediaType.APPLICATION_JSON_VALUE)
	(...)

	@RequestMapping(value = "/hello/{someone}", 
                        method = RequestMethod.GET, 
                        produces = MediaType.APPLICATION_JSON_VALUE)
	public HttpEntity<String> sayHelloToSomeone(@PathVariable("someone") final String someone) {

		return new HttpEntity<String>(helloToSomeone + someone + " !");

	}

}

premier CONTROLLER

@RequestMapping avec variable(s)

 

- Relancer l'application

- Tester :

        - via CURL :

curl -i http://localhost:8080/api/v1/hello/Donald

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 14
Date: Sun, 12 Feb 2017 22:51:50 GMT

Hello Donald !

        -  ou via Postman...

premier CONTROLLER

@RequestMapping avec variable(s)

 

Si le nom de l'argument de la méthode correspond exactement au nom de la @PathVariable, il est possible de simplifier comme suit :

(...)
@RestController
@RequestMapping("/api/v1")
public class HelloController {
	(...)

	@RequestMapping(value = "/hello/{someone}", 
                        method = RequestMethod.GET, 
                        produces = MediaType.APPLICATION_JSON_VALUE)
	public HttpEntity<String> sayHelloToSomeone(@PathVariable final String someone) {

		return new HttpEntity<String>(helloToSomeone + someone + " !");

	}

}

premier CONTROLLER

@RequestMapping avec variable(s)

 

Il est possible d'avoir plusieurs @PathVariable

 

Il est possible d'utiliser les expressions régulières pour contrôler le format de la @PathVariable

premier CONTROLLER

@RequestMapping avec variable(s)

 

Autre méthode avec @RequestParam

 

Modifier la méthode sayHelloToSomeone comme suit :

@RequestMapping(value = "/hello", 
                method = RequestMethod.GET, 
                produces = MediaType.APPLICATION_JSON_VALUE)
	public HttpEntity<String> sayHelloToSomeone(@RequestParam("someone") final String someone) {

		return new HttpEntity<String>(helloToSomeone + someone + " !");

	}

Relancer l'application...

premier CONTROLLER

@RequestMapping avec variable(s)

 

premier CONTROLLER

@RequestMapping avec variable(s)

 

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'requestMappingHandlerMapping' 
defined in class path resource 
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: 
Invocation of init method failed; 
nested exception is java.lang.IllegalStateException: 
Ambiguous mapping. Cannot map 'helloController' method 

premier CONTROLLER

@RequestMapping avec variable(s)

 

Premier exercice : corriger la classe HelloController pour continuer à avoir les deux services opérationnels :

curl -i http://localhost:8080/api/v1/hello

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 13
Date: Mon, 13 Feb 2017 00:05:00 GMT

Hello World !

curl -i http://localhost:8080/api/v1/hello?someone=Donald
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 14
Date: Mon, 13 Feb 2017 00:07:33 GMT

Hello Donald !

premier CONTROLLER

@RequestMapping avec variable(s)

 

Corrigé :

package nc.gouv.dtsi.etudes.axi.formation.hello;

import (...)

@RestController
@RequestMapping("/api/v1")
public class HelloController {

	private static final String helloToSomeone = "Hello ";

	@RequestMapping(value = "/hello", 
                        method = RequestMethod.GET, 
                        produces = MediaType.APPLICATION_JSON_VALUE)
	public HttpEntity<String> sayHelloToSomeone(
			@RequestParam(name = "someone", 
                                      required = false, 
                                      defaultValue = "World") 
                        final String someone) {

		return new HttpEntity<String>(helloToSomeone + someone + " !");

	}

}

PLAY WITH DATA

Spring DATA JPA

Documentation de référence ici

Documentation officielle JPA ici

Utilisation d'une base de données embarquées basée sur le code officiel géographique de l'INSEE.

Spring DATA JPA

1. Dans src/main/resources, supprimer le fichier application.properties et créer un fichier application.yml​ 

spring:
  application:
    name: Formation Trinity Backend
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
    database-platform: org.hibernate.dialect.HSQLDialect
  datasource:
    url: jdbc:hsqldb:mem:testdb
    driverClassName: org.hsqldb.jdbcDriver
    username: sa
    password:
    platform: hsqldb
  data:
    rest:
      base-uri: /api
flyway:
  enabled: true

Spring DATA JPA

2. Modification du fichier pom.xml du projet, par ajout des dépendances nécessaires :

                (...)
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.flywaydb</groupId>
			<artifactId>flyway-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>
                (...)

Spring DATA JPA

3. Créer un nouveau package

 

4a. Création de la classe entité (mappe un objet de la couche de persistence, i.e. en général une table de la base de données)

4b. Création du repository

4c. Création du controller

Spring DATA JPA

Exemple (table T_REGION) :

 

Entité JPA :

package nc.gouv.dtsi.etudes.axi.formation.cog.region;

import ...;

@Entity
@Table(name = "T_REGION")
public class Region {

	@Id
	@Column(name = "REGION")
	private String codeRegion;
	
	@Column(name = "CHEFLIEU")
	private String chefLieu;
	
	@Column(name = "TNCC")
	private String typeDeNomEnClair;


	@Column(name = "NCC")
	private String nomEnClair;

	@Column(name = "NCCENR")
	private String nomTypoEnrichie;

	/*
         Accesseurs...
        */

}

Spring DATA JPA

Exemple (table T_REGION) :

 

Entité JPA :

 

Ne pas oublier les accesseurs, les constructeurs, etc.

Spring DATA JPA

Exemple (table T_REGION) :

 

Repository :

package nc.gouv.dtsi.etudes.axi.formation.cog.region;

import org.springframework.data.jpa.repository.JpaRepository;

public interface RegionRepository extends JpaRepository<Region, String> {

}

Spring DATA JPA

Exemple (table T_REGION) :

 

Controller :

package nc.gouv.dtsi.etudes.axi.formation.cog.region;

import ...;

@RestController
@RequestMapping(path = "/api/v1", 
                method = RequestMethod.GET, 
                produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@CrossOrigin(origins = "*")
public class RegionController {

	@Autowired
	private RegionRepository lRegionRepository;

	@RequestMapping(path = "/regions")
	public List<Region> getAll() {
		return lRegionRepository.findAll();
	}

}

Spring DATA JPA

- Lancer l'application

- Tester :

        - via CURL 

        - via Postman

        - via le navigateur...

Spring DATA JPA

Spring DATA JPA

Et la magie du framework Spring...

 

Par exemple pour rechercher par la valeur d'un des attributs de notre entité, par exemple, l'attribut :

        /**
	 * NCCENR - Nom en clair (typographie riche) Libellé en typographie riche,
	 * majuscules, minuscules, accentuation.
	 */
	@Column(name = "NCCENR")
	private String nomTypoEnrichie;

Spring DATA JPA

1. Modifier l'interface Repository en lui ajoutant :

public interface RegionRepository extends JpaRepository<Region, String> {

	Region findByNomTypoEnrichie(final String pCriteria);

}

2. Enrichir le controller de la méthode :

@RequestMapping(path = "/regions/{criteria}")
public ResponseEntity<Region> getByCriteria(
	@PathVariable(name = ("criteria"), required = true) final String pCriteria) {
	/*
	 * Default pessimistic response
	 */
	ResponseEntity<Region> response = new ResponseEntity<>(null,
		HttpStatus.NOT_FOUND);

	Region result = lRegionRepository
		.findByNomTypoEnrichie(pCriteria);

	if (result != null) {
		response = new ResponseEntity<>(result, HttpStatus.OK);
	}

	return response;
}

Spring DATA JPA

- Relancer l'application

- Tester :

        - via CURL 

        - via Postman

        - via le navigateur...

Spring DATA JPA

Pour aller encore plus loin, nous allons combiner les critères...

1. Remodifier l'interface Repository :

public interface RegionRepository extends JpaRepository<Region, String> {

	Region findByCodeRegionOrNomTypoEnrichie(final String pCodeRegion,
			final String pNomTypoEnrichie);

}

2. Modifier le  controller :

@RequestMapping(path = "/regions/{criteria}")
public ResponseEntity<Region> getByCriteria(
	@PathVariable(name = ("criteria"), required = true) final String pCriteria) {
	
        (...)

        Region result = lRegionRepository
		.findByCodeRegionOrNomTypoEnrichie(pCriteria, pCriteria);

	(...)
}

Spring DATA JPA

Attention à la subtilité :

findByCodeRegionOrNomTypoEnrichie(pCriteria, pCriteria)

La valeur du critère de recherche est passée en double pour que la recherche soit faite sur les deux attributs concernés...

 

Spring DATA JPA

- Relancer l'application

- Tester :

        - via CURL 

        - via Postman

        - via le navigateur...

Spring DATA JPA

A VOUS DE JOUER !

Spring DATA REST

Documentation de référence ici

1. Dans src/main/resources, supprimer le fichier application.properties et créer un fichier application.yml​ 

spring:
  application:
    name: Formation Trinity Backend
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
    database-platform: org.hibernate.dialect.HSQLDialect
  datasource:
    url: jdbc:hsqldb:mem:testdb
    driverClassName: org.hsqldb.jdbcDriver
    username: sa
    password:
    platform: hsqldb
  data:
    rest:
      base-uri: /api
flyway:
  enabled: true

Spring DATA REST

2. Modification du fichier pom.xml du projet, par ajout de la dépendance :

                (...)
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-rest</artifactId>
		</dependency>
                (...)

Spring DATA REST

Exemple (table T_REGION) :

 

Entité JPA : cf. ce qui a été fait dans l'exemple Spring Data JPA

 

Repository :

package nc.gouv.dtsi.etudes.axi.formation.cog.region;

import org.springframework.data.jpa.repository.JpaRepository;

@RepositoryRestResource(collectionResourceRel = "region", path = "regions")
public interface RegionRepository extends JpaRepository<Region, String> {

    (...)

}

Spring DATA REST

3. Créer un nouveau package

 

4a. Création de la classe entité (mappe un objet de la couche de persistence, i.e. en général une table de la base de données)

4b. Création du repository

Spring DATA REST

@RepositoryRestResource

 

N'est pas obligatoire. Permet de préciser et / ou modifier les détails d'exportation et d'implémentation de l'Interface lors de l'exécution.

Par défaut, c'est le nom de l'entité qui est utilisé pour mapper le endpoint.

Spring DATA REST

Le Repository est donc une interface et les opérations possibles sont définies au niveau de l'Interface étendue, JpaRepository dans notre exemple.

 

Les différents types de Repository :

- CrudRepository<T, ID extends Serializable>

- PagingAndSortingRepository<T, ID extends Serializable>

- RevisionRepository<T, ID extends Serializable>

- JpaRepository<T, ID extends Serializable>

...

 

Cf. la javadoc...

Spring DATA REST

Spring Data REST utilise HAL pour formatter la réponse en JSON. Ce format fournit en particulier des informations supplémentaires pour la navigation dans le résultat de la requête. 

Spring DATA REST

Différences avec SPRING DATA JPA ?

Spring DATA REST

A VOUS DE JOUER !

Spring HATEOAS

Documentation de référence ici

Pas utilisé à la DTSI !

FOCUS

API SPECIFICATION

 

Cloner le projet :

 

https://github.com/DVanderstoken/formation-trinity-backend-final.git

 

et basculer sur la branche feat/spring-data-jpa

git clone https://github.com/DVanderstoken/formation-trinity-backend-final.git
Clonage dans 'formation-trinity-backend-final'...
remote: Counting objects: 162, done.
remote: Compressing objects: 100% (73/73), done.
remote: Total 162 (delta 36), reused 149 (delta 23), pack-reused 0
Réception d'objets: 100% (162/162), 948.83 KiB | 43.00 KiB/s, fait.
Résolution des deltas: 100% (36/36), fait.
Vérification de la connectivité... fait.

cd formation-trinity-backend-final/

git checkout feat/spring-data-jpa 

La branche feat/spring-data-jpa est paramétrée pour suivre la branche distante feat/spring-data-jpa depuis origin.
Basculement sur la nouvelle branche 'feat/spring-data-jpa'

Effectuer des recherches "complexes" :

 

 

 

Rappel de ce qui a été vu : 

public interface RegionRepository extends JpaRepository<Region, String> {


	Region findByCodeRegionOrNomTypoEnrichie(final String pCodeRegion,
			final String pNomTypoEnrichie);

}

Beaucoup de combinaison possible mais lisibilité rapidement limitée du code, en particulier le nom de la méthode...

Mise en oeuvre de l'API Specification :

 

- Dans le fichier pom.xml du projet :

<build>
    <plugins>
        (...)
        <!-- Used to generate JPA Static Metamodel & Specification API usage -->
        <plugin>
            <groupId>org.bsc.maven</groupId>
	    <artifactId>maven-processor-plugin</artifactId>
	    <version>3.2.0</version>
	    <executions>
	        <execution>
	            <id>process</id>
		    <goals>
		        <goal>process</goal>
		    </goals>
		    <phase>generate-sources</phase>
		    <configuration>
		        <processors>
                            <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
		        </processors>
		    </configuration>
                </execution>
	    </executions>
	    <configuration>
	        <outputDirectory>src/main/generated</outputDirectory>
	    </configuration>
	    <dependencies>
	        <dependency>
	            <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-jpamodelgen</artifactId>
	            <version>5.0.2.Final</version>
	        </dependency>
            </dependencies>
        </plugin>
        (...)
    </plugins>
</build>

Mise en oeuvre de l'API Specification :

 

- Dans l'environnement de développement Eclipse :

1/ clic droit sur le projet concerné ;

2/ clic sur [Properties] ;

3/ Dans la boîte de dialogue des préférences du projet, déplier [Java Compiler], et sélectionner [Annotation processing] :

        3.1/ cocher [Enable project specific settings]

        3.2/ cocher [Enable annotation processing]

        3.3/ cocher [Enable processing in editor]

        3.4/ spécifier le dossier dans lequel seront stockées les sources générées

4/ clic sur [OK]

Mise en oeuvre de l'API Specification :

 

Cette configuration va permettre la génération automatique des classes correspondant au JPA Static Metamodel pour les classes annotées @Entity.

 

Les classes ainsi créées sont suffixées par '_'.

Mise en oeuvre de l'API Specification :

 

L'interface Specification<T> définit une seule méthode permettant de créer un prédicat - au sens JPA - et de construire la clause WHERE :

javax.persistence.criteria.Predicate	toPredicate(javax.persistence.criteria.Root<T> root, 
                                                    javax.persistence.criteria.CriteriaQuery<?> query, 
                                                    javax.persistence.criteria.CriteriaBuilder cb)

Mise en oeuvre de l'API Specification :

 

Modification de l'interface Repository (exemple) :

package nc.gouv.dtsi.etudes.axi.formation.cog.commune;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface CommuneRepository
		extends JpaRepository<CommuneAbregee, CommuneId>,
		JpaSpecificationExecutor<CommuneAbregee> {

}

Il faut hériter de l'interface JpaSpecificationExecutor<T> qui amène les méthodes de recherche :

long	count(Specification<T> spec)
List<T>	findAll(Specification<T> spec)
Page<T>	findAll(Specification<T> spec, Pageable pageable)
List<T>	findAll(Specification<T> spec, Sort sort)
T	findOne(Specification<T> spec)

Mise en oeuvre de l'API Specification :

 

Les prédicats sont créés de manière unitaire dans une classe dédiée et assemblés lors de l'appel au Repository :

package nc.gouv.dtsi.etudes.axi.formation.cog.commune;

import (...);

public class CommuneSpecification {

	public static Specification<CommuneAbregee> isInRegion(final String codeRegion) {
		return checkSpec(CommuneAbregee_.codeRegion, codeRegion);
	}

	public static Specification<CommuneAbregee> isInDepartement(
			final String codeDepartement) {
		return checkSpec(CommuneAbregee_.codeRegion, codeDepartement);
	}

	public static Specification<CommuneAbregee> isTheCommune(
			final String codeCommune) {
		return checkSpec(CommuneAbregee_.codeRegion, codeCommune);
	}

	private static Specification<CommuneAbregee> checkSpec(
			SingularAttribute<Commune, String> propertyName, String content) {
		if (StringUtils.trimToNull(content) != null) {
			return (root, query, cb) -> cb.equal(root.get(propertyName),
					content);
		}
		return null;
	}
}

Mise en oeuvre de l'API Specification :

 

Les prédicats sont créés de manière unitaire dans une classe dédiée et assemblés lors de l'appel au Repository :

package nc.gouv.dtsi.etudes.axi.formation.cog.commune;

import (...);

@RestController
@RequestMapping(path = "/api/v1")
@CrossOrigin(origins = "*")
public class CommuneController {

	@RequestMapping(path = "/communes/search"
                      , method = RequestMethod.GET
                      , produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
	public Page<CommuneAbregee> search(
			@RequestParam(name = "region", 
                                      required = false) final String pRegion,
			@RequestParam(name = "departement", 
                                      required = false) final String pDepartement,
			@RequestParam(name = "commune", 
                                      required = false) final String pCommune,
			Pageable pPageable) {

		return lCommuneRepository.findAll(where(isInRegion(pRegion))
				.and(isInDepartement(pDepartement)).and(isTheCommune(pCommune)),
				pPageable);

	}

}

Exécuter l'application et tester...

A vous de jouer !

FOCUS

Externalisation de la configuration

 

Externalisation de la configuration

cf. src/main/ressources/application.yml

 

Documentation ici

 

Première approche à approfondir lors de l'intégration à l'écosystème Trinity Backend (Cf. Spring Cloud).

FOCUS

Flyway

Le fichier pom.xml

 

Dépendances supplémentaires :

                <dependency>
			<groupId>org.flywaydb</groupId>
			<artifactId>flyway-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
		</dependency>
		<dependency>
			<groupId>net.sf.supercsv</groupId>
			<artifactId>super-csv</artifactId>
			<version>2.4.0</version>
		</dependency>

Documentation de référence ici

Outil de migration des bases de données.

- Utilise :

      - le langage SQL

      - le langage Java

      - un mix des deux

- Fournit un client de ligne de commande

- S'intègre facilement avec Spring

 

Convention over configuration !

Configuration (cf. fichier application.yml) :

flyway:
 enabled: true

s'appuie sur la configuration Spring d'accès à le source de données !

spring:
  datasource:
    url: jdbc:hsqldb:mem:testdb
    driverClassName: org.hsqldb.jdbcDriver
    username: sa
    password:
    platform: hsqldb

Les migrations

 

Cf. répertoires : 

- src/main/resources.db/migration (migrations SQL)

- src/main/java/db/migration (migrations Java)

- Convention over configuration

 

- importance de la gestion des versions

 

- pas de retour arrière !

A retenir

Les tests

ou comment éviter...

Les tests

Les tests

Adapter au(x) besoin(s) et au contexte...

Cas particulier ici : peu, voire pas de services (couche métier)

 

Tests par couche !

    --> tests de la couche d'accès aux données

    --> tests de la couche controllers

Les tests

Exemple sur la couche d'accès aux données :

package nc.gouv.dtsi.etudes.axi.formation.cog.tests;

import (...);

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
public class RegionRepositoryTest extends FormationTrinityBackendAbstractTest {

	@Autowired
	private RegionRepository lRegionRepository;

	@Test
	public void shouldReturnNonEmptyRegionList() {

		List<Region> result = lRegionRepository.findAll();

		assertFalse(result.isEmpty());

	}

}

Les tests

@RunWith(SpringJUnit4ClassRunner.class)

 

Junit va invoquer la classe spécifier par l'annotation @RunWith pour exécuter les tests. La classe spécifiée SpringJUnit4ClassRunner est une extension JUnit qui fournit à JUnit les fonctionnalités du Spring TestContext Framework.

 

@SpringBootTest(classes = FormationTrinityBackendApplication.class)

 

Permet d'exécuter les tests dans le contexte, avec la configuration et dans l'environnement de l'application Spring Boot à laquelle les tests se rapportent.

Les tests

Exemple sur la couche controller :

package nc.gouv.dtsi.etudes.axi.formation.layer.controller;

import (...)

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
@WebAppConfiguration
public class RegionControllerTest extends FormationTrinityBackendAbstractTest {

	private MockMvc lMockMvc;

	@Autowired
	private WebApplicationContext lContext;

	@Before
	public void setUp() {
		lMockMvc = MockMvcBuilders.webAppContextSetup(lContext).build();
	}

	@Test
	public void shouldReturnNonEmptyRegionListWithHttpCode200()
			throws Exception {

		lMockMvc.perform(get("/api/v1/regions")).andExpect(status().isOk())
				.andExpect(
						content().contentType(MediaType.APPLICATION_JSON_UTF8));
	}

}

Les tests

@WebAppConfiguration

 

L'utilisation de cette annotation sur une classe de test signifie que ledit test va être exécuté dans le contexte de l'application (Web).

Les tests

Un peu de factorisation...

Les tests

Ce qu'il y a en commun :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)

Une proposition ?

Les tests

Un peu de factorisation...

package nc.gouv.dtsi.etudes.axi.formation;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import nc.gouv.dtsi.etudes.axi.formation.FormationTrinityBackendFinalApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendFinalApplication.class)
public abstract class FormationTrinityBackendAbstractTest {

}
public class RegionRepositoryTest extends FormationTrinityBackendAbstractTest {

(...)
public class RegionControllerTest extends FormationTrinityBackendAbstractTest {

(...)

A vous de jouer !

documenter les api rest

cf. la documentation ici.

documenter les api

Enrichir le fichier pom.xml de l'application avec : 

documenter les api

                <dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-data-rest</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-bean-validators</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.6.1</version>
		</dependency>

Créer une classe de configuration de la génération de la documentation :

documenter les api

package nc.gouv.dtsi.etudes.axi.formation;

import (...);

@Configuration
@EnableSwagger2
public class ApplicationApiDocConfiguration {

	@Autowired
	private Environment environment;

	/**
	 * Enabled only if Spring active profiles contains the apidoc profile !
	 * 
	 * @return a SpringFox Docket
	 */
	@Bean
	public Docket docket() {
		return new Docket(DocumentationType.SWAGGER_2)
				.enable(Arrays.asList(this.environment.getActiveProfiles())
						.contains("apidoc"));
	}

}

La documentation des APIs est ici rendue disponible uniquement s'il y a un profile actif - au sens Spring du terme - nommé "apidoc".

 

La présence de ce profil peut ainsi être gérée en fonction des cycles (intégration, qualification, production, etc.) s'il n'est pas souhaité de la rendre disponible dans un cycle donné.

documenter les api

documenter les api

Made with Slides.com