Installation

Le code du projet est disponible sur le repository suivant:

https://github.com/patrick-hg/demo-spring-elasticsearch

Ajouter les dependances suivantes dans un projet Spring Boot.

<dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>

A la rédaction de cet article nous avions obtenons la version suivante du projet:
    spring-data-elasticsearch: 3.2.6

Nous ajouteront aussi Swagger pour appeler nos services, ainsi que OpenCSV qui nous aidera par la suite à parser un fichier de données au format CSV.

<dependency>             <groupId>io.springfox</groupId>             <artifactId>springfox-swagger2</artifactId>             <version>2.9.2</version> </dependency> <dependency>             <groupId>io.springfox</groupId>             <artifactId>springfox-swagger-ui</artifactId>             <version>2.9.2</version> </dependency> <dependency>             <groupId>com.opencsv</groupId>             <artifactId>opencsv</artifactId>             <version>4.6</version> </dependency>

Projet Spring Data Elasticsearch sur GitHub:

https://github.com/spring-projects/spring-data-elasticsearch

En se référant à la table si dessous, on télécharge la version correspondante d’Elasticsearch. Dans notre cas ce sera la 6.8.1

Spring Data ElasticsearchElasticsearch
3.2.x6.8.1
3.1.x6.2.2
3.0.x5.5.0
2.1.x2.4.0
2.0.x2.2.0
1.3.x1.5.2

Télécharger et démarrer Elasticsearch

https://www.elastic.co/downloads/past-releases/elasticsearch-6-8-1

Présentation d’Elasticsearch

Elasticsearch est une base de données documents, sous forme de simple texte JSON, orientée vers la recherche de données, notamment le full text search. Elasticsearch est écrite en Java et basée sur la technologie Apache Lucene.

Avec Elasticsearch nous avons aussi QueryDSL qui permet de requêter la base de donnée via une syntaxe Json.

Si on veut comparer avec les bases de données relationelles, une table est un index, un tuple dans une table est un document.

Elasticsearch fait partie de la stack elastic ELK, comptant plusieurs produits qui peuvent interagir ensemble.

Nous pouvons citer plusieurs outils connus, dont les suivantes:

 Kibana est une plateforme pour construire des graphiques utiles pour le monitoring d’applications. 
 Logstash est une plateforme de traitement les logs, et gestion d’évenements

Interragir avec elasticsearch via les commandes:

Retourner un document spécifique en utilisant son identifiantcurl -X GET <host>/<index>/<type>/<doc_id> ex: curl -X GET « localhost:9200/messages/message/NqdFMH0B3IoxXuQ8Ubht »
Supprimer un documentcurl -X DELETE localhost:9200/<index>/<type>/<documentID>
Supprimer un indexcurl -X DELETE localhost:9200/<index>
ex:       curl -X DELETE localhost:9200/users             curl -X DELETE localhost:9200/posts Cette commande supprime un index avec tout les documents qu’il contient.
Retourner le nombre de documents sur un indexcurl -X GET <host>/<index>/_count
Ex: curl -X GET « localhost:9200/users/_count?pretty »
Lister tout les documents sur in indexcurl -X GET <host>/<index>/_search
ex:   curl -X GET ‘localhost:9200/users/_search?pretty’ retournera une liste de tout les utilisateurs.
Retourne des informations sur le cluster Elasticsearchcurl -X GET http://localhost:9200/_cluster/stats?pretty

Le fait d’ajouter pretty à toute requêtte, rendra le retour (Json) plus facile a lire.

Requêter Elasticsearch via QueryDSL

Rechercher un utilisateur par son usernamecurl -X GET « localhost:9200/users/_search?pretty » -H ‘Content-Type: application/json’ -d’ {   « query »: {     « term »: {       « username »: « kim.lan »     }   } } ‘
Rechercher les utilisateurs qui ont moins de 25 ans  curl -X GET « localhost:9200/users/_search?pretty » -H ‘Content-Type: application/json’ -d’ {   « query »: {     « range »: {       « age »: {         « lt »: « 25 »       }     }   } } ‘
Rechercher les messages d’un utilisateurcurl -X GET « localhost:9200/messages/_search?pretty » -H ‘Content-Type: application/json’ -d’ {   « query »: {     « term »: {       « username »: « kim.lan »     }   } } ‘

Modèle

Dans notre exemple, on prendra le modele suivant:

Nous avons des utilisateur et des messages.

Un utilisateur “User” a un identifiant, un nom, un pseudo unique (username) et des données personnelles.

Un message “Message” a un identifiant, du contenu, des données de géolocalisation, etc…

Les utilisateurs publient des messages.

User

@Document(indexName = "users", type = "user") public class User {     @Id     private String id;     private String username;     private String name;     private int age;     private String nationality;     private Date memberSince;     protected User() {     }     public User(String username, String name, int age, String nationality, Date memberSince) {         this.username = username;         this.name = name;         this.age = age;         this.nationality = nationality;         this.memberSince = memberSince;     }     // Getters & setters ...

Message

@Document(indexName = "messages", type = "message") public class Message {     @Id     private String id;       private String content;     private List<String> tags;     private String localization;     private Boolean available;     @Field(type = FieldType.Date)     private String creationDate;     private String username;     private String language;     protected Message() {     }     public Message(String localization, String username, String content, String language, String creationDate) {         this.localization = localization;         this.username = username;         this.content = content;         this.creationDate = creationDate;         this.available = true;         this.language = language;         this.tags = Utils.tagsFromText(content);     }     // Getters & setters ...

Configuration

Application Properties

# Local Elasticsearch config spring.data.elasticsearch.repositories.enabled=true spring.data.elasticsearch.cluster-nodes=localhost:9300   # App config server.port=8102 spring.application.name=BootElastic

SwaggerConfig

@Configuration @EnableSwagger2 public class SwaggerConfig {     @Bean     public Docket api() {         return new Docket(DocumentationType.SWAGGER_2)                 .select()                 .apis(RequestHandlerSelectors.any())                 .paths(PathSelectors.any())                 .build();     } }

Repositories et Requêtes

UserRepository

@Repository public interface UserRepository extends ElasticsearchRepository<User, String> {     Optional<User> existsByUsername(String username); }

MessageRepository

@Repository public interface MessageRepository extends ElasticsearchRepository<Message, String> {    etc...
Page<Message> findByUsernameOrderByCreationDate(String name, Pageable pageable);

Ici Spring-data construira automatiquement la requete pour nous.

/* "query": {     "term": {         "username": "?0"     } }   */ @Query("{\"term\": {\"username\": \"?0\"}}") Page<Message> findByUsernameUsingCustomQuery(String name, Pageable pageable); // elasticsearch term query

Ici on déclare une requete QueryDSL pour elasticsearch.

On recherche les messages dont le username est égal à la valeur du paramètre.

/* "query": {     "bool": {         "should": [             {                 "match": {                     "content": "?0"                 }             },             {                 "match": {                     "username": "?0"                 }             },             {                 "match": {                     "localization": "?0"                 }             },             {                 "match": {                     "language": "?0"                 }             }         ]     } }  */ @Query("{\"bool\": {\"should\": [{\"match\": {\"content\": \"?0\"}}, {\"match\": {\"username\": \"?0\"}}, {\"match\": {\"localization\": \"?0\"}}, {\"match\": {\"language\": \"?0\"}}]}}") Page<Message> search(String text, Pageable pageable);  

Dans ce cas on recherche les messages qui devraient matcher avec les différentes conditions mais sans que ce soit stricte. Les documents avec le plus de matchs seront premiers parmis les résultats de recherche.

Services

UserService

@Service public class UserService {     @Autowired     private UserRepository userRepository;     public List<User> getAll() {         List<User> users = new ArrayList<>();         userRepository.findAll().forEach(users::add);         return users;     }     public Optional<User> findByUsername(String username) {         return userRepository.existsByUsername(username);     }     public void persistUser(User user) {         if (findByUsername(user.getUsername()).isEmpty()) {     // seulement s'il n'existe pas             userRepository.save(user);         }     }     public void persistAll(List<User> users) {         users.forEach(this::persistUser);     } }

MessageService

@Service public class MessageService {     @Autowired     private MessageRepository messageRepository;     public List<Message> getAll() {         List<Message> tweets = new ArrayList<>();         messageRepository.findAll().forEach(tweets::add);         return tweets;     }       public List<Message> findByUsername(String username, Integer pageNum, Integer pageSize) {         Page<Message> pageResult = messageRepository.findByUsernameOrderByCreationDate( username, pageableOf(pageNum, pageSize));         return pageResult != null ? pageResult.getContent() : Collections.emptyList();     }       public List<Message> findByUsernameUsingCustomQuery(String username, Integer pageNum, Integer pageSize) {         Page<Message> pageResult = messageRepository.findByUsernameUsingCustomQuery( username, pageableOf(pageNum, pageSize));         return pageResult != null ? pageResult.getContent() : Collections.emptyList();     }       public SearchResult search(String text, String from, String until, Integer pageNum, Integer pageSize) {         Page<Message> pageResult = Strings.isEmpty(from) && Strings.isEmpty(until)                 ? messageRepository.search(text, pageableOf(pageNum, pageSize))                 : messageRepository.searchWithDateRange(text, from, until, pageableOf(pageNum, pageSize));         return new SearchResult(pageResult.getContent(), pageResult.getTotalElements(), pageResult.getTotalPages(), pageResult.getNumberOfElements());     } }

Controllers

UserController

@RestController @RequestMapping("/user") public class UserController {     @Autowired     private UserService userService;     @GetMapping     public List<User> getAll() {         return userService.getAll();     }     @RequestMapping(value = "/new", method = RequestMethod.POST)     public void persist(@RequestBody User user) {         userService.persistUser(user);     } }

MessageController

@RestController @RequestMapping("/message") public class MessageController {     @Autowired     private MessageRepository messageRepository;     @Autowired     private MessageService messageService;     @GetMapping     public List<Message> getAll() {         return messageService.getAll();     }     @RequestMapping(value = "/new", method = RequestMethod.POST)     public Message persist(@RequestBody Message message) {         if (Strings.isEmpty(message.getId())) {             message.setCreationDate(LocalDate.now().toString());         }         message.setTags(Utils.tagsFromText(message.getContent()));         return messageRepository.save(message);     }     @GetMapping(value = "/find-by-username")     public List<Message> findByUsernameWithPagination(@RequestParam String username,                                                       @RequestParam(required = false) Integer pageNum,                                                       @RequestParam(required = false) Integer pageSize,                                                       @RequestParam Boolean useCustomQuery) {         if (useCustomQuery) {             return messageService.findByUsernameUsingCustomQuery(username, pageNum, pageSize);         }         return messageService.findByUsername(username, pageNum, pageSize);     }     @GetMapping(value = "/search")     public SearchResult search(@RequestParam String text,                                @RequestParam(required = false) Integer pageNum,                                @RequestParam(required = false) Integer pageSize,                                @RequestParam(required = false) String from,                                @RequestParam(required = false) String until) {         return messageService.search(text, from, until, pageNum, pageSize);     } }

Générer des Données de test

ImportFromCSV: On charge des utilisateurs et des messages depuis les deux fichiers de données users.csv et messages.csv

users.csv

username    ; name           ; age ; nationalite  ; membreDepuis jean.dupond ; Jean DUPOND    ; 23  ; FRANCAISE    ; 2019-05-12 john.doe    ; John DOE       ; 24  ; ANGLAISE     ; 2019-07-12 rick.martin ; Rick MARTIN    ; 25  ; ANGLAISE     ; 2020-04-12 paul.martin ; Paul MARTIN    ; 27  ; FRANCAISE    ; 2020-10-12
etc ...

messages.csv

localization; username      ; language ; content Paris       ; jean.dupond   ; ANGLAIS  ; this is a message text with #someHashtags London      ; john.doe      ; FRANCAIS ; Le @RCLens retrouve sa place New York    ; rick.martin   ; ANGLAIS  ; We just launched our #Kickstarter pre-launch Miami       ; paul.martin   ; ANGLAIS  ; XRP Toolkit v2 expands Xumm's feature set even Los Angeles ; aadesh.patel  ; ANGLAIS  ; I've been fooling around with a Kuwahara Bombay      ; marc.martin   ; ANGLAIS  ; Definitely the clearest, and most starter- London      ; marc.henry    ; ANGLAIS  ; Asynchronous RDBMS access with #Spring Data
etc ...

Sources:

Le projet « Devoir de Conseil » est un projet transverse au Groupe avec de nombreuses équipes contributrices : architectes, équipes d’exploitation par filiale, patrimoines applicatifs, centres de services, équipe socle (data).

Ce projet est constitué de plusieurs briques :

Lors de cette mission, l’analyste devra :

DSI qui s’occupe de la direction finance

Recherchent en Dev Expert People Soft 8.9 (appli Legacy)

Tache :

Il s’agit de travailler sur un site en charge de l’onboarding / revue des données de nos clients afin entre-autre de répondre aux exigences régulateurs.

Le site est actuellement en ANGULAR 11 (90% du travail) et JAVA (proxy KUBERNETES pour interagir avec d’autres API).

Front en Angular 11 : 90% du travail au quotidien

Une partie BACK en JAVA 11. Migration vers KUBERNETES en cours.

Passage sur le cloud public en cours – JENKINS – GIT sur AZURE – Utilisation CDN et AKS

Finance de Marchés – Maîtrise d’œuvre Front Office Murex (SI Front de gestion des produits change & hybrides taux-change)

L’expertise du progiciel Murex Front est indispensable.

Prestation en méthode agile en collaboration avec d’autres membres de l’équipe (MOE & MOA).
Vous êtes tenu de communiquer régulièrement l’état d’avancement de vos réalisations en cours, assurer la qualité de vos livrables et partager vos connaissances avec les autres membres de l’équipe.
Vous devez également participer à l’amélioration des processus et des outils de développement et automatisation des tests nécessaires au bon fonctionnement de l’application.
Vous serez sollicité dans l’analyse des performances des différents process Murex et le développement des optimisations jugées nécessaires.
Une intervention occasionnelle le week-end ou la nuit vous sera demandée en cas de mises en production importantes ou d’événement majeur nécessitant un suivi particulier.

2j tt / 3j sur site

Contexte: Développer une application et utiliser l’application qui développe, à partir d’un produit du marché EBX.

On peut customiser le produit en java.

Utilisation de ETL pour le développement de l’application

Une expérience confirmée en data steward : pas de compétences informatiques

La mission principale est la saisie de données, c’est pas quelque chose d’automatisable car il faut analyser les données, juger la légitimité de ses données et définir si oui ou non la demande est acceptable. Il est garant de la qualité de données.

Nous sommes actuellement à la recherche d’un profil doté une expérience significative en tant qu’architecte ou d’expert Sales force.

Le profil doit être plutôt technique et il doit être expérimenté.

Poste de support applicatif Equity

L’équipe supporte l’ensemble des applications utilisées pour l’activité Equity (Front, Middle, Back et Risques)


L’activité de l’équipe est : Gestion des incidents , des demandes, des habilitations. Monitoring , suivi des problèmes


Le poste nécessite à la fois des compétences fonctionnelles sur l’activité Equity mais également des compétences techniques (SQL, Windows, Shell Unix)
Contraintes horaires et astreintes au programme