
Elastic Search ou Solr sont certainement les plus pertinentes. Mais ces solutions introduisent de la complexité (notamment sur votre architecture applicative).
L’autre solution est d’utiliser les fonctionnalités offertes par votre solution de base de données. Les résultats seront peut être moins bon ou plus long, mais vous pouvez ainsi répondre à un besoin de recherche fulltext rapidement en utilisant votre infrastructure en place. Cette solution naïve peut être un bon point de départ avant de faire plus compliqué.
Il est temps de prendre un exemple concret. Pour celà je vais me baser sur du code Kotlin et une base de données MongoDb. Comme je participe au développement du site de la conférence MiXiT, notre use case est tout trouver : rechercher des mots clés dans le descriptif des conférences ou dans les bios des speakers…. Le code est Open Source est est disponible sous Github.
Dans le cadre du site MiXiT, nous avons choisi MongoDB pour plusieurs raisons. MongoDB
est une bases de données NoSQL reconnue, offrant de bonnes performances, souple niveau schéma et offrant des capacités d’indexation.
propose un driver Java non bloquant permettant dand notre cas d’avoir une application réactive non bloquante du client jusqu’à la base de données. Ce n’est pas le sujet de cet artile mais nous avons utilisé le nouveau framework WebFlux de Spring.
permet de lancer des recherches full text depuis la version 2.4.
Nous allons nous focaliser sur cette dernière fonctionnalité. Pour la recherche fulltext, MongoDB
permet d’indexer différents champs en vous laissant la possibilité de définir des poids (weighting) qui seront utilisés pour calculer un score pour les résultats retournés
supporte différents langages comme français, anglais, allemand, espagnol…
permet d’utiliser des requêtes avancées similaires à ce que vous pouvez faire dans google. Par exemple +chat -cheval cherchera les champs qui contiennent chat et non cheval.
implémente le stemming (voir le premier paragraphe) pour être souple dans les recherches
supprime les mots fréquents du langage (Stop words).
La commande ci dessous, permet de créer un index sur la collection conference sur le champ description
db.conferences.createIndex( { description: "text" } )
Vous pouvez définir plusieurs champs et des poids. Les poids sont utilisés pour classer par pertinence les résultats. Pour chaque champ indexé MongoDB applique un poids par défaut de 1. Le score est la somme des points d’un document.
db.blog.createIndex( { content: "text", keywords: "text", about: "text" }, { weights: { content: 10, keywords: 5 } } )
Pour plus d’information sur les possibilités offertes par MongoDB sur l’indexation, je vous laisse vous reporter à la documentation officielle. Nous allons voir maintenant comment gérer l’interaction dans notre code Java ou Kotlin. Le tout via le framework Spring.
Le projet Spring Data MongoDB permet de simplifier les interactions entre votre base de données MongoDB et votre application Spring.
Commençons par ajouter les dépendances dans le script de configuration Gradle. Nous ajoutons des dépendances pour utiliser SpringBoot, WebFlux, SpringData pour Mongo et MongoDb
compile("org.springframework.boot:spring-boot-starter-webflux") compile("org.springframework.boot:spring-boot-starter-data-mongodb-reactive") runtime("de.flapdoodle.embed:de.flapdoodle.embed.mongo")
Comme vous pouvez le voir nous avons fait le choix en développement d’utiliser de.flapdoodle.embed.mongo
qui est une base de données embarquée. Cette solution vous évite de devoir installer une base de données avant de faire des tests. Comme nous utilisons Spring Boot, vous n’avez pas plus de paramètres à donner. En effet la classe org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration
va automatiquement configurer la base de données en appliquant les conventions de base.
Vous pouvez maintenant définir un document MongoDb (équivalent d’une table si nous devions faire un parallèle avec une base de données relationnelles classique)
@Document data class Talk( val format: TalkFormat, @TextIndexed(weight = 10F) val title: String, @TextIndexed(weight = 5F) val summary: String, val speakerIds: List<String> = emptyList(), val language: Language = Language.FRENCH, @TextIndexed val description: String? = null, val start: LocalDateTime? = null, val end: LocalDateTime? = null, @Id val id: String? = null )
L’annotation @TextIndexed
permet de définir les champs qui devront être indexés par MongoDB. Vous pouvez préciser un poids à chaque champ. Dans cet exemple, je donne plus de poids quand le texte recherché est trouvé dans le titre d’une session.
Il ne reste plus qu’à lancer une requête fullText via MongoDB. Spring Data propose une abstraction pour lancer des requêtes
@Repository class TalkRepository(private val template: ReactiveMongoTemplate) { fun findOne(id: String) = template.findById<Talk>(id) fun findFullText(criteria: List<String>): Flux<Talk> { val textCriteria = TextCriteria() criteria.forEach { textCriteria.matching(it) } val query = TextQuery(textCriteria).sortByScore() return template.find(query) } }
En quelques lignes nous venons de voir comment lancer une recherche fullText dans une applicaton Spring Boot Kotlin. Le code en Java est très similaire de ce qui a été montré ici.